pensieve.love/commands.lua

1044 lines
31 KiB
Lua
Raw Normal View History

Recently_modified_lookback_window = 30
-- keep sync'd with Edge_list
Opposite = {
next='previous',
previous='next',
child='parent',
parent='child',
}
-- keep sync'd with Opposite
Edge_list = {'previous', 'next', 'child', 'parent'}
-- 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',
'add (___) (create immediately link)',
'step (___) (open link in new column)',
'extract (open note in new column)',
'unroll (___) (repeatedly step from cursor)',
'append (___) (add link after repeatedly stepping from cursor)',
'neighbors (open all links in new column)',
'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 ___',
2022-08-04 11:33:22 +00:00
'reload note at cursor from disk',
'reload all from disk',
'delete note at cursor from disk (if possible)',
'copy selection to clipboard (ctrl+c)',
'search (all notes)',
'/ (find on screen)',
},
editable={
'exit editing (ctrl+e)',
'find in note (ctrl+f)',
'copy selection to clipboard (ctrl+c)',
'cut selection to clipboard (ctrl+x)',
'paste from clipboard (ctrl+v)',
'undo (ctrl+z)',
'redo (ctrl+y)',
'cursor to next word (alt+right arrow)',
'cursor to previous word (alt+left arrow)',
'capture',
'maximize note',
'close column surrounding cursor',
'add (___) (create immediately link)',
'step (___) (open link in new column)',
'unroll (___) (repeatedly step from cursor)',
'append (___) (add link after repeatedly stepping from cursor)',
'neighbors (open all links in new column)',
'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-08-04 11:33:22 +00:00
'reload note at cursor from disk',
'reload all from disk',
'search (all notes)',
'/ (find on screen)',
},
maximized={
'back to surface',
2022-08-04 11:33:22 +00:00
'edit note (ctrl+e)',
'reload note from disk',
'copy selection to clipboard (ctrl+c)',
'/ (find on screen)',
},
maximized_editable={
'exit editing (ctrl+e)',
'back to surface',
'find in note (ctrl+f)',
'copy selection to clipboard (ctrl+c)',
'cut selection to clipboard (ctrl+x)',
'paste from clipboard (ctrl+v)',
'undo (ctrl+z)',
'redo (ctrl+y)',
'cursor to next word (alt+right arrow)',
'cursor to previous word (alt+left arrow)',
2022-08-04 11:33:22 +00:00
'reload note from disk',
'search (all notes)',
'/ (find on screen)',
},
2022-07-28 04:29:41 +00:00
}
-- We incrementally create the menu based on context. Menu_cursor tracks how
-- far along the screen width we've gotten.
Menu_cursor = 0
Palette_cursor = {y=0, x=0}
2022-08-11 05:21:37 +00:00
Palette_alternatives_height = 5 -- number of rows of options to show
function draw_menu_bar()
if App.run_tests then return end -- disable in tests
App.color(Menu_background_color)
love.graphics.rectangle('fill', 0,0, App.screen.width, Menu_status_bar_height)
App.color(Menu_border_color)
love.graphics.rectangle('line', 0,0, App.screen.width, Menu_status_bar_height)
if Display_settings.show_palette then
return
end
App.color(Menu_command_color)
Menu_cursor = 5
add_hotkey_to_menu('ctrl+enter: search commands...')
App.color(Menu_border_color)
love.graphics.line(Menu_cursor-10,2, Menu_cursor-10,Menu_status_bar_height-2)
if Cursor_pane.col >= 1 then
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane then
if Display_settings.mode == 'normal'
then
if not pane.editable then
local left_sx = left_edge_sx(Cursor_pane.col)
local up_sy = up_edge_sy(Cursor_pane.col, Cursor_pane.row)
if should_show_column(left_sx) and should_show_pane(pane, up_sy) then
add_hotkey_to_menu('ctrl+e: edit')
add_panning_hotkeys_to_menu()
end
add_hotkey_to_menu('x/X: narrower/wider columns')
else
if pane.cursor_x >= 0 and pane.cursor_x < App.screen.width and pane.cursor_y >= Header_height and pane.cursor_y < App.screen.height then
add_hotkey_to_menu('ctrl+e: stop editing')
add_hotkey_to_menu('ctrl+f: find')
add_hotkey_to_menu('alt+left alt+right: prev/next word')
add_hotkey_to_menu('ctrl+z ctrl+y: undo/redo')
add_hotkey_to_menu('ctrl+x ctrl+c ctrl+v: cut/copy/paste')
else
add_panning_hotkeys_to_menu()
end
end
end
end
end
add_hotkey_to_menu('ctrl+= ctrl+- ctrl+0: zoom')
end
function add_panning_hotkeys_to_menu()
add_hotkey_to_menu('arrows shift+arrows ctrl+up/down: pan')
end
function add_hotkey_to_menu(s)
2022-08-05 13:40:40 +00:00
if Text_cache[s] == nil then
Text_cache[s] = App.newText(love.graphics.getFont(), s)
end
2022-08-05 13:40:40 +00:00
local width = App.width(Text_cache[s])
if Menu_cursor + width > App.screen.width - 5 then
return
end
App.color(Menu_command_color)
2022-08-05 13:40:40 +00:00
App.screen.draw(Text_cache[s], Menu_cursor,5)
Menu_cursor = Menu_cursor + width + 30
end
2022-08-04 04:03:12 +00:00
function keychord_pressed_on_command_palette(chord, key)
2022-07-24 04:00:49 +00:00
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-08-11 05:00:16 +00:00
Display_settings.palette = command_string(candidates[Display_settings.palette_index])
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-08-11 05:00:16 +00:00
if file_exists(Directory..candidates[Display_settings.palette_index]) then
command.open_file_in_next_column(candidates[Display_settings.palette_index])
2022-07-24 04:26:35 +00:00
else
2022-08-11 05:00:16 +00:00
run_command(command_string(candidates[Display_settings.palette_index]))
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
Display_settings.palette_index = 1
elseif chord == 'up' then
if Display_settings.palette_index > 1 then
Display_settings.palette_index = Display_settings.palette_index-1
end
elseif chord == 'down' then
if Display_settings.palette_index < #candidates() then
Display_settings.palette_index = Display_settings.palette_index+1
end
elseif chord == 'left' then
if Display_settings.palette_index > Palette_alternatives_height then
Display_settings.palette_index = Display_settings.palette_index-Palette_alternatives_height
end
elseif chord == 'right' then
if Display_settings.palette_index <= #candidates()-Palette_alternatives_height then
Display_settings.palette_index = Display_settings.palette_index+Palette_alternatives_height
end
2022-07-24 04:00:49 +00:00
end
end
2022-07-30 22:40:22 +00:00
function command_string(s)
local result, _ = s:gsub(' %(.*', ''):gsub(' _.*', '')
return result
2022-07-30 22:40:22 +00:00
end
2022-07-24 04:00:49 +00:00
function draw_command_palette()
-- background
App.color(Command_palette_background_color)
love.graphics.rectangle('fill', 0,0, App.screen.width, Menu_status_bar_height)
2022-07-24 04:00:49 +00:00
App.color(Command_palette_border_color)
love.graphics.rectangle('line', 0,0, App.screen.width, Menu_status_bar_height)
2022-07-24 04:00:49 +00:00
-- input box
App.color(Command_palette_command_color)
draw_palette_input(5, 5)
2022-07-24 04:00:49 +00:00
-- alternatives
App.color(Command_palette_alternatives_background_color)
2022-08-11 05:21:37 +00:00
love.graphics.rectangle('fill', 0, Menu_status_bar_height, App.screen.width, 5+Palette_alternatives_height*Line_height+5)
App.color(Command_palette_border_color)
2022-08-11 05:21:37 +00:00
love.graphics.rectangle('line', 0, Menu_status_bar_height, App.screen.width, 5+Palette_alternatives_height*Line_height+5)
Palette_cursor = {y=Menu_status_bar_height+5, x=5, nextx=5}
for i,cmd in ipairs(candidates()) do
2022-08-11 05:11:09 +00:00
add_command_to_palette(cmd, i == Display_settings.palette_index)
end
end
2022-08-11 05:11:09 +00:00
function add_command_to_palette(s, cursor_highlight)
2022-08-05 13:40:40 +00:00
if Text_cache[s] == nil then
Text_cache[s] = App.newText(love.graphics.getFont(), s)
end
2022-08-05 13:40:40 +00:00
local width = App.width(Text_cache[s])
if Palette_cursor.x + width/2 > App.screen.width - 5 then
return
end
2022-08-11 05:11:09 +00:00
if cursor_highlight then
App.color(Command_palette_highlighted_alternative_background_color)
else
App.color(Command_palette_alternatives_background_color)
end
love.graphics.rectangle('fill', Palette_cursor.x-5, Palette_cursor.y, width+10, Line_height)
App.color(Command_palette_alternatives_color)
2022-08-05 13:40:40 +00:00
App.screen.draw(Text_cache[s], Palette_cursor.x, Palette_cursor.y)
Palette_cursor.nextx = math.max(Palette_cursor.nextx, Palette_cursor.x+width+10)
Palette_cursor.y = Palette_cursor.y + Line_height
2022-08-11 05:21:37 +00:00
if Palette_cursor.y >= Menu_status_bar_height + 5+Palette_alternatives_height*Line_height then
App.color(Command_palette_border_color)
love.graphics.line(Palette_cursor.nextx, Menu_status_bar_height+2, Palette_cursor.nextx, Menu_status_bar_height + 5+5*Line_height+5)
Palette_cursor.x = Palette_cursor.nextx + 5
Palette_cursor.y = Menu_status_bar_height+5
2022-07-24 04:00:49 +00:00
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)
if Display_settings.mode == 'normal' then
append(results, file_candidates(Display_settings.palette))
end
return results
end
end
function initial_candidates()
if Cursor_pane.col < 1 then
return Commands.normal
end
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane == nil then
return Commands.normal
end
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()
2022-08-04 11:33:22 +00:00
elseif cmd == 'edit note' or cmd == 'edit note at cursor' then
2022-08-04 19:34:46 +00:00
command.edit_note()
elseif cmd == 'exit editing' then
command.exit_editing()
elseif cmd == 'close column surrounding cursor' then
command.close_column_surrounding_cursor()
2022-08-02 20:22:04 +00:00
elseif cmd == 'add' then
command.add_note(args)
2022-08-02 20:35:04 +00:00
elseif cmd == 'step' then
2022-08-02 22:40:13 +00:00
command.step(args)
elseif cmd == 'extract' then
command.extract()
elseif cmd == 'unroll' then
command.unroll(args)
elseif cmd == 'append' then
command.append_note(args)
elseif cmd == 'neighbors' then
command.neighbors()
elseif cmd == 'down one pane' then
command.down_one_pane()
elseif cmd == 'up one pane' then
command.up_one_pane()
2022-07-31 07:26:11 +00:00
elseif cmd == 'top pane of column' then
command.top_pane_of_column()
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_in_next_column(args)
2022-08-04 11:33:22 +00:00
elseif cmd == 'reload all from disk' then
command.reload_all()
2022-08-04 11:33:22 +00:00
elseif cmd == 'reload note from disk' or cmd == 'reload note at cursor from disk' then
2022-08-04 11:32:45 +00:00
command.reload_note()
elseif cmd == 'delete note at cursor from disk' then
command.delete_note()
-- editing
elseif cmd == 'find in note' then
command.send_key_to_current_pane('C-f', 'f')
elseif cmd == 'copy selection to clipboard' then
command.send_key_to_current_pane('C-c', 'c')
elseif cmd == 'cut selection to clipboard' then
command.send_key_to_current_pane('C-x', 'x')
elseif cmd == 'paste from clipboard' then
command.send_key_to_current_pane('C-v', 'v')
elseif cmd == 'undo' then
command.send_key_to_current_pane('C-z', 'z')
elseif cmd == 'redo' then
command.send_key_to_current_pane('C-y', 'y')
elseif cmd == 'cursor to next word' then
command.send_key_to_current_pane('M-right', 'right')
elseif cmd == 'cursor to previous word' then
command.send_key_to_current_pane('M-left', 'left')
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-08-05 16:29:10 +00:00
-- commands that create columns also need to be recreatable from a title
function create_column(column_name)
if file_exists(Directory..column_name) then
local column = {name=column_name}
local pane = load_pane(column_name)
table.insert(column, pane)
table.insert(Surface, column)
else
-- delegate to one of various helpers based on the column name
local column = {name=column_name}
populate_column(column)
if #column == 0 then
-- Something has changed from underneath us, likely between restarts;
-- assume we already printed out an error.
2022-08-05 16:29:10 +00:00
return
end
table.insert(Surface, column)
end
end
function populate_column(column)
if column.name == 'recently modified' then
populate_recently_modified_column(column)
elseif string.match(column.name, '%S+ from %S+') then
local rel, start_id = string.match(column.name, '(%S+) from (%S+)')
2022-08-05 16:29:10 +00:00
initialize_cache_if_necessary(start_id)
populate_unroll_column(column, start_id, rel)
elseif string.match(column.name, 'neighbors of %S+') then
local start_id = string.match(column.name, 'neighbors of (%S+)')
initialize_cache_if_necessary(start_id)
populate_neighbors_column(column, start_id)
else
error("don't know how to populate column \""..column.name.."\"")
2022-08-05 16:29:10 +00:00
end
end
2022-07-24 04:26:35 +00:00
command = {}
function command.capture()
2022-08-06 19:07:49 +00:00
local pane = new_pane()
2022-08-07 03:22:54 +00:00
local column = {name=pane.id}
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'
if Cursor_pane.col >= 1 then
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane then
refresh_pane_height(pane)
plan_draw()
end
end
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
end
bring_cursor_column_on_screen()
plan_draw()
2022-07-24 04:37:24 +00:00
end
2022-08-04 19:34:46 +00:00
function command.edit_note()
if Cursor_pane.col < 1 then
print('no current note')
return
end
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane == nil then
print('no current note')
return
end
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
if Text.lt1(pane.cursor1, pane.screen_top1) then
pane.cursor1 = {line=pane.screen_top1.line, pos=pane.screen_top1.pos}
end
end
function command.exit_editing()
assert(Cursor_pane.col >= 1)
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
assert(pane)
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
plan_draw()
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
plan_draw()
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
plan_draw()
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
plan_draw()
end
function command.wider_columns()
Display_settings.column_width = Display_settings.column_width + 5*App.width(Em)
for _,column in ipairs(Surface) do
for _,pane in ipairs(column) do
pane.left = 0
pane.right = Display_settings.column_width
end
end
2022-08-08 18:47:33 +00:00
clear_all_pane_heights()
plan_draw()
end
function command.narrower_columns()
Display_settings.column_width = Display_settings.column_width - 5*App.width(Em)
for _,column in ipairs(Surface) do
for _,pane in ipairs(column) do
pane.left = 0
pane.right = Display_settings.column_width
end
end
2022-08-08 18:47:33 +00:00
clear_all_pane_heights()
plan_draw()
end
function command.recently_modified()
if not file_exists(Directory..'recent') then
return
end
local column = {name='recently modified'}
populate_recently_modified_column(column)
add_column_to_right_of_cursor(column)
plan_draw()
end
function populate_recently_modified_column(column)
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
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)
2022-08-06 19:18:33 +00:00
local pane = load_pane(filename)
table.insert(column, pane)
end
end
end
function command.open_file_in_next_column(filename)
local column = {name=filename}
2022-08-06 19:18:33 +00:00
local pane = load_pane(filename)
table.insert(column, pane)
2022-08-03 01:48:08 +00:00
add_column_to_right_of_cursor(column)
plan_draw()
end
function command.reload_all()
2022-08-03 04:35:22 +00:00
local column_names = {}
for _,column in ipairs(Surface) do
table.insert(column_names, column.name)
end
Surface = {}
Cache = {}
local old_viewport = {x=Display_settings.x, y=Display_settings.y}
2022-08-03 04:35:22 +00:00
local old_cursor_pane = Cursor_pane
Cursor_pane = {col=0,row=1}
for _,column_name in ipairs(column_names) do
create_column(column_name)
end
Cursor_pane = old_cursor_pane
-- something's moving us around
Display_settings.x = old_viewport.x
Display_settings.y = old_viewport.y
plan_draw()
end
2022-08-02 20:22:04 +00:00
function command.add_note(rel)
if rel == nil then
rel = 'next'
end
if Cursor_pane.col < 1 then
print('no current note')
return
end
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane == nil then
print('no current note')
return
end
if Cache[pane.id].links[rel] then
print(('%s already has a %s note'):format(pane.id, rel))
return
end
2022-08-03 01:40:13 +00:00
local new_pane = new_pane()
stop_editing_all()
new_pane.editable = true
print('connecting up links')
Cache[pane.id].links[rel] = new_pane.id
Cache[new_pane.id].links[Opposite[rel]] = pane.id
2022-08-03 01:40:13 +00:00
local column = {name=new_pane.id}
table.insert(column, new_pane)
2022-08-03 01:48:08 +00:00
add_column_to_right_of_cursor(column)
2022-08-06 19:57:49 +00:00
refresh_pane_height(pane) -- just in case this is the first link
plan_draw()
end
2022-08-02 22:40:13 +00:00
function command.step(rel)
2022-08-02 20:35:04 +00:00
if rel == nil then
rel = 'next'
end
if Cursor_pane.col < 1 then
print('no current note')
return
end
2022-08-02 20:35:04 +00:00
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane == nil then
print('no current note')
return
end
2022-08-02 20:35:04 +00:00
if Cache[pane.id].links[rel] == nil then
print(('%s has no %s note'):format(pane.id, rel))
return
end
command.open_file_in_next_column(Cache[pane.id].links[rel])
2022-08-02 20:35:04 +00:00
end
function command.extract(rel)
if Cursor_pane.col < 1 then
print('no current note')
return
end
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane == nil then
print('no current note')
return
end
command.open_file_in_next_column(pane.id)
end
function command.unroll(rel)
if rel == nil then
rel = 'next'
end
if Cursor_pane.col < 1 then
print('no current note')
return
end
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane == nil then
print('no current note')
return
end
local column = {name=('%s from %s'):format(rel, pane.id)}
populate_unroll_column(column, pane.id, rel)
if #column == 0 then
return
end
if #Surface[Cursor_pane.col] == 1 then
assert(Cursor_pane.row == 1)
Surface[Cursor_pane.col] = column
else
table.insert(Surface, Cursor_pane.col+1, column)
Cursor_pane.col = Cursor_pane.col+1
Cursor_pane.row = 1
end
2022-08-03 01:48:08 +00:00
bring_cursor_column_on_screen()
plan_draw()
end
function populate_unroll_column(column, id, rel)
-- back out to start of chain
2022-08-03 22:45:45 +00:00
while Cache[id].links[Opposite[rel]] do
id = Cache[id].links[Opposite[rel]]
initialize_cache_if_necessary(id)
end
if Cache[id].links[rel] == nil then
print(('%s has no %s note'):format(id, rel))
return
end
-- unroll from start
local curr = id
local n=0
while curr do
initialize_cache_if_necessary(curr)
curr = Cache[curr].links[rel]
n = n+1
end
curr = id
local i=1
while curr do
2022-08-06 19:18:33 +00:00
local pane = load_pane(curr)
add_title(pane, ('%d/%d'):format(i, n))
table.insert(column, pane)
curr = Cache[curr].links[rel]
i = i+1
end
end
function command.append_note(rel)
if rel == nil then
rel = 'next'
end
if Cursor_pane.col < 1 then
print('no current note')
return
end
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane == nil then
print('no current note')
return
end
local curr_id = pane.id
while true do
initialize_cache_if_necessary(curr_id)
local next_id = Cache[curr_id].links[rel]
if next_id == nil then
break
end
curr_id = next_id
end
2022-08-03 01:40:13 +00:00
local new_pane = new_pane()
2022-08-03 01:30:40 +00:00
stop_editing_all()
new_pane.editable = true
2022-08-03 01:40:13 +00:00
print('connecting up links')
Cache[curr_id].links[rel] = new_pane.id
Cache[new_pane.id].links[Opposite[rel]] = curr_id
2022-08-03 01:40:13 +00:00
local column = {name=new_pane.id}
table.insert(column, new_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
plan_draw()
end
function command.neighbors()
if Cursor_pane.col < 1 then
print('no current note')
return
end
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane == nil then
print('no current note')
return
end
local column = {name=('neighbors of %s'):format(pane.id)}
populate_neighbors_column(column, pane.id)
if #Surface[Cursor_pane.col] == 1 then
assert(Cursor_pane.row == 1)
Surface[Cursor_pane.col] = column
else
table.insert(Surface, Cursor_pane.col+1, column)
Cursor_pane.col = Cursor_pane.col+1
Cursor_pane.row = 1
end
bring_cursor_column_on_screen()
plan_draw()
end
function populate_neighbors_column(column, start_id)
2022-08-06 19:18:33 +00:00
table.insert(column, load_pane(start_id))
for rel,target in pairs(Cache[start_id].links) do
2022-08-06 19:18:33 +00:00
local pane = load_pane(target)
add_title(pane, rel)
table.insert(column, pane)
end
end
2022-08-04 11:32:45 +00:00
function command.reload_note()
if Cursor_pane.col < 1 then
print('no current note')
return
end
2022-08-04 11:32:45 +00:00
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane == nil then
print('no current note')
return
end
2022-08-04 11:32:45 +00:00
Cache[pane.id] = nil
for _,column in ipairs(Surface) do
for i,curr in ipairs(column) do
if curr.id == pane.id then
2022-08-06 19:18:33 +00:00
column[i] = load_pane(pane.id)
2022-08-06 21:14:27 +00:00
column[i].title = pane.title
2022-08-04 11:32:45 +00:00
end
end
end
2022-08-08 18:47:33 +00:00
clear_all_pane_heights()
plan_draw()
2022-08-04 11:32:45 +00:00
end
function command.delete_note()
if Cursor_pane.col < 1 then
print('no current note')
return
end
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane == nil then
print('no current note')
return
end
if #Cache[pane.id].links > 0 then
print('pane has links; giving up')
return
end
-- TODO: test harness support for file ops below
if App.run_tests then
return
end
-- delete from disk
love.filesystem.remove(Directory..pane.id)
-- delete from recently modified
local filenames = {}
for line in love.filesystem.lines(Directory..'recent') do
if line ~= pane.id then
table.insert(filenames, line)
end
end
local f = love.filesystem.newFile(Directory..'recent')
f:open('w')
for _,filename in ipairs(filenames) do
f:write(filename)
f:write('\n')
end
f:close()
-- Delete any columns dedicated to just this note, and update cursor pane if necessary.
local delete_cursor_column = Surface[Cursor_pane.col].name == pane.id
for i=#Surface,Cursor_pane.col+1,-1 do
local column = Surface[i]
if column.name == pane.id then
table.remove(Surface, i)
end
end
local num_deleted = 0
for i=Cursor_pane.col,1,-1 do
local column = Surface[i]
if column.name == pane.id then
table.remove(Surface, i)
num_deleted = num_deleted+1
end
end
if num_deleted > 0 then
Cursor_pane.col = Cursor_pane.col - num_deleted
if delete_cursor_column then
Cursor_pane.row = 1
end
else
assert(not delete_cursor_column)
end
--
while Cursor_pane.row > #Surface[Cursor_pane.col] do
Cursor_pane.row = #Surface[Cursor_pane.col]
if Cursor_pane.row == 0 then
Cursor_pane.col = Cursor_pane.col - 1
Cursor_pane.row = 1
end
end
--
command.reload_all()
end
function command.send_key_to_current_pane(chord, key)
if Cursor_pane.col < 1 then
print('no current note')
return
end
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
if pane == nil then
print('no current note')
return
end
edit.keychord_pressed(pane, chord, key)
end
2022-08-03 01:40:13 +00:00
-- return a new pane with a unique filename
function new_pane()
2022-08-05 20:12:16 +00:00
local id = os.date('%Y/%m/%d/%H-%M-%S')
print('creating directory '..Directory..dirname(id))
local status = love.filesystem.createDirectory(Directory..dirname(id))
2022-08-03 01:40:13 +00:00
assert(status)
Cache[id] = {id=id, filename=Directory..id, lines={{mode='text', data=''}}, line_cache={{}}, left=0, right=Display_settings.column_width, links={}}
2022-08-06 19:18:33 +00:00
return load_pane(id)
2022-08-03 01:40:13 +00:00
end
2022-08-03 01:48:08 +00:00
function add_column_to_right_of_cursor(column)
table.insert(Surface, Cursor_pane.col+1, column)
Cursor_pane.col = Cursor_pane.col+1
Cursor_pane.row = 1
bring_cursor_column_on_screen()
end
function bring_cursor_column_on_screen()
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 emit_links_in_json_in_consistent_order(outfile, links)
--? outfile:write(json.encode(Cache[id].links))
local first_written = false
outfile:write('{')
for _,label in pairs(Edge_list) do
if links[label] then
if first_written then
outfile:write(',')
else
first_written = true
end
outfile:write('"')
outfile:write(label)
outfile:write('":"')
outfile:write(links[label])
outfile:write('"')
end
end
-- links we don't know about, just in case
for rel,target in pairs(links) do
if Opposite[rel] == nil then
if first_written then
outfile:write(',')
else
first_written = true
end
outfile:write('"')
outfile:write(rel)
outfile:write('":"')
outfile:write(target)
outfile:write('"')
end
end
outfile:write('}')
end
function trim(s)
return s:gsub('^%s+', ''):gsub('%s+$', '')
end