Josh
Josh

Reputation: 3265

Lua: Line breaks in strings

I've been working on a formatter that will take a long string and format it into a series of lines broken off at the word within a certain character limit. For instance, He eats the bread, broken off at every 8 characters, would return something like:

He eats
the
bread

This is because "He eats" contains 7 characters, and "the bread" contains 9, so it has to break off at "the" and continue with "bread".

The script itself has been working wonderfully, thanks to the help of other members before. However, now I have a new challenge.

I'm utilizing a free-entry box for entering the series of lines. It's for a program called MUSHclient, if anyone is familiar with it. The utils.editbox opens up an editor text box, and allows free form, and returns the result as a string. For example:

result = utils.editbox("What are you typing?")

saves the typed response into result which is a string type. I want to put line breaks in properly, but I can't figure out how to do that. If I print(result), it returns something like the following:

This is the result of three separate paragraphs.

As you can see, there is a line break between each paragraph.

So the result contains line breaks, or control characters.

Now, I've managed to extrapolate the individual lines, but not the linebreaks in between, using the following code:

for line in result:gmatch("[^%c]+%c?") do 
  Send(note_wrap(line))
end

How can I extrapolate for linebreaks as well as textual content?

Edit For reference, the note_wrap function is as follows:

function note_wrap(str, limit, indent, indent1)
  indent = indent or ""
  indent1 = indent1 or indent
  limit = limit or 79
  local here = 1-#indent1
  local last_color = ''
  return indent1..str:gsub("(%s+)()(%S+)()",
    function(sp, st, word, fi)
      local delta = 0
      local color_before_current_word = last_color
      word:gsub('()@([@%a])', 
        function(pos, c)
          if c == '@' then 
            delta = delta + 1 
          elseif c == 'x' then 
            delta = delta + 5
            last_color = word:sub(pos, pos+4)
          else                 
            delta = delta + 2 
            last_color = word:sub(pos, pos+1)
          end
        end)
      here = here + delta
      if fi-here > limit then
        here = st - #indent + delta
        return "\n"..indent..color_before_current_word..word
      end
    end)
end

The reason for this code is to be able to take @rHe eats @x123the bread, ignore the color codes (indicated by @<letter> or @x<digits>) and return the aforementioned result of:

He eats
the
bread

but insert the color codes so it then returns:

@rHe eats
@x123the
bread

If this can be modified to recognize new lines and put an actual new line in, that would be fabulous.

Edit 2 Tried Paul's solution, and it's not doing what I'm needing it to. I may not have been clear enough, so I'll try to clear it up in this edit.

When I type into the free form box, I want the information presented exactly as is, but formatted to break correctly AND maintain entered newlines. For example, if I write this in the free form box:

Mary had a little lamb, little lamb, little lamb
Mary had a little lamb
It's fleece was white as snow

using Paul's code, and setting string to 79, I get:

Mary had a little lamb, little lamb, little lamb

Mary had a little lamb

It's fleece was white as snow

And that's not what I want. I'd want it to return it just as it was written, and breaking line as necessary. So if I had a 20 character limit, it'd return:

Mary had a little
lamb, little lamb,
little lamb
Mary had a little
lamb
It's fleece was
white as snow

If I added manual line breaks, I'd want it to respect those, so if I hit return twice after the first line, it'd have a line break under the first line as well. For example, if I wrote this post in the free form box, I'd want it to respect every new paragraph and every proper line break as well. I hope this clears things up.

Upvotes: 4

Views: 17821

Answers (1)

Paul Kulchenko
Paul Kulchenko

Reputation: 26744

I use something like this in my code (this assumes that \n is the line separator):

local function formatUpToX(s, x, indent)
  x = x or 79
  indent = indent or ""
  local t = {""}
  local function cleanse(s) return s:gsub("@x%d%d%d",""):gsub("@r","") end
  for prefix, word, suffix, newline in s:gmatch("([ \t]*)(%S*)([ \t]*)(\n?)") do
    if #(cleanse(t[#t])) + #prefix + #cleanse(word) > x and #t > 0 then
      table.insert(t, word..suffix) -- add new element
    else -- add to the last element
      t[#t] = t[#t]..prefix..word..suffix
    end
    if #newline > 0 then table.insert(t, "") end
  end
  return indent..table.concat(t, "\n"..indent)
end
print(formatUpToX(result, 20))
print(formatUpToX("@rHe eats @x123the bread", 8, "  "))

The cleanse function removes any markup that needs to be included in the string, but doesn't need to count against the limit.

For the example you have, I get the following output (using 20 as the limit for the first fragment and 8 for the second):

This is the result 
of three separate 
paragraphs.

As you can see, 
there is a line 
break between each 
paragraph.

So the result 
contains line 
breaks, or control 
characters.

  @rHe eats 
  @x123the 
  bread

Upvotes: 1

Related Questions