commit - 96122ce9a1fce816da654d60218cce92efc00123
commit + 5840ee4c5f0f9f206cd72b5e535832c630919f5f
blob - ab41080edff33d734e5cc7b84b712ff5515c5da8
blob + 93a0d15e976cc8f5d930bd06d5dc8d9620666a9f
--- aestel/menu.lua
+++ aestel/menu.lua
--- aestel/ui.lua
--- Generic key-driven menu rendering and navigation
+-- aestel/menu.lua
+-- A module for rendering and navigating menus
local M = {}
local display = require("aestel.display")
local input = require("aestel.input")
--- Utility to read full key (handles arrow escape sequences)
local function read_key_full()
local key = input.read_key()
if key == "\27" then -- ESC prefix for arrows
local second = input.read_key()
if second == "[" then
local third = input.read_key()
- if third == "A" then return "up" -- Arrow up
- elseif third == "B" then return "down" -- Arrow down
- elseif third == "C" then return "right" -- NEW: Arrow right (enter/down dir)
- elseif third == "D" then return "left" -- NEW: Arrow left (up dir)
+ if third == "A" then return "up"
+ elseif third == "B" then return "down"
+ elseif third == "C" then return "right"
+ elseif third == "D" then return "left"
end
end
end
- return key -- Single char (j, k, q, Enter, etc.)
+ return key
end
-- Display a key-navigated menu and return the selected index
--- (1-based, 0 for "up", or nil (quit))
function M.menu(title, options, start_row, header_lines, empty_message, cwd)
if not options or #options == 0 then
if empty_message then
start_row = start_row or 1
- -- Note: cwd is ignored for empty directories, empty_message replaces it
local terminal_height = input.get_terminal_height()
input.enable_raw()
-
- -- Write the empty message on the last terminal line
local terminal_width = input.get_terminal_width()
local padding = string.rep(" ", math.max(0, terminal_width - #empty_message))
io.stderr:write(string.format("\27[%d;1H\27[K", terminal_height))
io.stderr:write(colors.REVERSE .. empty_message .. padding .. colors.RESET)
io.stderr:flush()
-
local key = read_key_full()
input.disable_raw()
if key == "h" or key == "left" then
- return 0 -- Signal "go up"
+ return 0
end
- return nil -- Quit
+ return nil
end
return nil
end
start_row = start_row or 1
- header_lines = header_lines or 0 -- Extra lines above menu (e.g., for path display)
+ header_lines = header_lines or 0
local selected_index = 1
local scroll_offset = 1
local running = true
local result = nil
-
local terminal_height = input.get_terminal_height()
local max_visible_items = terminal_height - start_row + 1
if cwd then
- max_visible_items = max_visible_items - 2 -- Reserve one blank line + one status line
+ max_visible_items = max_visible_items - 2
end
input.enable_raw()
while running do
- -- Clear menu area ONLY from start_row down (preserve headers)
io.stderr:write(string.format("\27[%d;1H", start_row))
- io.stderr:write("\27[J") -- Erase down from start_row
+ io.stderr:write("\27[J")
io.stderr:flush()
-- Calculate visible range (ensure selected is always visible)
local option = options[i]
local is_selected = (i == selected_index)
local display_name = option.name or tostring(option)
-
- local prefix = (is_selected and "\27[1m > " or " ") -- Bold > for selected
-
+ -- Display position
+ local prefix = (is_selected and "\27[1m > " or " ")
-- Determine color by type
local option_type = (type(option) == "table") and option.type or nil
local color = display.color_for_type(option_type)
-
-- Position and write to specific row
local row = start_row + visible_count
- io.stderr:write(string.format("\27[%d;1H\27[K", row)) -- Position and clear line
+ io.stderr:write(string.format("\27[%d;1H\27[K", row))
io.stderr:write(prefix .. color .. display_name .. colors.RESET .. "\r\n")
visible_count = visible_count + 1
end
-
if cwd then
display.status_bar(cwd, selected_index, #options)
end
if selected_index < #options then
selected_index = selected_index + 1
end
- elseif key == "h" or key == "left" then -- CHANGED: Added "left" for up dir
- result = 0 -- Signal "go up"
+ elseif key == "h" or key == "left" then
+ result = 0
running = false
- elseif key == "." then -- NEW: Toggle hidden
+ elseif key == "." then -- Hidden files toggle
result = -1
running = false
- elseif key == "l" or key == "right" or key == "\r" or key == "\n" then -- CHANGED: Added "right" for down dir/select
- result = selected_index -- Select current (dir or file)
+ elseif key == "l" or key == "right" or key == "\r" or key == "\n" then
+ result = selected_index
running = false
elseif key == "q" then
- result = nil -- Quit
+ result = nil
running = false
end
end
input.disable_raw()
+
return result
end