pensieve.love/commands.lua

422 lines
13 KiB
Lua
Raw Normal View History

Recently_modified_lookback_window = 10
-- Candidate commands to show in in the command palette in different contexts.
-- Ideally we'd have rules for:
-- contexts to show a command at
-- order in which to show commands for each context
-- But I don't want to design that, so I'm just going to embrace a
-- combinatorial explosion of duplication for a while.
2022-07-28 04:29:41 +00:00
Commands = {
normal={
'capture',
'edit note at cursor (ctrl+e)',
'maximize note',
'close column surrounding cursor',
'down one pane (ctrl+down)',
'up one pane (ctrl+up)',
'top pane of column (ctrl+home)',
'bottom pane of column (ctrl+end)',
'wider columns (X)',
'narrower columns (x)',
'recently modified',
2022-07-31 05:10:32 +00:00
'open file ___',
'reload note at cursor from disk',
'search (all notes)',
'/ (find on screen)',
},
editable={
'exit editing (ctrl+e)',
'capture',
'maximize note',
'close column surrounding cursor',
'down one pane (ctrl+down)',
'up one pane (ctrl+up)',
'top pane of column (ctrl+home)',
'bottom pane of column (ctrl+end)',
'wider columns (X)',
'narrower columns (x)',
'recently modified',
'reload note at cursor from disk',
'search (all notes)',
'/ (find on screen)',
},
maximized={
'back to surface',
'edit note at cursor (ctrl+e)',
'wider columns (X)',
'narrower columns (x)',
'reload note at cursor from disk',
'/ (find on screen)',
},
maximized_editable={
'exit editing (ctrl+e)',
'back to surface',
'wider columns (X)',
'narrower columns (x)',
'reload note at cursor from disk',
'search (all notes)',
'/ (find on screen)',
},
2022-07-28 04:29:41 +00:00
}
2022-07-24 04:00:49 +00:00
function handle_command_palette_keychord(chord, key)
if chord == 'escape' then
Display_settings.show_palette = false
-- don't forget text in case we want to continue it
2022-07-24 04:00:49 +00:00
elseif chord == 'backspace' then
local len = utf8.len(Display_settings.palette)
local byte_offset = Text.offset(Display_settings.palette, len)
Display_settings.palette = string.sub(Display_settings.palette, 1, byte_offset-1)
Display_settings.palette_text = App.newText(love.graphics.getFont(), Display_settings.palette)
2022-07-24 05:21:00 +00:00
elseif chord == 'tab' then
-- select top candidate, but don't submit
local candidates = candidates()
2022-07-30 22:40:22 +00:00
Display_settings.palette = command_string(candidates[1])
2022-07-24 05:21:00 +00:00
Display_settings.palette_text = App.newText(love.graphics.getFont(), Display_settings.palette)
2022-07-24 04:00:49 +00:00
elseif chord == 'return' then
2022-07-24 05:21:00 +00:00
-- select top candidate and submit
2022-07-24 04:00:49 +00:00
-- TODO: handle search command
local candidates = candidates()
if #candidates > 0 then
2022-07-29 18:32:53 +00:00
if file_exists(Directory..candidates[1]) then
command.open_file(candidates[1])
2022-07-24 04:26:35 +00:00
else
2022-07-30 22:40:22 +00:00
run_command(command_string(candidates[1]))
2022-07-24 04:26:35 +00:00
end
else
-- try to run the command as if it contains args
run_command_with_args(Display_settings.palette)
2022-07-24 04:00:49 +00:00
end
-- forget text for next command
Display_settings.palette = ''
Display_settings.palette_text = App.newText(love.graphics.getFont(), '')
--
Display_settings.show_palette = false
2022-07-24 04:00:49 +00:00
end
end
2022-07-30 22:40:22 +00:00
function command_string(s)
return s:gsub(' %(.*', ''):gsub(' _.*', '')
end
2022-07-24 04:00:49 +00:00
function draw_command_palette()
local pwidth,pheight = 400, 300
local left = math.max(App.screen.width/2-pwidth/2, 0)
local top = math.max(App.screen.height/2-pheight/2, 0)
local width = math.min(pwidth, App.screen.width)
local height = math.min(pheight, App.screen.height)
-- background
App.color(Command_palette_background_color)
love.graphics.rectangle('fill', left, top, width, height, 10, 10) -- rounded corners
App.color(Command_palette_border_color)
love.graphics.rectangle('line', left, top, width, height, 10, 10) -- rounded corners
love.graphics.line(left, top+5+Line_height+5, left+width, top+5+Line_height+5)
-- input box
App.color(Command_palette_command_color)
draw_palette_input(left+10, top+5)
-- alternatives
App.color(Command_palette_alternatives_color)
local y = top+5+Line_height+5+5
for _,cmd in ipairs(candidates()) do
love.graphics.print(cmd, left+5,y)
y = y+Line_height
if y >= top+height then
break
end
end
end
function draw_palette_input(x, y)
love.graphics.draw(Display_settings.palette_text, x,y)
x = x+App.width(Display_settings.palette_text)
draw_cursor(x, y)
end
function draw_cursor(x, y)
-- blink every 0.5s
if math.floor(Cursor_time*2)%2 == 0 then
App.color(Cursor_color)
love.graphics.rectangle('fill', x,y, 3,Line_height)
end
end
function candidates()
2022-07-29 05:08:59 +00:00
-- slight context-sensitive tweaks
local candidates = initial_candidates()
if Display_settings.palette == '' then
return candidates
elseif Display_settings.palette:sub(1,1) == '/' then
return {}
else
local results = filter_candidates(candidates, Display_settings.palette)
append(results, file_candidates(Display_settings.palette))
return results
end
end
function initial_candidates()
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if Display_settings.mode == 'normal' then
if not pane.editable then
return Commands.normal
else
return Commands.editable
end
elseif Display_settings.mode == 'maximize' then
if not pane.editable then
return Commands.maximized
else
return Commands.maximized_editable
end
end
2022-07-24 04:00:49 +00:00
end
function filter_candidates(candidates, prefix)
local result = {}
for _,cand in ipairs(candidates) do
if cand:find(prefix, 1, --[[literal pattern]] true) == 1 then
2022-07-24 04:00:49 +00:00
table.insert(result, cand)
end
end
return result
end
function file_candidates(prefix)
if file_exists(Directory..prefix) then
2022-07-27 01:05:48 +00:00
return {prefix}
end
2022-07-24 06:27:44 +00:00
local path = Directory
2022-07-24 05:17:41 +00:00
local visible_dir = ''
if prefix:find('/') then
visible_dir = dirname(prefix)
path = path..dirname(prefix)
2022-07-24 04:00:49 +00:00
end
local files = love.filesystem.getDirectoryItems(path)
local base = basename(prefix)
return concat_all(visible_dir, filter_candidates(reorder(path, files), base))
end
function reorder(dir, files)
local result = {}
local info = {}
for _,file in ipairs(files) do
info[file] = love.filesystem.getInfo(dir..'/'..file)
end
-- files before directories
for _,file in ipairs(files) do
if info[file].type ~= 'directory' then
table.insert(result, file)
end
end
for _,file in ipairs(files) do
if info[file].type == 'directory' then
table.insert(result, file..'/')
end
end
return result
end
2022-07-24 04:26:35 +00:00
function run_command(cmd, args)
if cmd == 'capture' then
command.capture()
elseif cmd == 'maximize note' then
command.maximize_note()
elseif cmd == 'back to surface' then
command.back_to_surface()
elseif cmd == 'edit note at cursor' then
command.edit_note_at_cursor()
elseif cmd == 'exit editing' then
command.exit_editing()
elseif cmd == 'close column surrounding cursor' then
command.close_column_surrounding_cursor()
elseif cmd == 'down one pane' then
command.down_one_pane()
elseif cmd == 'up one pane' then
command.up_one_pane()
elseif cmd == 'bottom pane of column' then
command.bottom_pane_of_column()
elseif cmd == 'wider columns' then
command.wider_columns()
elseif cmd == 'narrower columns' then
command.narrower_columns()
elseif cmd == 'recently modified' then
command.recently_modified()
elseif cmd == 'open file' then
command.open_file(args)
else
print(('not implemented yet: %s'):format(function_name))
end
end
function run_command_with_args(cmd_with_args)
for _,cand in ipairs(initial_candidates()) do
2022-07-30 22:40:22 +00:00
cand = command_string(cand)
local found_offset = cmd_with_args:find(cand, 1, --[[literal pattern]] true)
if found_offset == 1 then
local pivot = #cand+1
if cmd_with_args:sub(pivot, pivot) == ' ' then
run_command(cand, trim(cmd_with_args:sub(pivot)))
end
return
end
end
end
2022-07-24 04:26:35 +00:00
command = {}
function command.capture()
2022-07-24 04:26:35 +00:00
local filename = os.date('%Y/%m/%d/%H-%M-%S')
print('creating directory '..Directory..dirname(filename))
2022-07-24 06:27:44 +00:00
local status = love.filesystem.createDirectory(Directory..dirname(filename))
2022-07-24 04:26:35 +00:00
assert(status)
Cache[filename] = {lines={{mode='text', data=''}}, left=0, right=Display_settings.column_width}
2022-07-26 06:40:09 +00:00
initialize_file_metadata(Cache[filename])
2022-07-24 04:26:35 +00:00
local column = {name=filename}
2022-07-26 06:42:03 +00:00
local pane = load_pane_from_file(filename)
2022-07-24 04:26:35 +00:00
table.insert(column, pane)
table.insert(Surface, Cursor_pane.col+1, column)
Cursor_pane.col = Cursor_pane.col+1
Cursor_pane.row = 1
local col_sx = left_edge_sx(Cursor_pane.col)
if col_sx > Display_settings.x + App.screen.width - Display_settings.column_width then
Display_settings.x = math.max(0, col_sx + Display_settings.column_width + Margin_right + Padding_horizontal - App.screen.width)
Display_settings.y = 0
end
2022-07-24 04:26:35 +00:00
stop_editing_all()
2022-07-29 19:27:45 +00:00
pane.editable = true
Display_settings.mode = 'maximize'
2022-07-24 04:26:35 +00:00
end
2022-07-24 04:37:24 +00:00
2022-07-29 05:08:59 +00:00
function command.maximize_note()
Display_settings.mode = 'maximize'
end
2022-07-29 05:08:59 +00:00
function command.back_to_surface()
Display_settings.mode = 'normal'
2022-07-30 03:54:37 +00:00
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
update_metadata(pane)
update_pane_bounds()
2022-07-29 05:08:59 +00:00
end
2022-07-24 04:37:24 +00:00
function command.close_column_surrounding_cursor()
stop_editing_all()
table.remove(Surface, Cursor_pane.col)
if Cursor_pane.col > 1 then
Cursor_pane.col = Cursor_pane.col - 1
Cursor_pane.row = 1
local col_sx = left_edge_sx(Cursor_pane.col)
if col_sx + Display_settings.column_width < Display_settings.x then
Display_settings.x = col_sx - Margin_left - Padding_horizontal
end
2022-07-24 04:37:24 +00:00
end
end
function command.edit_note_at_cursor()
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
2022-07-29 19:27:45 +00:00
assert(not pane.editable)
stop_editing_all()
pane.recent_updated = false
2022-07-29 19:27:45 +00:00
pane.editable = true
if pane.lines == Cache[pane.id].lines then
-- break an alias
pane.lines = deepcopy(pane.lines)
end
end
function command.exit_editing()
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
2022-07-29 19:27:45 +00:00
assert(pane.editable)
stop_editing(pane)
end
function command.down_one_pane()
if Cursor_pane.row < #Surface[Cursor_pane.col] then
Cursor_pane.row = Cursor_pane.row + 1
end
Display_settings.y = up_edge_sy(Cursor_pane.col, Cursor_pane.row) - Padding_vertical
update_pane_bounds()
end
function command.up_one_pane()
if Cursor_pane.row > 1 then
Cursor_pane.row = Cursor_pane.row - 1
end
Display_settings.y = up_edge_sy(Cursor_pane.col, Cursor_pane.row) - Padding_vertical
update_pane_bounds()
end
function command.bottom_pane_of_column()
if Cursor_pane.row < #Surface[Cursor_pane.col] then
Cursor_pane.row = #Surface[Cursor_pane.col]
end
Display_settings.y = up_edge_sy(Cursor_pane.col, Cursor_pane.row) - Padding_vertical
update_pane_bounds()
end
function command.top_pane_of_column()
if Cursor_pane.row > 1 then
Cursor_pane.row = 1
end
Display_settings.y = up_edge_sy(Cursor_pane.col, Cursor_pane.row) - Padding_vertical
update_pane_bounds()
end
function command.wider_columns()
Display_settings.column_width = Display_settings.column_width + 5*App.width(Em)
redraw_all()
update_pane_bounds()
end
function command.narrower_columns()
Display_settings.column_width = Display_settings.column_width - 5*App.width(Em)
redraw_all()
update_pane_bounds()
end
function command.recently_modified()
2022-07-26 17:02:27 +00:00
local filenames = {}
2022-07-24 06:27:44 +00:00
for line in love.filesystem.lines(Directory..'recent') do
2022-07-26 17:02:27 +00:00
table.insert(filenames, line)
end
local done, ndone = {}, 0
local column = {name='recently modified'}
2022-07-26 17:02:27 +00:00
for i=#filenames,1,-1 do
local filename = filenames[i]
if ndone >= Recently_modified_lookback_window then break end
if not done[filename] then
done[filename] = true
ndone = ndone+1
--? print('loading', filename)
local pane = load_pane_from_file(filename)
table.insert(column, pane)
end
end
table.insert(Surface, Cursor_pane.col+1, column)
Cursor_pane.col = Cursor_pane.col+1
Cursor_pane.row = 1
local col_sx = left_edge_sx(Cursor_pane.col)
if col_sx > Display_settings.x + App.screen.width - Display_settings.column_width then
Display_settings.x = math.max(0, col_sx + Display_settings.column_width + Margin_right + Padding_horizontal - App.screen.width)
Display_settings.y = 0
end
end
function command.open_file(filename)
local column = {name=filename}
local pane = load_pane_from_file(filename)
table.insert(column, pane)
table.insert(Surface, Cursor_pane.col+1, column)
Cursor_pane.col = Cursor_pane.col+1
Cursor_pane.row = 1
local col_sx = left_edge_sx(Cursor_pane.col)
if col_sx > Display_settings.x + App.screen.width - Display_settings.column_width then
Display_settings.x = math.max(0, col_sx + Display_settings.column_width + Margin_right + Padding_horizontal - App.screen.width)
Display_settings.y = 0
end
update_pane_bounds()
end
function trim(s)
return s:gsub('^%s+', ''):gsub('%s+$', '')
end