luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit cbbaa8fda857e5bfc3642f732bb56a365bf45bbb
parent 6d8e2ce424a94807a0aa2669ef1f1868d9c61d37
Author: luajitos <bbhbb2094@gmail.com>
Date:   Sat,  6 Dec 2025 22:20:04 +0000

Text selection now replaceable/pastable

Diffstat:
Miso_includes/apps/com.luajitos.lunareditor/src/editor.lua | 94++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Miso_includes/apps/com.luajitos.taskbar/src/init.lua | 16++++++++--------
Miso_includes/os/init.lua | 109++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Miso_includes/os/libs/Sys.lua | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Miso_includes/os/postinit.lua | 38++++++++++++++++++++++++++++++++++++--
5 files changed, 287 insertions(+), 37 deletions(-)

diff --git a/iso_includes/apps/com.luajitos.lunareditor/src/editor.lua b/iso_includes/apps/com.luajitos.lunareditor/src/editor.lua @@ -264,14 +264,7 @@ end -- Input callback window.onInput = function(key, scancode) - local baseScancode = scancode % 128 - - -- Debug: print key info - if key then - print("Lunar Editor: key='" .. key .. "' byte=" .. tostring(key:byte()) .. " scancode=" .. tostring(scancode) .. " base=" .. tostring(baseScancode)) - else - print("Lunar Editor: key=nil scancode=" .. tostring(scancode) .. " base=" .. tostring(baseScancode)) - end + local baseScancode = (scancode or 0) % 128 -- Arrow keys if baseScancode == 72 then -- Up @@ -442,6 +435,91 @@ window.onPaste = function(content, contentType) window:markDirty() end +-- Selection edit callback for multi-line selection editing +-- point1 and point2 are {x, y} coordinates in the content area +window.onSelectionEditted = function(point1, point2, newContent) + -- Convert y coordinates to line numbers + -- Text starts at y = toolbarHeight + 2, each line is lineHeight pixels + local textStartY = toolbarHeight + 2 + + local startLine = scrollY + math.floor((point1.y - textStartY) / lineHeight) + 1 + local endLine = scrollY + math.floor((point2.y - textStartY) / lineHeight) + 1 + + -- Convert x coordinates to column positions + -- Text starts at x = 5, each character is charWidth pixels + local startCol = math.floor((point1.x - 5) / charWidth) + 1 + local endCol = math.floor((point2.x - 5) / charWidth) + 1 + + -- Normalize so start is before end + if startLine > endLine or (startLine == endLine and startCol > endCol) then + startLine, endLine = endLine, startLine + startCol, endCol = endCol, startCol + end + + -- Validate line numbers + if startLine < 1 then startLine = 1 end + if endLine > #lines then endLine = #lines end + if startLine > #lines then return end + + -- Clamp column positions + if startCol < 1 then startCol = 1 end + if endCol < 1 then endCol = 1 end + if startCol > #lines[startLine] + 1 then startCol = #lines[startLine] + 1 end + if endCol > #lines[endLine] then endCol = #lines[endLine] end + + -- Get the text before and after the selection + local beforeText = lines[startLine]:sub(1, startCol - 1) + local afterText = lines[endLine]:sub(endCol + 1) + + -- Split newContent into lines + local newLines = {} + for line in ((newContent or "") .. "\n"):gmatch("([^\n]*)\n") do + table.insert(newLines, line) + end + -- Remove the trailing empty line added by the pattern + if #newLines > 0 and newLines[#newLines] == "" and not (newContent or ""):match("\n$") then + table.remove(newLines) + end + if #newLines == 0 then + newLines = {""} + end + + -- Remove all lines in the selection range + for i = endLine, startLine + 1, -1 do + table.remove(lines, i) + end + + -- Build the replacement + if #newLines == 1 then + -- Single line replacement + lines[startLine] = beforeText .. newLines[1] .. afterText + cursorLine = startLine + cursorCol = #beforeText + #newLines[1] + 1 + else + -- Multi-line replacement + -- First line: beforeText + first new line + lines[startLine] = beforeText .. newLines[1] + + -- Middle lines: insert as new lines + for i = 2, #newLines - 1 do + table.insert(lines, startLine + i - 1, newLines[i]) + end + + -- Last line: last new line + afterText + local lastNewLine = newLines[#newLines] + table.insert(lines, startLine + #newLines - 1, lastNewLine .. afterText) + + cursorLine = startLine + #newLines - 1 + cursorCol = #lastNewLine + 1 + end + + modified = true + updateTitle() + clampCursor() + ensureCursorVisible() + window:markDirty() +end + -- Click callback window.onClick = function(x, y, button) -- Check toolbar diff --git a/iso_includes/apps/com.luajitos.taskbar/src/init.lua b/iso_includes/apps/com.luajitos.taskbar/src/init.lua @@ -187,7 +187,7 @@ local function showWindowPopup(appInfo, buttonX) gfx:drawRect(0, yPos, popupWidth, appButtonHeight, 0x606060) -- Draw text - gfx:drawText(5, yPos + 13, title, 0xFFFFFF) + gfx:drawUText(5, yPos + 13, title, 0xFFFFFF) end end @@ -393,7 +393,7 @@ local function showStartMenu() -- Title bar gfx:fillRect(0, 0, menuWidth, 30, 0x0066CC) - gfx:drawText(10, 8, "Applications", 0xFFFFFF) + gfx:drawUText(10, 8, "Applications", 0xFFFFFF) -- List available apps by category local yPos = 40 @@ -408,7 +408,7 @@ local function showStartMenu() end if not hasApps then - gfx:drawText(10, yPos, "No applications found", 0x888888) + gfx:drawUText(10, yPos, "No applications found", 0x888888) else -- Sort categories alphabetically local sortedCategories = {} @@ -427,7 +427,7 @@ local function showStartMenu() -- Draw category header gfx:fillRect(5, yPos, menuWidth - 10, categoryHeaderHeight, 0x1A1A1A) - gfx:drawText(10, yPos + 6, category, 0xCCCCCC) + gfx:drawUText(10, yPos + 6, category, 0xCCCCCC) yPos = yPos + categoryHeaderHeight + 2 -- Draw apps in this category @@ -463,7 +463,7 @@ local function showStartMenu() end -- Draw app name - gfx:drawText(text_x_offset, yPos + 8, appInfo.name, 0xFFFFFF) + gfx:drawUText(text_x_offset, yPos + 8, appInfo.name, 0xFFFFFF) yPos = yPos + itemHeight + 2 end @@ -619,7 +619,7 @@ taskbar.onDraw = function(gfx) gfx:drawRect(startButtonX, startButtonY, startButtonWidth, startButtonHeight, 0x0088FF) -- Start button text - gfx:drawText(startButtonX + 20, startButtonY + 13, "Start", 0xFFFFFF) + gfx:drawUText(startButtonX + 20, startButtonY + 13, "Start", 0xFFFFFF) -- Draw application buttons local apps = getRunningApplications() @@ -672,7 +672,7 @@ taskbar.onDraw = function(gfx) -- Draw text (dimmed if minimized) local textColor = appInfo.hasMinimized and 0xAAAAAA or 0xFFFFFF - gfx:drawText(currentX + text_x_offset, appButtonY + 13, appName, textColor) + gfx:drawUText(currentX + text_x_offset, appButtonY + 13, appName, textColor) currentX = currentX + appButtonWidth + 5 end @@ -681,7 +681,7 @@ taskbar.onDraw = function(gfx) local timeText = formatTime() local timeX = screenWidth - 100 -- Position near right edge local timeY = 15 -- Vertically centered - gfx:drawText(timeX, timeY, timeText, 0xFFFFFF) + gfx:drawUText(timeX, timeY, timeText, 0xFFFFFF) -- Draw separator line at top gfx:fillRect(0, 0, screenWidth, 1, 0x444444) diff --git a/iso_includes/os/init.lua b/iso_includes/os/init.lua @@ -53,17 +53,13 @@ initCursorBuffer() -- Text selection helper: find text at position and return index + character position local function findTextAtPosition(win, mx, my) if not win.selectableText then - osprint("[SELECT] findTextAtPosition: no selectableText array\n") return nil end -- Mouse coordinates are already content-relative (window.x/y point to content area) local content_mx = mx local content_my = my - osprint("[SELECT] findTextAtPosition: mx=" .. mx .. " my=" .. my .. " -> content_mx=" .. content_mx .. " content_my=" .. content_my .. " numTexts=" .. #win.selectableText .. " isBorderless=" .. tostring(win.isBorderless) .. "\n") - for i, textInfo in ipairs(win.selectableText) do - osprint("[SELECT] text[" .. i .. "]: x=" .. textInfo.x .. " y=" .. textInfo.y .. " w=" .. textInfo.w .. " h=" .. textInfo.h .. " text='" .. (textInfo.text:sub(1,20)) .. "'\n") if content_mx >= textInfo.x and content_mx <= textInfo.x + textInfo.w and content_my >= textInfo.y and content_my < textInfo.y + textInfo.h then -- Calculate character position within the text @@ -106,6 +102,98 @@ local function getSelectedText(win) return table.concat(result, "\n") end +-- Calculate edited text boxes after selection replacement +-- textBoxes: array of {text, x, y, w, h, scale, ...} +-- point1, point2: selection endpoints {x, y} in content coordinates +-- newContent: string to replace selection with +-- Returns: new array of text boxes with selection replaced +local function calcSelectionEditted(textBoxes, point1, point2, newContent) + if not textBoxes or #textBoxes == 0 then + return textBoxes + end + + -- Find which text boxes contain the selection points + local startIdx, startPos, finishIdx, finishPos + + for i, box in ipairs(textBoxes) do + local charWidth = 8 * (box.scale or 1) + local charHeight = 12 * (box.scale or 1) + + -- Check if point1 is in this box + if not startIdx then + if point1.y >= box.y and point1.y < box.y + charHeight and + point1.x >= box.x and point1.x <= box.x + box.w then + startIdx = i + startPos = math.floor((point1.x - box.x) / charWidth) + 1 + if startPos < 1 then startPos = 1 end + if startPos > #box.text then startPos = #box.text end + end + end + + -- Check if point2 is in this box + if not finishIdx then + if point2.y >= box.y and point2.y < box.y + charHeight and + point2.x >= box.x and point2.x <= box.x + box.w then + finishIdx = i + finishPos = math.floor((point2.x - box.x) / charWidth) + 1 + if finishPos < 1 then finishPos = 1 end + if finishPos > #box.text then finishPos = #box.text end + end + end + end + + if not startIdx or not finishIdx then + return textBoxes + end + + -- Normalize so start is before finish + if startIdx > finishIdx or (startIdx == finishIdx and startPos > finishPos) then + startIdx, finishIdx = finishIdx, startIdx + startPos, finishPos = finishPos, startPos + end + + -- Build new text boxes array + local result = {} + + -- Copy boxes before selection + for i = 1, startIdx - 1 do + result[#result + 1] = textBoxes[i] + end + + -- Handle the selection replacement + local firstBox = textBoxes[startIdx] + local lastBox = textBoxes[finishIdx] + + if firstBox then + -- Text before selection in first box + newContent + text after selection in last box + local beforeText = firstBox.text:sub(1, startPos - 1) + local afterText = lastBox and lastBox.text:sub(finishPos + 1) or "" + + -- Create merged box with replaced content + local newBox = {} + for k, v in pairs(firstBox) do + newBox[k] = v + end + newBox.text = beforeText .. (newContent or "") .. afterText + -- Recalculate width based on new text length + local scale = newBox.scale or 1 + local charWidth = 8 * scale + newBox.w = #newBox.text * charWidth + + result[#result + 1] = newBox + end + + -- Copy boxes after selection + for i = finishIdx + 1, #textBoxes do + result[#result + 1] = textBoxes[i] + end + + return result +end + +-- Make calcSelectionEditted available globally +_G.calcSelectionEditted = calcSelectionEditted + -- SafeGfx for cursor drawing (draws to cursor_buffer) local function createCursorGfx() local gfx = {} @@ -948,7 +1036,6 @@ local function drawAllWindows() -- Check if this text has selection highlighting local selRange = selectedRanges[textIndex] if selRange then - osprint("[SELECT] Highlighting textIndex=" .. textIndex .. " text='" .. text:sub(1,10) .. "' s=" .. selRange.s .. " e=" .. selRange.e .. " scale=" .. scale .. " charWidth=" .. charWidth .. "\n") local r = bit.band(bit.rshift(color, 16), 0xFF) local g = bit.band(bit.rshift(color, 8), 0xFF) local b = bit.band(color, 0xFF) @@ -1536,9 +1623,7 @@ function MainDraw() -- If mouse events weren't handled and window is selectable, start text selection if not mouseDownHandled and clicked_window.selectable ~= false then local textIdx, charPos = findTextAtPosition(clicked_window, click_x, click_y) - osprint("[SELECT] mouseDown at click_x=" .. click_x .. " click_y=" .. click_y .. " selectable=" .. tostring(clicked_window.selectable) .. " selectableText count=" .. #(clicked_window.selectableText or {}) .. "\n") if textIdx then - osprint("[SELECT] START selection: textIdx=" .. textIdx .. " charPos=" .. charPos .. "\n") clicked_window.selection = { start = { index = textIdx, pos = charPos }, finish = { index = textIdx, pos = charPos }, @@ -1548,7 +1633,6 @@ function MainDraw() clicked_window.dirty = true else -- Clear selection when clicking outside text - osprint("[SELECT] No text found at position, clearing selection\n") clicked_window.selection = nil end end @@ -1728,16 +1812,11 @@ function MainDraw() end -- Handle mouse up for captured window - if mouse1_released then - osprint("[SELECT] mouse1_released, capture_window=" .. tostring(_G.mouse_capture_window ~= nil) .. "\n") - end if mouse1_released and _G.mouse_capture_window then local win = _G.mouse_capture_window local up_x = _G.cursor_state.x - win.x local up_y = _G.cursor_state.y - win.y - osprint("[SELECT] mouseUp: up_x=" .. up_x .. " up_y=" .. up_y .. " hasSelection=" .. tostring(win.selection ~= nil) .. "\n") - local mouseUpHandled = false if win.onMouseUp then local success, result = pcall(win.onMouseUp, up_x, up_y) @@ -1748,19 +1827,15 @@ function MainDraw() end end - osprint("[SELECT] mouseUpHandled=" .. tostring(mouseUpHandled) .. " selectable=" .. tostring(win.selectable) .. " selection.start=" .. tostring(win.selection and win.selection.start) .. "\n") - -- Finalize text selection if active if not mouseUpHandled and win.selectable ~= false and win.selection and win.selection.start then local textIdx, charPos = findTextAtPosition(win, up_x, up_y) if textIdx then win.selection.finish = { index = textIdx, pos = charPos } - osprint("[SELECT] END selection: textIdx=" .. textIdx .. " charPos=" .. charPos .. "\n") end -- Get the selected text content win.selection.content = getSelectedText(win) win.selection.type = "text" - osprint("[SELECT] FINALIZED selection content: '" .. (win.selection.content or "") .. "'\n") win.dirty = true end _G.mouse_capture_window = nil diff --git a/iso_includes/os/libs/Sys.lua b/iso_includes/os/libs/Sys.lua @@ -665,10 +665,73 @@ function sys.sendInput(key, scancode) end end + -- Check if there's an active selection and a printable key was pressed + local win = sys.activeWindow + local hasSelection = win.selection and win.selection.start and win.selection.finish and + win.selection.content and win.selection.content ~= "" + + -- Check if this is a printable character or special editing key (backspace, delete, enter) + local isPrintable = key and #key == 1 and key:byte() >= 32 + local isBackspace = key == "\b" + local isEnter = key == "\n" + local isDelete = baseScancode == 83 + + if hasSelection and (isPrintable or isBackspace or isEnter or isDelete) then + -- Selection is active and an editing key was pressed + if win.onSelectionEditted then + local newContent = "" + if isPrintable then + newContent = key + elseif isEnter then + newContent = "\n" + end + -- Backspace and Delete replace selection with empty string + + -- Convert selection indices to x,y coordinates + local startText = win.selectableText and win.selectableText[win.selection.start.index] + local finishText = win.selectableText and win.selectableText[win.selection.finish.index] + + local point1, point2 + if startText then + local charWidth = 8 * (startText.scale or 1) + point1 = { + x = startText.x + (win.selection.start.pos - 1) * charWidth, + y = startText.y + } + end + if finishText then + local charWidth = 8 * (finishText.scale or 1) + point2 = { + x = finishText.x + (win.selection.finish.pos - 1) * charWidth, + y = finishText.y + } + end + + if not point1 or not point2 then + -- Fall back to passing the raw selection if we can't get coordinates + point1 = win.selection.start + point2 = win.selection.finish + end + + local success, err = pcall(function() + win.onSelectionEditted(point1, point2, newContent) + end) + + if not success and osprint then + osprint("ERROR: onSelectionEditted callback failed: " .. tostring(err) .. "\n") + end + + -- Clear selection after edit + win.selection = nil + win.dirty = true + return true + end + end + -- Call the window's input callback if it exists - if sys.activeWindow.onInput then + if win.onInput then local success, err = pcall(function() - sys.activeWindow.onInput(key, scancode) + win.onInput(key, scancode) end) if not success and osprint then diff --git a/iso_includes/os/postinit.lua b/iso_includes/os/postinit.lua @@ -336,8 +336,42 @@ if _G.sys and _G.sys.hotkey then _G.sys.hotkey.add("ctrl+v", function() local win = _G.sys.activeWindow if win and _G.clipboard.content then - -- Try onPaste first - if win.onPaste then + -- Check if there's an active selection - use onSelectionEditted + local hasSelection = win.selection and win.selection.start and win.selection.finish and + win.selection.content and win.selection.content ~= "" + + if hasSelection and win.onSelectionEditted then + -- Convert selection indices to x,y coordinates + local startText = win.selectableText and win.selectableText[win.selection.start.index] + local finishText = win.selectableText and win.selectableText[win.selection.finish.index] + + local point1, point2 + if startText then + local charWidth = 8 * (startText.scale or 1) + point1 = { + x = startText.x + (win.selection.start.pos - 1) * charWidth, + y = startText.y + } + end + if finishText then + local charWidth = 8 * (finishText.scale or 1) + point2 = { + x = finishText.x + (win.selection.finish.pos - 1) * charWidth, + y = finishText.y + } + end + + if point1 and point2 then + local success, err = pcall(win.onSelectionEditted, point1, point2, _G.clipboard.content) + if not success and osprint then + osprint("[CLIPBOARD] onSelectionEditted error: " .. tostring(err) .. "\n") + end + end + -- Clear selection after paste + win.selection = nil + win.dirty = true + elseif win.onPaste then + -- Try onPaste local success, err = pcall(win.onPaste, _G.clipboard.content, _G.clipboard.type) if not success and osprint then osprint("[CLIPBOARD] onPaste error: " .. tostring(err) .. "\n")