fix early wrap for multi-line input elements

This commit is contained in:
Michael Freno
2025-11-10 15:55:15 -05:00
parent bb49a600ff
commit 35113685d5

View File

@@ -4042,11 +4042,27 @@ function Element:moveCursorToPreviousWord()
return return
end end
-- Helper function to get character at position
local function getCharAt(p)
if p < 0 or p >= utf8.len(text) then
return nil
end
local offset1 = utf8.offset(text, p + 1)
local offset2 = utf8.offset(text, p + 2)
if not offset1 then
return nil
end
if not offset2 then
-- Last character in string
return text:sub(offset1)
end
return text:sub(offset1, offset2 - 1)
end
-- Skip any whitespace/punctuation before current position -- Skip any whitespace/punctuation before current position
while pos > 0 do while pos > 0 do
local offset = utf8.offset(text, pos + 1) local char = getCharAt(pos - 1)
local char = offset and text:sub(offset, utf8.offset(text, pos + 2) - 1) or "" if char and char:match("[%w]") then
if char:match("[%w]") then
break break
end end
pos = pos - 1 pos = pos - 1
@@ -4054,11 +4070,8 @@ function Element:moveCursorToPreviousWord()
-- Move to start of current word -- Move to start of current word
while pos > 0 do while pos > 0 do
local offset = utf8.offset(text, pos + 1) local char = getCharAt(pos - 1)
local char = offset and text:sub(offset, utf8.offset(text, pos + 2) - 1) or "" if not char or not char:match("[%w]") then
if not char:match("[%w]") then
-- We've moved one position past the start, so move back
pos = pos + 1
break break
end end
pos = pos - 1 pos = pos - 1
@@ -4082,11 +4095,27 @@ function Element:moveCursorToNextWord()
return return
end end
-- Helper function to get character at position
local function getCharAt(p)
if p < 0 or p >= textLength then
return nil
end
local offset1 = utf8.offset(text, p + 1)
local offset2 = utf8.offset(text, p + 2)
if not offset1 then
return nil
end
if not offset2 then
-- Last character in string
return text:sub(offset1)
end
return text:sub(offset1, offset2 - 1)
end
-- Skip current word -- Skip current word
while pos < textLength do while pos < textLength do
local offset = utf8.offset(text, pos + 1) local char = getCharAt(pos)
local char = offset and text:sub(offset, utf8.offset(text, pos + 2) - 1) or "" if not char or not char:match("[%w]") then
if not char:match("[%w]") then
break break
end end
pos = pos + 1 pos = pos + 1
@@ -4094,9 +4123,8 @@ function Element:moveCursorToNextWord()
-- Skip any whitespace/punctuation -- Skip any whitespace/punctuation
while pos < textLength do while pos < textLength do
local offset = utf8.offset(text, pos + 1) local char = getCharAt(pos)
local char = offset and text:sub(offset, utf8.offset(text, pos + 2) - 1) or "" if char and char:match("[%w]") then
if char:match("[%w]") then
break break
end end
pos = pos + 1 pos = pos + 1
@@ -4597,33 +4625,68 @@ function Element:_wrapLine(line, maxWidth)
end end
if self.textWrap == "word" then if self.textWrap == "word" then
local words = {} -- Tokenize into words and whitespace, preserving exact spacing
for word in line:gmatch("%S+") do local tokens = {}
table.insert(words, word) local pos = 1
local lineLen = utf8.len(line)
while pos <= lineLen do
-- Check if current position is whitespace
local char = getUtf8Char(line, pos)
if char:match("%s") then
-- Collect whitespace sequence
local wsStart = pos
while pos <= lineLen and getUtf8Char(line, pos):match("%s") do
pos = pos + 1
end
table.insert(tokens, {
type = "space",
text = line:sub(utf8.offset(line, wsStart), utf8.offset(line, pos) and utf8.offset(line, pos) - 1 or #line),
startPos = wsStart - 1,
length = pos - wsStart
})
else
-- Collect word (non-whitespace sequence)
local wordStart = pos
while pos <= lineLen and not getUtf8Char(line, pos):match("%s") do
pos = pos + 1
end
table.insert(tokens, {
type = "word",
text = line:sub(utf8.offset(line, wordStart), utf8.offset(line, pos) and utf8.offset(line, pos) - 1 or #line),
startPos = wordStart - 1,
length = pos - wordStart
})
end
end end
for i, word in ipairs(words) do -- Process tokens and wrap
local testLine = currentLine == "" and word or (currentLine .. " " .. word) local charPos = 0 -- Track our position in the original line
for i, token in ipairs(tokens) do
if token.type == "word" then
local testLine = currentLine .. token.text
local width = font:getWidth(testLine) local width = font:getWidth(testLine)
if width > maxWidth and currentLine ~= "" then if width > maxWidth and currentLine ~= "" then
-- Current line is full, wrap before this word
local currentLineLen = utf8.len(currentLine) local currentLineLen = utf8.len(currentLine)
table.insert(wrappedParts, { table.insert(wrappedParts, {
text = currentLine, text = currentLine,
startIdx = startIdx, startIdx = startIdx,
endIdx = startIdx + currentLineLen, endIdx = startIdx + currentLineLen,
}) })
startIdx = startIdx + currentLineLen + 1 -- +1 for the space startIdx = charPos
currentLine = word currentLine = token.text
charPos = charPos + token.length
-- Check if the word itself is too long - if so, break it with character wrapping -- Check if the word itself is too long - if so, break it with character wrapping
if font:getWidth(word) > maxWidth then if font:getWidth(token.text) > maxWidth then
local wordLen = utf8.len(word) local wordLen = utf8.len(token.text)
local charLine = "" local charLine = ""
local charStartIdx = startIdx local charStartIdx = startIdx
for j = 1, wordLen do for j = 1, wordLen do
local char = getUtf8Char(word, j) local char = getUtf8Char(token.text, j)
local testCharLine = charLine .. char local testCharLine = charLine .. char
local charWidth = font:getWidth(testCharLine) local charWidth = font:getWidth(testCharLine)
@@ -4645,12 +4708,12 @@ function Element:_wrapLine(line, maxWidth)
end end
elseif width > maxWidth and currentLine == "" then elseif width > maxWidth and currentLine == "" then
-- Word is too long to fit on a line by itself - use character wrapping -- Word is too long to fit on a line by itself - use character wrapping
local wordLen = utf8.len(word) local wordLen = utf8.len(token.text)
local charLine = "" local charLine = ""
local charStartIdx = startIdx local charStartIdx = startIdx
for j = 1, wordLen do for j = 1, wordLen do
local char = getUtf8Char(word, j) local char = getUtf8Char(token.text, j)
local testCharLine = charLine .. char local testCharLine = charLine .. char
local charWidth = font:getWidth(testCharLine) local charWidth = font:getWidth(testCharLine)
@@ -4669,8 +4732,15 @@ function Element:_wrapLine(line, maxWidth)
currentLine = charLine currentLine = charLine
startIdx = charStartIdx startIdx = charStartIdx
charPos = charPos + token.length
else else
currentLine = testLine currentLine = testLine
charPos = charPos + token.length
end
else
-- It's whitespace - add to current line
currentLine = currentLine .. token.text
charPos = charPos + token.length
end end
end end
else else