commit 24f03437c48d300bed3c8033f0038a8c5309866a
parent a7f852f14c9223f1034d06d0cd1d9f88d51bb887
Author: luajitos <bbhbb2094@gmail.com>
Date: Mon, 1 Dec 2025 23:45:28 +0000
Fixed Lunar Editor and Explorer
Diffstat:
6 files changed, 492 insertions(+), 33 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -42,3 +42,6 @@ taskbar.bmp
# Markdown files
*.MD
+
+# Old Files
+splash.png
diff --git a/CLAUDE.md b/CLAUDE.md
@@ -1 +1,2 @@
-- if you only change lua then just run ./repack_lua.sh and tell me that you have repacked it, if you have changed C code then rebuild it with ./build.sh
-\ No newline at end of file
+- if you only change lua then just run ./repack_lua.sh and tell me that you have repacked it, if you have changed C code then rebuild it with ./build.sh
+- NEVER USE entry="src/... BECAUSE THE PATH IS RELATIVE TO src
+\ No newline at end of file
diff --git a/iso_includes/apps/com.luajitos.explorer/src/explorer.lua b/iso_includes/apps/com.luajitos.explorer/src/explorer.lua
@@ -136,9 +136,10 @@ window.onClick = function(mx, my)
osprint("[EXPLORER] onClick CALLED! Mouse click at (" .. mx .. ", " .. my .. ")\n")
end
- -- Check if click is in the file list area
- if my > headerHeight and my < windowHeight - footerHeight then
- local clickedIndex = math.floor((my - headerHeight) / lineHeight) + 1 + scrollOffset
+ -- Check if click is in the file list area (below header and column headers row)
+ local listStartY = headerHeight + lineHeight -- Account for column headers row
+ if my > listStartY and my < windowHeight - footerHeight then
+ local clickedIndex = math.floor((my - listStartY) / lineHeight) + 1 + scrollOffset
if osprint then
osprint("[EXPLORER] Clicked index: " .. clickedIndex .. " (total entries: " .. #entries .. ")\n")
diff --git a/iso_includes/apps/com.luajitos.lunareditor/src/editor.lua b/iso_includes/apps/com.luajitos.lunareditor/src/editor.lua
@@ -0,0 +1,427 @@
+-- Lunar Editor - Simple text editor for LuajitOS
+-- Displays text files with a toolbar (New, Open, Save)
+
+local windowWidth = 640
+local windowHeight = 480
+local toolbarHeight = 30
+local statusBarHeight = 20
+local contentHeight = windowHeight - toolbarHeight - statusBarHeight
+
+-- Editor state
+local lines = {""} -- Array of lines
+local cursorLine = 1
+local cursorCol = 1
+local scrollY = 0 -- Scroll offset in lines
+local currentFile = nil -- Currently open file path
+local modified = false -- Has the file been modified?
+
+-- Font metrics (scale 1 = 8px base font, but we use larger spacing)
+local fontScale = 1
+local charWidth = 8
+local lineHeight = 12
+local visibleLines = math.floor(contentHeight / lineHeight)
+
+-- Toolbar buttons
+local toolbarButtons = {
+ { x = 5, y = 5, width = 50, height = 20, label = "New", action = "new" },
+ { x = 60, y = 5, width = 50, height = 20, label = "Open", action = "open" },
+ { x = 115, y = 5, width = 50, height = 20, label = "Save", action = "save" },
+ { x = 170, y = 5, width = 55, height = 20, label = "SaveAs", action = "saveas" },
+}
+
+-- Helper: get window title
+local function getTitle()
+ local title = "Lunar Editor"
+ if currentFile then
+ -- Get filename from path
+ local filename = currentFile:match("([^/]+)$") or currentFile
+ title = title .. " - " .. filename
+ else
+ title = title .. " - Untitled"
+ end
+ if modified then
+ title = title .. " *"
+ end
+ return title
+end
+
+-- Create window
+local window = app:newWindow(getTitle(), windowWidth, windowHeight, true)
+
+if not window then
+ print("Lunar Editor: Failed to create window")
+ return
+end
+
+print("Lunar Editor: Starting...")
+
+-- Helper: update title
+local function updateTitle()
+ window.title = getTitle()
+end
+
+-- Helper: ensure cursor is in valid position
+local function clampCursor()
+ if cursorLine < 1 then cursorLine = 1 end
+ if cursorLine > #lines then cursorLine = #lines end
+ if cursorCol < 1 then cursorCol = 1 end
+ local lineLen = #lines[cursorLine]
+ if cursorCol > lineLen + 1 then cursorCol = lineLen + 1 end
+end
+
+-- Helper: ensure cursor is visible (scroll if needed)
+local function ensureCursorVisible()
+ if cursorLine <= scrollY then
+ scrollY = cursorLine - 1
+ elseif cursorLine > scrollY + visibleLines then
+ scrollY = cursorLine - visibleLines
+ end
+end
+
+-- File operations
+local function newFile()
+ lines = {""}
+ cursorLine = 1
+ cursorCol = 1
+ scrollY = 0
+ currentFile = nil
+ modified = false
+ updateTitle()
+ window:markDirty()
+ print("Lunar Editor: New file")
+end
+
+local function loadFile(path)
+ if not fs then
+ print("Lunar Editor: Filesystem not available")
+ return false
+ end
+
+ local content, err = fs:read(path)
+ if not content then
+ print("Lunar Editor: Failed to read file: " .. tostring(err))
+ return false
+ end
+
+ -- Split content into lines
+ lines = {}
+ for line in (content .. "\n"):gmatch("([^\n]*)\n") do
+ table.insert(lines, line)
+ end
+
+ -- Ensure at least one line
+ if #lines == 0 then
+ lines = {""}
+ end
+
+ cursorLine = 1
+ cursorCol = 1
+ scrollY = 0
+ currentFile = path
+ modified = false
+ updateTitle()
+ window:markDirty()
+ print("Lunar Editor: Loaded " .. path .. " (" .. #lines .. " lines)")
+ return true
+end
+
+local function saveFile(path)
+ if not fs then
+ print("Lunar Editor: Filesystem not available")
+ return false
+ end
+
+ -- Join lines with newlines
+ local content = table.concat(lines, "\n")
+
+ local ok, err = fs:write(path, content)
+ if not ok then
+ print("Lunar Editor: Failed to save file: " .. tostring(err))
+ return false
+ end
+
+ currentFile = path
+ modified = false
+ updateTitle()
+ window:markDirty()
+ print("Lunar Editor: Saved " .. path)
+ return true
+end
+
+-- Dialog functions
+local function openFileDialog()
+ if not Dialog or not Dialog.fileOpen then
+ print("Lunar Editor: Dialog.fileOpen not available")
+ return
+ end
+
+ local startPath = "/home"
+ if currentFile then
+ -- Use current file's directory
+ startPath = currentFile:match("(.*/)")
+ if not startPath then startPath = "/home" end
+ end
+
+ local dialog = Dialog.fileOpen(startPath, {
+ app = app,
+ fs = fs,
+ title = "Open File"
+ })
+
+ dialog:openDialog(function(selectedPath)
+ if selectedPath then
+ loadFile(selectedPath)
+ end
+ end)
+end
+
+local function saveFileDialog()
+ if not Dialog or not Dialog.fileSave then
+ print("Lunar Editor: Dialog.fileSave not available")
+ return
+ end
+
+ local startPath = "/home"
+ local defaultName = "untitled.txt"
+
+ if currentFile then
+ startPath = currentFile:match("(.*/)")
+ defaultName = currentFile:match("([^/]+)$") or "untitled.txt"
+ if not startPath then startPath = "/home" end
+ end
+
+ local dialog = Dialog.fileSave(startPath, defaultName, {
+ app = app,
+ fs = fs,
+ title = "Save File"
+ })
+
+ dialog:openDialog(function(selectedPath)
+ if selectedPath then
+ saveFile(selectedPath)
+ end
+ end)
+end
+
+-- Draw callback
+window.onDraw = function(gfx)
+ -- Draw toolbar background
+ gfx:fillRect(0, 0, windowWidth, toolbarHeight, 0x404040)
+
+ -- Draw toolbar buttons
+ for _, btn in ipairs(toolbarButtons) do
+ gfx:fillRect(btn.x, btn.y, btn.width, btn.height, 0x606060)
+ gfx:drawRect(btn.x, btn.y, btn.width, btn.height, 0x808080)
+ local textX = btn.x + (btn.width - #btn.label * 6) / 2
+ local textY = btn.y + 6
+ gfx:drawText(textX, textY, btn.label, 0xFFFFFF)
+ end
+
+ -- Draw separator line
+ gfx:fillRect(0, toolbarHeight - 1, windowWidth, 1, 0x303030)
+
+ -- Draw content area (white background)
+ gfx:fillRect(0, toolbarHeight, windowWidth, contentHeight, 0xFFFFFF)
+
+ -- Draw text content
+ local y = toolbarHeight + 2
+ local startLine = scrollY + 1
+ local endLine = math.min(#lines, scrollY + visibleLines)
+
+ for i = startLine, endLine do
+ local line = lines[i] or ""
+ gfx:drawText(5, y, line, 0x000000, fontScale)
+
+ -- Draw cursor if on this line
+ if i == cursorLine then
+ local cursorX = 5 + (cursorCol - 1) * charWidth
+ local cursorY = y
+ -- Draw cursor as a vertical line
+ gfx:fillRect(cursorX, cursorY, 2, lineHeight, 0x000000)
+ end
+
+ y = y + lineHeight
+ end
+
+ -- Draw status bar
+ local statusY = windowHeight - statusBarHeight
+ gfx:fillRect(0, statusY, windowWidth, statusBarHeight, 0x333333)
+
+ -- Status text: line/col info
+ local statusText = "Ln " .. cursorLine .. ", Col " .. cursorCol
+ if currentFile then
+ statusText = currentFile .. " | " .. statusText
+ else
+ statusText = "Untitled | " .. statusText
+ end
+ gfx:drawText(5, statusY + 4, statusText, 0xAAAAAA)
+
+ -- Right side: modified indicator
+ if modified then
+ gfx:drawText(windowWidth - 70, statusY + 4, "Modified", 0xFFAAAA)
+ end
+end
+
+-- Input callback
+window.onInput = function(key, scancode)
+ local baseScancode = scancode % 128
+
+ -- Arrow keys
+ if baseScancode == 72 then -- Up
+ if cursorLine > 1 then
+ cursorLine = cursorLine - 1
+ clampCursor()
+ ensureCursorVisible()
+ window:markDirty()
+ end
+ return
+ elseif baseScancode == 80 then -- Down
+ if cursorLine < #lines then
+ cursorLine = cursorLine + 1
+ clampCursor()
+ ensureCursorVisible()
+ window:markDirty()
+ end
+ return
+ elseif baseScancode == 75 then -- Left
+ if cursorCol > 1 then
+ cursorCol = cursorCol - 1
+ elseif cursorLine > 1 then
+ cursorLine = cursorLine - 1
+ cursorCol = #lines[cursorLine] + 1
+ end
+ clampCursor()
+ ensureCursorVisible()
+ window:markDirty()
+ return
+ elseif baseScancode == 77 then -- Right
+ if cursorCol <= #lines[cursorLine] then
+ cursorCol = cursorCol + 1
+ elseif cursorLine < #lines then
+ cursorLine = cursorLine + 1
+ cursorCol = 1
+ end
+ clampCursor()
+ ensureCursorVisible()
+ window:markDirty()
+ return
+ elseif baseScancode == 71 then -- Home
+ cursorCol = 1
+ window:markDirty()
+ return
+ elseif baseScancode == 79 then -- End
+ cursorCol = #lines[cursorLine] + 1
+ window:markDirty()
+ return
+ elseif baseScancode == 73 then -- Page Up
+ cursorLine = math.max(1, cursorLine - visibleLines)
+ clampCursor()
+ ensureCursorVisible()
+ window:markDirty()
+ return
+ elseif baseScancode == 81 then -- Page Down
+ cursorLine = math.min(#lines, cursorLine + visibleLines)
+ clampCursor()
+ ensureCursorVisible()
+ window:markDirty()
+ return
+ end
+
+ -- Text input
+ if key == "\b" then -- Backspace
+ if cursorCol > 1 then
+ local line = lines[cursorLine]
+ lines[cursorLine] = line:sub(1, cursorCol - 2) .. line:sub(cursorCol)
+ cursorCol = cursorCol - 1
+ modified = true
+ updateTitle()
+ elseif cursorLine > 1 then
+ -- Join with previous line
+ local prevLine = lines[cursorLine - 1]
+ cursorCol = #prevLine + 1
+ lines[cursorLine - 1] = prevLine .. lines[cursorLine]
+ table.remove(lines, cursorLine)
+ cursorLine = cursorLine - 1
+ modified = true
+ updateTitle()
+ end
+ clampCursor()
+ ensureCursorVisible()
+ window:markDirty()
+ elseif key == "\n" then -- Enter
+ local line = lines[cursorLine]
+ local beforeCursor = line:sub(1, cursorCol - 1)
+ local afterCursor = line:sub(cursorCol)
+ lines[cursorLine] = beforeCursor
+ table.insert(lines, cursorLine + 1, afterCursor)
+ cursorLine = cursorLine + 1
+ cursorCol = 1
+ modified = true
+ updateTitle()
+ clampCursor()
+ ensureCursorVisible()
+ window:markDirty()
+ elseif key and #key == 1 and key:byte() >= 32 then -- Printable character
+ local line = lines[cursorLine]
+ lines[cursorLine] = line:sub(1, cursorCol - 1) .. key .. line:sub(cursorCol)
+ cursorCol = cursorCol + 1
+ modified = true
+ updateTitle()
+ window:markDirty()
+ end
+end
+
+-- Click callback
+window.onClick = function(x, y, button)
+ -- Check toolbar
+ if y < toolbarHeight then
+ for _, btn in ipairs(toolbarButtons) do
+ if x >= btn.x and x < btn.x + btn.width and
+ y >= btn.y and y < btn.y + btn.height then
+ print("Lunar Editor: Button clicked: " .. btn.action)
+ if btn.action == "new" then
+ newFile()
+ elseif btn.action == "open" then
+ openFileDialog()
+ elseif btn.action == "save" then
+ if currentFile then
+ saveFile(currentFile)
+ else
+ saveFileDialog()
+ end
+ elseif btn.action == "saveas" then
+ saveFileDialog()
+ end
+ return
+ end
+ end
+ return
+ end
+
+ -- Click in content area - move cursor
+ if y >= toolbarHeight and y < windowHeight - statusBarHeight then
+ local contentY = y - toolbarHeight
+ local clickedLine = scrollY + math.floor(contentY / lineHeight) + 1
+ if clickedLine >= 1 and clickedLine <= #lines then
+ cursorLine = clickedLine
+ local clickedCol = math.floor((x - 5) / charWidth) + 1
+ cursorCol = math.max(1, math.min(clickedCol, #lines[cursorLine] + 1))
+ window:markDirty()
+ end
+ end
+end
+
+-- Check for command line arguments to open a file
+if args then
+ local argPath = args.o or args.open or args[1]
+ if argPath and argPath ~= "" then
+ -- Expand ~ to /home
+ if argPath:sub(1, 1) == "~" then
+ argPath = "/home" .. argPath:sub(2)
+ end
+ print("Lunar Editor: Opening file from arguments: " .. argPath)
+ loadFile(argPath)
+ end
+end
+
+print("Lunar Editor: Ready")
diff --git a/iso_includes/os/libs/Dialog.lua b/iso_includes/os/libs/Dialog.lua
@@ -346,6 +346,9 @@ function Dialog.fileOpen(startPath, options)
-- Click handler
self.window.onClick = function(mx, my)
+ if osprint then
+ osprint("[Dialog.fileOpen.onClick] mx=" .. mx .. " my=" .. my .. " window.y=" .. tostring(self.window.y) .. "\n")
+ end
local listY = 35
local listHeight = height - listY - 40
local itemHeight = 25
diff --git a/iso_includes/os/libs/SafeFS.lua b/iso_includes/os/libs/SafeFS.lua
@@ -133,6 +133,33 @@ local function matchesPattern(path, pattern)
return false
end
+-- Helper: Check if a path would clash with a pseudo file
+local function checkPseudoFileClash(parentDir, filename)
+ if not parentDir or not parentDir.files then
+ return false, nil
+ end
+
+ -- Check if there's a pseudo file with this exact name
+ for _, file in ipairs(parentDir.files) do
+ if file.isPseudo and file.name == filename then
+ return true, file
+ end
+ end
+
+ -- Check if filename looks like it has arguments (matches "pseudofile .*")
+ local spacePos = filename:find(" ")
+ if spacePos then
+ local baseName = filename:sub(1, spacePos - 1)
+ for _, file in ipairs(parentDir.files) do
+ if file.isPseudo and file.name == baseName then
+ return true, file
+ end
+ end
+ end
+
+ return false, nil
+end
+
-- Helper: Check if creating at this path would place something directly in root
-- Returns true if the path's parent is "/" (the root directory)
local function isDirectlyInRoot(path)
@@ -922,6 +949,14 @@ function SafeFS:write(path, content)
-- Update existing file
file.content = content
+ -- Also update in C ramdisk
+ if _CRamdiskOpen and _CRamdiskWrite and _CRamdiskClose then
+ local handle = _CRamdiskOpen(resolvedPath, "w")
+ if handle then
+ _CRamdiskWrite(handle, content)
+ _CRamdiskClose(handle)
+ end
+ end
return true
end
end
@@ -933,7 +968,7 @@ function SafeFS:write(path, content)
return false, "Cannot create file: would clash with pseudo file '" .. pseudoFile.name .. "'"
end
- -- Create new file
+ -- Create new file in SafeFS tree
if not parentDir.files then
parentDir.files = {}
end
@@ -946,6 +981,22 @@ function SafeFS:write(path, content)
}
table.insert(parentDir.files, newFile)
+ -- Also write to C ramdisk so other apps can see it
+ if _CRamdiskOpen and _CRamdiskWrite and _CRamdiskClose then
+ local handle, err = _CRamdiskOpen(resolvedPath, "w")
+ if handle then
+ _CRamdiskWrite(handle, content)
+ _CRamdiskClose(handle)
+ if osprint then
+ osprint("[SafeFS] Wrote to C ramdisk: " .. resolvedPath .. "\n")
+ end
+ else
+ if osprint then
+ osprint("[SafeFS] Failed to open for write: " .. resolvedPath .. " err=" .. tostring(err) .. "\n")
+ end
+ end
+ end
+
return true
end
@@ -1717,33 +1768,6 @@ function SafeFS:move(source, destination)
return true
end
--- Helper: Check if a path would clash with a pseudo file
-local function checkPseudoFileClash(parentDir, filename)
- if not parentDir or not parentDir.files then
- return false, nil
- end
-
- -- Check if there's a pseudo file with this exact name
- for _, file in ipairs(parentDir.files) do
- if file.isPseudo and file.name == filename then
- return true, file
- end
- end
-
- -- Check if filename looks like it has arguments (matches "pseudofile .*")
- local spacePos = filename:find(" ")
- if spacePos then
- local baseName = filename:sub(1, spacePos - 1)
- for _, file in ipairs(parentDir.files) do
- if file.isPseudo and file.name == baseName then
- return true, file
- end
- end
- end
-
- return false, nil
-end
-
-- Create a pseudo file that uses callbacks instead of content
-- The file node will call onRead(args) to get content and onWrite(data) to receive writes
function SafeFS:createPseudoFile(path)