2023-09-08 15:01:38 +00:00
|
|
|
Recently_modified_lookback_window = 100 -- how many notes to populate the 'recently modified' column with
|
2022-07-26 17:04:07 +00:00
|
|
|
|
2022-08-05 14:06:07 +00:00
|
|
|
-- keep sync'd with Edge_list
|
2022-07-31 05:44:37 +00:00
|
|
|
Opposite = {
|
|
|
|
next='previous',
|
|
|
|
previous='next',
|
2022-10-18 16:53:33 +00:00
|
|
|
side='previous', -- experiment; you can unroll from a side thread and lose the current note
|
2022-07-31 05:44:37 +00:00
|
|
|
child='parent',
|
|
|
|
parent='child',
|
2022-10-10 06:43:07 +00:00
|
|
|
cross='cross',
|
2022-11-12 05:16:15 +00:00
|
|
|
before='after',
|
|
|
|
after='before',
|
2023-03-26 13:54:49 +00:00
|
|
|
link='backlink',
|
|
|
|
backlink='link',
|
2022-07-31 05:44:37 +00:00
|
|
|
}
|
|
|
|
|
2022-08-05 14:06:07 +00:00
|
|
|
-- keep sync'd with Opposite
|
2023-03-26 13:54:49 +00:00
|
|
|
Edge_list = {'previous', 'next', 'side', 'child', 'parent', 'cross', 'before', 'after', 'link', 'backlink'}
|
2022-08-05 14:06:07 +00:00
|
|
|
|
2023-03-10 21:11:54 +00:00
|
|
|
-- Most link types/labels are unique; a node can have only one link with each type.
|
2022-10-29 20:50:00 +00:00
|
|
|
-- However there are some exceptions.
|
2023-03-10 21:11:54 +00:00
|
|
|
-- The opposite of a unique link (e.g. parent) might be non-unique (e.g. child).
|
|
|
|
Non_unique_links = {'side', 'child', 'cross', 'before', 'after'}
|
2022-10-29 20:50:00 +00:00
|
|
|
|
2022-07-30 21:43:47 +00:00
|
|
|
-- 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 = {
|
2022-07-30 21:43:47 +00:00
|
|
|
normal={
|
|
|
|
'capture',
|
|
|
|
'edit note at cursor (ctrl+e)',
|
|
|
|
'maximize note',
|
|
|
|
'close column surrounding cursor',
|
2022-08-03 04:07:33 +00:00
|
|
|
'add (___) (create immediately link)',
|
|
|
|
'step (___) (open link in new column)',
|
2022-08-03 04:10:48 +00:00
|
|
|
'extract (open note in new column)',
|
2022-10-29 21:05:09 +00:00
|
|
|
'unroll (___) (repeatedly step from cursor; unique only)',
|
|
|
|
'append (___) (repeatedly step, then add; unique only)',
|
2022-08-05 15:56:31 +00:00
|
|
|
'neighbors (open all links in new column)',
|
2022-07-30 21:43:47 +00:00
|
|
|
'down one pane (ctrl+down)',
|
|
|
|
'up one pane (ctrl+up)',
|
|
|
|
'top pane of column (ctrl+home)',
|
|
|
|
'bottom pane of column (ctrl+end)',
|
2022-08-30 00:15:59 +00:00
|
|
|
'grab (temporary second cursor for some commands)',
|
|
|
|
'ungrab (clear second cursor)',
|
2022-08-30 00:20:01 +00:00
|
|
|
'link (___) (to second cursor)',
|
2022-11-12 05:43:34 +00:00
|
|
|
'copy id (of current node to clipboard)',
|
2022-10-18 16:53:33 +00:00
|
|
|
'rename link ___ (to) ___ (some other label)',
|
|
|
|
'clear link ___ (use with care! ignores opposite link)',
|
2022-12-05 16:45:56 +00:00
|
|
|
'move column ___ (after given index; 1 by default)',
|
2022-07-30 21:43:47 +00:00
|
|
|
'wider columns (X)',
|
|
|
|
'narrower columns (x)',
|
|
|
|
'recently modified',
|
2022-10-27 06:13:22 +00:00
|
|
|
'errors',
|
2022-07-31 05:10:32 +00:00
|
|
|
'open file ___',
|
2022-08-29 17:04:39 +00:00
|
|
|
'find on surface (ctrl+f)',
|
2022-08-29 17:56:27 +00:00
|
|
|
'search (all notes)',
|
2022-08-04 11:33:22 +00:00
|
|
|
'reload all from disk',
|
2022-08-03 04:49:51 +00:00
|
|
|
'delete note at cursor from disk (if possible)',
|
2022-08-11 17:49:30 +00:00
|
|
|
'copy selection to clipboard (ctrl+c)',
|
2022-11-16 07:44:54 +00:00
|
|
|
'debug stats (toggle)',
|
2022-11-16 07:38:08 +00:00
|
|
|
'snapshot summary of memory use to disk',
|
2022-07-30 21:43:47 +00:00
|
|
|
},
|
|
|
|
editable={
|
|
|
|
'exit editing (ctrl+e)',
|
2022-08-11 17:49:30 +00:00
|
|
|
'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-07-30 21:43:47 +00:00
|
|
|
'capture',
|
|
|
|
'maximize note',
|
|
|
|
'close column surrounding cursor',
|
2022-08-03 04:07:33 +00:00
|
|
|
'add (___) (create immediately link)',
|
|
|
|
'step (___) (open link in new column)',
|
2022-10-29 21:05:09 +00:00
|
|
|
'unroll (___) (repeatedly step from cursor; unique only)',
|
|
|
|
'append (___) (repeatedly step, then add; unique only)',
|
2022-08-05 15:56:31 +00:00
|
|
|
'neighbors (open all links in new column)',
|
2022-07-30 21:43:47 +00:00
|
|
|
'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 all from disk',
|
2022-12-11 19:56:50 +00:00
|
|
|
'snapshot summary of memory use to disk',
|
2022-07-30 21:43:47 +00:00
|
|
|
},
|
|
|
|
maximized={
|
|
|
|
'back to surface',
|
2022-08-04 11:33:22 +00:00
|
|
|
'edit note (ctrl+e)',
|
2022-09-09 17:55:24 +00:00
|
|
|
'add (___) (create immediately link)',
|
|
|
|
'step (___) (open link in new column)',
|
2022-10-29 21:05:09 +00:00
|
|
|
'append (___) (repeatedly step, then add; unique only)',
|
2022-08-11 17:49:30 +00:00
|
|
|
'copy selection to clipboard (ctrl+c)',
|
2022-07-30 21:43:47 +00:00
|
|
|
},
|
|
|
|
maximized_editable={
|
|
|
|
'exit editing (ctrl+e)',
|
|
|
|
'back to surface',
|
2022-08-11 17:49:30 +00:00
|
|
|
'find in note (ctrl+f)',
|
2022-09-09 17:55:24 +00:00
|
|
|
'add (___) (create immediately link)',
|
|
|
|
'step (___) (open link in new column)',
|
2022-10-29 21:05:09 +00:00
|
|
|
'append (___) (repeatedly step, then add; unique only)',
|
2022-08-11 17:49:30 +00:00
|
|
|
'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-07-30 21:43:47 +00:00
|
|
|
},
|
2022-07-28 04:29:41 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 06:21:54 +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
|
2022-08-04 06:21:54 +00:00
|
|
|
|
|
|
|
function draw_menu_bar()
|
|
|
|
if App.run_tests then return end -- disable in tests
|
2022-08-10 16:54:26 +00:00
|
|
|
App.color(Menu_background_color)
|
2022-08-04 06:21:54 +00:00
|
|
|
love.graphics.rectangle('fill', 0,0, App.screen.width, Menu_status_bar_height)
|
2022-08-10 16:54:26 +00:00
|
|
|
App.color(Menu_border_color)
|
2022-08-04 06:21:54 +00:00
|
|
|
love.graphics.rectangle('line', 0,0, App.screen.width, Menu_status_bar_height)
|
|
|
|
if Display_settings.show_palette then
|
2022-08-31 22:44:35 +00:00
|
|
|
-- TODO: continue to put shortcuts on the menu bar, enter commands/search strings one row down
|
2022-08-04 06:21:54 +00:00
|
|
|
return
|
|
|
|
end
|
2022-08-10 16:54:26 +00:00
|
|
|
App.color(Menu_command_color)
|
2022-08-04 12:16:04 +00:00
|
|
|
Menu_cursor = 5
|
|
|
|
add_hotkey_to_menu('ctrl+enter: search commands...')
|
2022-08-10 16:54:26 +00:00
|
|
|
App.color(Menu_border_color)
|
2022-08-04 06:21:54 +00:00
|
|
|
love.graphics.line(Menu_cursor-10,2, Menu_cursor-10,Menu_status_bar_height-2)
|
2022-08-12 05:56:23 +00:00
|
|
|
if Display_settings.mode == 'search' then
|
|
|
|
add_hotkey_to_menu('esc: cancel')
|
|
|
|
add_hotkey_to_menu('up: next match')
|
|
|
|
add_hotkey_to_menu('down: previous match')
|
|
|
|
add_hotkey_to_menu('ctrl+v: paste')
|
|
|
|
return
|
|
|
|
end
|
2022-08-11 17:49:30 +00:00
|
|
|
if Cursor_pane.col >= 1 then
|
2022-08-19 02:39:05 +00:00
|
|
|
--? print(Cursor_pane.col, Cursor_pane.row, #Surface)
|
2022-08-04 06:21:54 +00:00
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
2022-08-08 17:33:47 +00:00
|
|
|
if pane then
|
2022-08-12 05:56:23 +00:00
|
|
|
if Display_settings.mode == 'normal' then
|
2022-08-11 17:49:30 +00:00
|
|
|
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')
|
2022-08-12 02:37:00 +00:00
|
|
|
add_hotkey_to_menu('ctrl+f: find on surface')
|
2022-08-11 17:49:30 +00:00
|
|
|
add_panning_hotkeys_to_menu()
|
|
|
|
end
|
|
|
|
add_hotkey_to_menu('x/X: narrower/wider columns')
|
2022-08-08 17:33:47 +00:00
|
|
|
else
|
2022-08-22 17:16:11 +00:00
|
|
|
if pane.cursor_x == nil then
|
|
|
|
add_panning_hotkeys_to_menu()
|
|
|
|
else
|
|
|
|
assert(pane.cursor_y)
|
2022-08-22 14:07:38 +00:00
|
|
|
add_hotkey_to_menu('ctrl+e: stop editing')
|
|
|
|
add_hotkey_to_menu('ctrl+h on drawing: help')
|
|
|
|
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')
|
|
|
|
end
|
2022-08-08 17:33:47 +00:00
|
|
|
end
|
2022-08-04 06:21:54 +00:00
|
|
|
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)
|
2023-04-01 21:48:59 +00:00
|
|
|
local width = App.width(s)
|
2022-11-06 17:17:02 +00:00
|
|
|
if Menu_cursor > App.screen.width - 30 then
|
2022-08-04 06:21:54 +00:00
|
|
|
return
|
|
|
|
end
|
2022-08-10 16:54:26 +00:00
|
|
|
App.color(Menu_command_color)
|
2023-04-01 21:48:59 +00:00
|
|
|
App.screen.print(s, Menu_cursor,5)
|
2022-08-04 06:21:54 +00:00
|
|
|
Menu_cursor = Menu_cursor + width + 30
|
|
|
|
end
|
|
|
|
|
2022-12-24 03:30:06 +00:00
|
|
|
function keychord_press_on_command_palette(chord, key)
|
2022-07-24 04:00:49 +00:00
|
|
|
if chord == 'escape' then
|
2023-05-03 05:02:17 +00:00
|
|
|
-- forget text for next command
|
|
|
|
Display_settings.palette_command = ''
|
2022-07-29 03:36:39 +00:00
|
|
|
Display_settings.show_palette = false
|
2023-05-03 05:02:17 +00:00
|
|
|
Display_settings.palette_alternative_index = 1
|
2022-08-31 04:23:59 +00:00
|
|
|
Display_settings.palette_candidates = nil
|
2022-07-24 04:00:49 +00:00
|
|
|
elseif chord == 'backspace' then
|
2022-08-11 21:35:42 +00:00
|
|
|
local len = utf8.len(Display_settings.palette_command)
|
|
|
|
local byte_offset = Text.offset(Display_settings.palette_command, len)
|
|
|
|
Display_settings.palette_command = string.sub(Display_settings.palette_command, 1, byte_offset-1)
|
2022-08-11 21:36:58 +00:00
|
|
|
Display_settings.palette_alternative_index = 1
|
2022-08-31 04:23:59 +00:00
|
|
|
Display_settings.palette_candidates = candidates()
|
2022-07-24 05:21:00 +00:00
|
|
|
elseif chord == 'tab' then
|
|
|
|
-- select top candidate, but don't submit
|
2022-08-31 04:23:59 +00:00
|
|
|
local candidates = Display_settings.palette_candidates
|
2022-08-11 21:35:42 +00:00
|
|
|
Display_settings.palette_command = command_string(candidates[Display_settings.palette_alternative_index])
|
2022-08-11 19:37:56 +00:00
|
|
|
elseif chord == 'C-v' then
|
2023-09-01 03:01:44 +00:00
|
|
|
Display_settings.palette_command = Display_settings.palette_command..App.get_clipboard()
|
2022-08-31 04:23:59 +00:00
|
|
|
Display_settings.palette_candidates = candidates()
|
2022-07-24 04:00:49 +00:00
|
|
|
elseif chord == 'return' then
|
2022-08-31 04:23:59 +00:00
|
|
|
-- submit selected candidate
|
|
|
|
local candidates = Display_settings.palette_candidates
|
2022-07-24 04:00:49 +00:00
|
|
|
if #candidates > 0 then
|
2022-08-11 21:35:42 +00:00
|
|
|
if file_exists(Directory..candidates[Display_settings.palette_alternative_index]) then
|
|
|
|
command.open_file_in_next_column(candidates[Display_settings.palette_alternative_index])
|
2022-07-24 04:26:35 +00:00
|
|
|
else
|
2022-08-11 21:35:42 +00:00
|
|
|
run_command(command_string(candidates[Display_settings.palette_alternative_index]))
|
2022-07-24 04:26:35 +00:00
|
|
|
end
|
2022-07-30 22:32:19 +00:00
|
|
|
else
|
|
|
|
-- try to run the command as if it contains args
|
2022-08-11 21:35:42 +00:00
|
|
|
run_command_with_args(Display_settings.palette_command)
|
2022-07-24 04:00:49 +00:00
|
|
|
end
|
2022-07-27 17:01:32 +00:00
|
|
|
-- forget text for next command
|
2022-08-11 21:35:42 +00:00
|
|
|
Display_settings.palette_command = ''
|
2022-07-29 03:36:39 +00:00
|
|
|
Display_settings.show_palette = false
|
2022-08-11 21:35:42 +00:00
|
|
|
Display_settings.palette_alternative_index = 1
|
2022-08-31 04:23:59 +00:00
|
|
|
Display_settings.palette_candidates = nil
|
2022-12-04 05:09:08 +00:00
|
|
|
-- clean up some columns if possible
|
2022-12-13 06:14:23 +00:00
|
|
|
if Cursor_pane.col < 45 then
|
|
|
|
while #Surface > 50 do
|
2023-03-09 02:23:37 +00:00
|
|
|
print_and_log('keychord_press (palette) return: dropping '..Surface[#Surface].name)
|
2022-12-04 05:09:08 +00:00
|
|
|
table.remove(Surface)
|
|
|
|
end
|
|
|
|
end
|
2022-08-11 05:23:27 +00:00
|
|
|
elseif chord == 'up' then
|
2022-08-11 21:35:42 +00:00
|
|
|
if Display_settings.palette_alternative_index > 1 then
|
|
|
|
Display_settings.palette_alternative_index = Display_settings.palette_alternative_index-1
|
2022-08-11 05:23:27 +00:00
|
|
|
end
|
|
|
|
elseif chord == 'down' then
|
2022-08-31 04:23:59 +00:00
|
|
|
if Display_settings.palette_alternative_index < #Display_settings.palette_candidates then
|
2022-08-11 21:35:42 +00:00
|
|
|
Display_settings.palette_alternative_index = Display_settings.palette_alternative_index+1
|
2022-08-11 05:23:27 +00:00
|
|
|
end
|
|
|
|
elseif chord == 'left' then
|
2022-08-11 21:35:42 +00:00
|
|
|
if Display_settings.palette_alternative_index > Palette_alternatives_height then
|
|
|
|
Display_settings.palette_alternative_index = Display_settings.palette_alternative_index-Palette_alternatives_height
|
2022-08-11 05:23:27 +00:00
|
|
|
end
|
|
|
|
elseif chord == 'right' then
|
2022-08-31 04:23:59 +00:00
|
|
|
if Display_settings.palette_alternative_index <= #Display_settings.palette_candidates-Palette_alternatives_height then
|
2022-08-11 21:35:42 +00:00
|
|
|
Display_settings.palette_alternative_index = Display_settings.palette_alternative_index+Palette_alternatives_height
|
2022-08-11 05:23:27 +00:00
|
|
|
end
|
2022-07-24 04:00:49 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-30 22:40:22 +00:00
|
|
|
function command_string(s)
|
2022-07-31 05:44:37 +00:00
|
|
|
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)
|
2022-08-04 06:21:54 +00:00
|
|
|
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)
|
2022-08-04 06:21:54 +00:00
|
|
|
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)
|
2022-08-04 06:21:54 +00:00
|
|
|
draw_palette_input(5, 5)
|
2022-07-24 04:00:49 +00:00
|
|
|
-- alternatives
|
2022-08-10 16:54:26 +00:00
|
|
|
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)
|
2022-08-04 06:21:54 +00:00
|
|
|
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)
|
2022-08-04 06:21:54 +00:00
|
|
|
Palette_cursor = {y=Menu_status_bar_height+5, x=5, nextx=5}
|
2022-08-31 04:23:59 +00:00
|
|
|
for i,cmd in ipairs(Display_settings.palette_candidates) do
|
2022-08-11 21:35:42 +00:00
|
|
|
add_command_to_palette(cmd, i == Display_settings.palette_alternative_index)
|
2022-08-04 06:21:54 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-08-29 17:56:27 +00:00
|
|
|
function draw_command_palette_for_search_all()
|
|
|
|
-- background
|
|
|
|
App.color(Command_palette_background_color)
|
|
|
|
love.graphics.rectangle('fill', 0,0, App.screen.width, Menu_status_bar_height)
|
|
|
|
App.color(Command_palette_border_color)
|
|
|
|
love.graphics.rectangle('line', 0,0, App.screen.width, Menu_status_bar_height)
|
|
|
|
-- input box
|
|
|
|
App.color(Command_palette_command_color)
|
|
|
|
local x = 5
|
|
|
|
local y = 5
|
2023-04-02 15:53:43 +00:00
|
|
|
love.graphics.print(Display_settings.search_all_query, x,y)
|
2022-08-29 17:56:27 +00:00
|
|
|
if Display_settings.mode == 'search_all' then
|
2022-10-31 16:21:33 +00:00
|
|
|
-- draw cursor
|
2023-04-07 21:46:43 +00:00
|
|
|
x = x+App.width(Display_settings.search_all_query)
|
2022-08-29 17:56:27 +00:00
|
|
|
draw_cursor(x, y)
|
2022-10-31 16:21:33 +00:00
|
|
|
elseif Display_settings.mode == 'searching_all' then
|
|
|
|
-- show progress
|
2022-08-29 17:56:27 +00:00
|
|
|
App.color(Command_palette_alternatives_background_color)
|
|
|
|
love.graphics.rectangle('fill', 0, Menu_status_bar_height, App.screen.width, 5+Line_height+5)
|
|
|
|
App.color(Command_palette_border_color)
|
|
|
|
love.graphics.rectangle('line', 0, Menu_status_bar_height, App.screen.width, 5+Line_height+5)
|
2022-08-29 22:26:39 +00:00
|
|
|
App.screen.print(Display_settings.search_all_progress_indicator, --[[x]] 5, --[[y]] Menu_status_bar_height+5)
|
2022-08-29 17:56:27 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-08-11 05:11:09 +00:00
|
|
|
function add_command_to_palette(s, cursor_highlight)
|
2023-04-02 15:53:43 +00:00
|
|
|
local width = App.width(s)
|
2022-08-04 06:21:54 +00:00
|
|
|
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
|
2022-08-11 05:16:50 +00:00
|
|
|
love.graphics.rectangle('fill', Palette_cursor.x-5, Palette_cursor.y, width+10, Line_height)
|
2022-08-04 17:09:57 +00:00
|
|
|
App.color(Command_palette_alternatives_color)
|
2023-04-02 15:53:43 +00:00
|
|
|
App.screen.print(s, Palette_cursor.x, Palette_cursor.y)
|
2022-08-04 06:21:54 +00:00
|
|
|
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
|
2022-08-04 17:09:57 +00:00
|
|
|
App.color(Command_palette_border_color)
|
2022-08-04 06:21:54 +00:00
|
|
|
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)
|
2023-04-02 15:53:43 +00:00
|
|
|
love.graphics.print(Display_settings.palette_command, x,y)
|
|
|
|
x = x+App.width(Display_settings.palette_command)
|
2022-07-24 04:00:49 +00:00
|
|
|
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
|
2022-07-30 22:32:19 +00:00
|
|
|
local candidates = initial_candidates()
|
2022-08-11 21:35:42 +00:00
|
|
|
if Display_settings.palette_command == '' then
|
2022-07-30 22:32:19 +00:00
|
|
|
return candidates
|
2022-08-11 21:35:42 +00:00
|
|
|
elseif Display_settings.palette_command:sub(1,1) == '/' then
|
2022-07-30 22:32:19 +00:00
|
|
|
return {}
|
|
|
|
else
|
2022-08-11 21:35:42 +00:00
|
|
|
local results = filter_candidates(candidates, Display_settings.palette_command)
|
2022-08-11 05:26:30 +00:00
|
|
|
if Display_settings.mode == 'normal' then
|
2022-08-11 21:35:42 +00:00
|
|
|
append(results, file_candidates(Display_settings.palette_command))
|
2022-08-11 05:26:30 +00:00
|
|
|
end
|
2022-07-30 22:32:19 +00:00
|
|
|
return results
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function initial_candidates()
|
2022-08-11 17:07:58 +00:00
|
|
|
if Cursor_pane.col < 1 then
|
2022-08-04 00:05:43 +00:00
|
|
|
return Commands.normal
|
|
|
|
end
|
2022-07-28 04:33:45 +00:00
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
2022-08-08 17:33:47 +00:00
|
|
|
if pane == nil then
|
|
|
|
return Commands.normal
|
|
|
|
end
|
2022-07-30 21:43:47 +00:00
|
|
|
if Display_settings.mode == 'normal' then
|
|
|
|
if not pane.editable then
|
2022-07-30 22:32:19 +00:00
|
|
|
return Commands.normal
|
2022-07-30 21:43:47 +00:00
|
|
|
else
|
2022-07-30 22:32:19 +00:00
|
|
|
return Commands.editable
|
2022-07-30 21:43:47 +00:00
|
|
|
end
|
|
|
|
elseif Display_settings.mode == 'maximize' then
|
|
|
|
if not pane.editable then
|
2022-07-30 22:32:19 +00:00
|
|
|
return Commands.maximized
|
2022-07-30 21:43:47 +00:00
|
|
|
else
|
2022-07-30 22:32:19 +00:00
|
|
|
return Commands.maximized_editable
|
2022-07-30 21:43:47 +00:00
|
|
|
end
|
2022-07-28 04:33:45 +00:00
|
|
|
end
|
2022-07-24 04:00:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function filter_candidates(candidates, prefix)
|
|
|
|
local result = {}
|
|
|
|
for _,cand in ipairs(candidates) do
|
2022-07-27 01:27:18 +00:00
|
|
|
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)
|
2022-08-31 04:16:26 +00:00
|
|
|
--? print('-- '..prefix)
|
2023-09-08 21:16:50 +00:00
|
|
|
local info = nativefs.getInfo(Directory..prefix)
|
2022-08-31 04:16:26 +00:00
|
|
|
--? print(info)
|
|
|
|
--? if info then
|
|
|
|
--? print(info.type)
|
|
|
|
--? end
|
|
|
|
if info and info.type == 'file' 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 = ''
|
2022-08-31 04:16:26 +00:00
|
|
|
if info and info.type == 'directory' then
|
|
|
|
if prefix:sub(#prefix) == '/' then
|
|
|
|
visible_dir = prefix
|
|
|
|
else
|
|
|
|
visible_dir = prefix..'/'
|
|
|
|
end
|
|
|
|
path = path..visible_dir
|
|
|
|
elseif prefix:find('/') then
|
2022-07-24 05:17:41 +00:00
|
|
|
visible_dir = dirname(prefix)
|
2022-08-31 04:16:26 +00:00
|
|
|
path = path..visible_dir
|
2022-07-24 04:00:49 +00:00
|
|
|
end
|
2022-08-31 04:16:26 +00:00
|
|
|
--? print('path:', path)
|
2023-09-08 21:16:50 +00:00
|
|
|
local files = App.files(path)
|
2022-08-31 04:16:26 +00:00
|
|
|
--? print(#files, 'files')
|
|
|
|
local base
|
|
|
|
if info and info.type == 'directory' then
|
|
|
|
base = ''
|
|
|
|
else
|
|
|
|
base = basename(prefix)
|
|
|
|
end
|
2022-07-24 04:00:49 +00:00
|
|
|
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
|
2023-09-08 21:16:50 +00:00
|
|
|
info[file] = nativefs.getInfo(dir..'/'..file)
|
2022-07-24 04:00:49 +00:00
|
|
|
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
|
|
|
|
2022-07-30 21:59:05 +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()
|
2022-07-30 21:59:05 +00:00
|
|
|
elseif cmd == 'exit editing' then
|
|
|
|
command.exit_editing()
|
|
|
|
elseif cmd == 'close column surrounding cursor' then
|
|
|
|
command.close_column_surrounding_cursor()
|
2022-08-30 00:13:36 +00:00
|
|
|
elseif cmd == 'grab' then
|
|
|
|
command.grab()
|
|
|
|
elseif cmd == 'ungrab' then
|
|
|
|
command.ungrab()
|
2022-08-30 00:20:01 +00:00
|
|
|
elseif cmd == 'link' then
|
|
|
|
command.link(args)
|
2022-11-12 05:43:34 +00:00
|
|
|
elseif cmd == 'copy id' then
|
|
|
|
command.copy_id_to_clipboard()
|
2022-10-18 16:53:33 +00:00
|
|
|
elseif cmd == 'rename link' then
|
|
|
|
command.rename_link(args)
|
|
|
|
elseif cmd == 'clear link' then
|
|
|
|
command.clear_link(args)
|
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)
|
2022-08-03 04:10:48 +00:00
|
|
|
elseif cmd == 'extract' then
|
|
|
|
command.extract()
|
2022-08-02 22:46:22 +00:00
|
|
|
elseif cmd == 'unroll' then
|
|
|
|
command.unroll(args)
|
2022-08-03 01:26:26 +00:00
|
|
|
elseif cmd == 'append' then
|
|
|
|
command.append_note(args)
|
2022-08-05 15:56:31 +00:00
|
|
|
elseif cmd == 'neighbors' then
|
|
|
|
command.neighbors()
|
2022-07-30 21:59:05 +00:00
|
|
|
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()
|
2022-07-30 21:59:05 +00:00
|
|
|
elseif cmd == 'bottom pane of column' then
|
|
|
|
command.bottom_pane_of_column()
|
2022-12-05 16:45:56 +00:00
|
|
|
elseif cmd == 'move column' then
|
|
|
|
command.move_column(args)
|
2022-07-30 21:59:05 +00:00
|
|
|
elseif cmd == 'wider columns' then
|
|
|
|
command.wider_columns()
|
|
|
|
elseif cmd == 'narrower columns' then
|
|
|
|
command.narrower_columns()
|
|
|
|
elseif cmd == 'recently modified' then
|
|
|
|
command.recently_modified()
|
2022-10-27 06:13:22 +00:00
|
|
|
elseif cmd == 'errors' then
|
|
|
|
command.errors()
|
2022-07-30 22:32:19 +00:00
|
|
|
elseif cmd == 'open file' then
|
2022-08-09 04:20:03 +00:00
|
|
|
command.open_file_in_next_column(args)
|
2022-08-29 17:04:39 +00:00
|
|
|
elseif cmd == 'find on surface' then
|
|
|
|
command.commence_find_on_surface()
|
2022-08-29 17:56:27 +00:00
|
|
|
elseif cmd == 'search' then
|
|
|
|
command.commence_search_in_disk()
|
2022-08-04 11:33:22 +00:00
|
|
|
elseif cmd == 'reload all from disk' then
|
2022-08-03 04:31:28 +00:00
|
|
|
command.reload_all()
|
2022-08-03 04:49:51 +00:00
|
|
|
elseif cmd == 'delete note at cursor from disk' then
|
|
|
|
command.delete_note()
|
2022-11-16 07:44:54 +00:00
|
|
|
elseif cmd == 'debug stats' then
|
|
|
|
Display_settings.show_debug = not Display_settings.show_debug
|
2022-11-16 07:38:08 +00:00
|
|
|
elseif cmd == 'snapshot summary of memory use to disk' then
|
|
|
|
command.snapshot_memory()
|
2022-08-11 17:49:30 +00:00
|
|
|
-- 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')
|
2022-07-30 21:59:05 +00:00
|
|
|
else
|
2023-09-08 21:16:50 +00:00
|
|
|
print_and_log(('run_command: not implemented yet: %s'):format(cmd))
|
2022-07-30 21:59:05 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-30 22:32:19 +00:00
|
|
|
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)
|
2022-07-30 22:32:19 +00:00
|
|
|
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
|
2022-08-11 18:05:38 +00:00
|
|
|
-- I'm assuming that special columns have multiple words, and single-word
|
|
|
|
-- columns are always filenames. pensieve.love won't ever create filenames
|
|
|
|
-- containing spaces.
|
2022-08-05 16:29:10 +00:00
|
|
|
function create_column(column_name)
|
2022-08-09 04:20:03 +00:00
|
|
|
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)
|
2022-08-11 18:05:38 +00:00
|
|
|
elseif not column_name:find(' ') then
|
|
|
|
-- File not found
|
|
|
|
--
|
|
|
|
-- It makes me nervous to silently drop errors, but at this point there's
|
|
|
|
-- really nothing actionable someone can do in response to an error.
|
|
|
|
--
|
|
|
|
-- Deeper issue: no way yet to communicate errors in the UI.
|
|
|
|
--
|
|
|
|
-- Philosophical question: what does crash-only mean if you ever run into
|
|
|
|
-- data loss? There's a hard tension between resilience and silent failures.
|
|
|
|
--
|
|
|
|
-- For now I'm going to rely on all my protections against data loss
|
|
|
|
-- elsewhere. Lines.love has never lost my data in several months of use.
|
|
|
|
--
|
|
|
|
-- While data loss seems unlikely, there _is_ a legitimate way you can end
|
|
|
|
-- up with a filename that doesn't exist: start a capture, then change
|
|
|
|
-- your mind and never type anything into it. It will continue to show as
|
|
|
|
-- a column on the surface, but there's no file backing it. You can still
|
|
|
|
-- edit it later and create a file for it. But if you just quit, the
|
|
|
|
-- column will silently disappear after restart.
|
2023-03-09 02:23:37 +00:00
|
|
|
print_and_log('create_column: file not found: '..column_name)
|
2022-08-05 16:50:22 +00:00
|
|
|
else
|
2022-08-09 04:20:03 +00:00
|
|
|
-- delegate to one of various helpers based on the column name
|
2022-08-05 16:50:22 +00:00
|
|
|
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)
|
2022-08-05 16:50:22 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function populate_column(column)
|
2022-08-09 04:20:03 +00:00
|
|
|
if column.name == 'recently modified' then
|
|
|
|
populate_recently_modified_column(column)
|
2022-10-27 06:13:22 +00:00
|
|
|
elseif column.name == 'errors' then
|
|
|
|
populate_errors_column(column)
|
2022-08-09 04:20:03 +00:00
|
|
|
elseif string.match(column.name, '%S+ from %S+') then
|
2022-08-05 16:50:22 +00:00
|
|
|
local rel, start_id = string.match(column.name, '(%S+) from (%S+)')
|
|
|
|
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+)')
|
|
|
|
populate_neighbors_column(column, start_id)
|
2022-10-29 21:05:09 +00:00
|
|
|
elseif string.match(column.name, '%S+ of %S+') then
|
|
|
|
local rel, start_id = string.match(column.name, '(%S+) of (%S+)')
|
|
|
|
populate_step_column(column, start_id, rel)
|
2022-08-29 17:56:27 +00:00
|
|
|
elseif string.match(column.name, 'search: .+') then
|
2022-08-29 22:26:39 +00:00
|
|
|
--? print('column name', column.name)
|
2022-08-29 23:30:46 +00:00
|
|
|
local search_all_query = string.match(column.name, 'search: (.+)')
|
|
|
|
--? print('search term', search_all_query)
|
|
|
|
populate_search_all_column(column, search_all_query)
|
2022-08-05 16:50:22 +00:00
|
|
|
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 = {}
|
|
|
|
|
2022-07-29 05:23:35 +00:00
|
|
|
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
|
2022-08-13 14:38:33 +00:00
|
|
|
bring_cursor_column_on_screen()
|
2022-07-24 04:26:35 +00:00
|
|
|
stop_editing_all()
|
2022-07-29 19:27:45 +00:00
|
|
|
pane.editable = true
|
2022-08-13 14:39:11 +00:00
|
|
|
command.maximize_note()
|
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()
|
2022-07-29 04:58:46 +00:00
|
|
|
Display_settings.mode = 'maximize'
|
|
|
|
end
|
|
|
|
|
2022-07-29 05:08:59 +00:00
|
|
|
function command.back_to_surface()
|
|
|
|
Display_settings.mode = 'normal'
|
2022-08-11 17:07:58 +00:00
|
|
|
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
|
2022-08-08 18:04:25 +00:00
|
|
|
bring_cursor_column_on_screen()
|
|
|
|
plan_draw()
|
2022-07-24 04:37:24 +00:00
|
|
|
end
|
2022-07-24 04:39:30 +00:00
|
|
|
|
2022-08-04 19:34:46 +00:00
|
|
|
function command.edit_note()
|
2022-08-11 17:07:58 +00:00
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-07-24 04:39:30 +00:00
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
2022-08-11 17:07:58 +00:00
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-07-29 19:27:45 +00:00
|
|
|
assert(not pane.editable)
|
2022-07-29 04:55:01 +00:00
|
|
|
stop_editing_all()
|
|
|
|
pane.recent_updated = false
|
2022-07-29 19:27:45 +00:00
|
|
|
pane.editable = true
|
2022-08-09 04:53:25 +00:00
|
|
|
if Text.lt1(pane.cursor1, pane.screen_top1) then
|
|
|
|
pane.cursor1 = {line=pane.screen_top1.line, pos=pane.screen_top1.pos}
|
|
|
|
end
|
2022-07-29 04:55:01 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function command.exit_editing()
|
2022-08-11 17:07:58 +00:00
|
|
|
assert(Cursor_pane.col >= 1)
|
2022-07-29 04:55:01 +00:00
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
2022-08-11 17:07:58 +00:00
|
|
|
assert(pane)
|
2022-07-29 19:27:45 +00:00
|
|
|
assert(pane.editable)
|
2022-07-29 04:55:01 +00:00
|
|
|
stop_editing(pane)
|
2022-07-24 04:39:30 +00:00
|
|
|
end
|
2022-07-24 05:38:19 +00:00
|
|
|
|
2022-07-30 05:17:40 +00:00
|
|
|
function command.down_one_pane()
|
|
|
|
if Cursor_pane.row < #Surface[Cursor_pane.col] then
|
|
|
|
Cursor_pane.row = Cursor_pane.row + 1
|
|
|
|
end
|
2022-07-30 15:34:01 +00:00
|
|
|
Display_settings.y = up_edge_sy(Cursor_pane.col, Cursor_pane.row) - Padding_vertical
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-07-30 05:17:40 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function command.up_one_pane()
|
|
|
|
if Cursor_pane.row > 1 then
|
|
|
|
Cursor_pane.row = Cursor_pane.row - 1
|
|
|
|
end
|
2022-07-30 15:34:01 +00:00
|
|
|
Display_settings.y = up_edge_sy(Cursor_pane.col, Cursor_pane.row) - Padding_vertical
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-07-30 05:17:40 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function command.bottom_pane_of_column()
|
|
|
|
if Cursor_pane.row < #Surface[Cursor_pane.col] then
|
|
|
|
Cursor_pane.row = #Surface[Cursor_pane.col]
|
|
|
|
end
|
2022-07-30 15:34:01 +00:00
|
|
|
Display_settings.y = up_edge_sy(Cursor_pane.col, Cursor_pane.row) - Padding_vertical
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-07-30 05:17:40 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function command.top_pane_of_column()
|
|
|
|
if Cursor_pane.row > 1 then
|
|
|
|
Cursor_pane.row = 1
|
|
|
|
end
|
2022-07-30 15:34:01 +00:00
|
|
|
Display_settings.y = up_edge_sy(Cursor_pane.col, Cursor_pane.row) - Padding_vertical
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-07-30 05:17:40 +00:00
|
|
|
end
|
|
|
|
|
2022-12-05 16:45:56 +00:00
|
|
|
-- move column to _after_ index
|
|
|
|
function command.move_column(index)
|
|
|
|
if index == nil then index = 1 end
|
|
|
|
if Cursor_pane.col < 1 then
|
|
|
|
add_error('no current column')
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local column = Surface[Cursor_pane.col]
|
|
|
|
table.remove(Surface, Cursor_pane.col)
|
|
|
|
table.insert(Surface, index+1, column)
|
|
|
|
Cursor_pane.col = index+1
|
|
|
|
bring_cursor_column_on_screen()
|
|
|
|
bring_cursor_of_cursor_pane_in_view('down')
|
|
|
|
plan_draw()
|
|
|
|
end
|
|
|
|
|
2022-07-30 21:13:30 +00:00
|
|
|
function command.wider_columns()
|
2023-04-02 15:53:43 +00:00
|
|
|
Display_settings.column_width = Display_settings.column_width + 5*App.width('m')
|
2022-08-04 12:03:46 +00:00
|
|
|
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()
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-07-30 21:13:30 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function command.narrower_columns()
|
2023-04-02 15:53:43 +00:00
|
|
|
Display_settings.column_width = Display_settings.column_width - 5*App.width('m')
|
2022-08-04 12:03:46 +00:00
|
|
|
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()
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-07-30 21:13:30 +00:00
|
|
|
end
|
|
|
|
|
2022-07-24 05:38:19 +00:00
|
|
|
function command.recently_modified()
|
2022-08-04 00:05:43 +00:00
|
|
|
if not file_exists(Directory..'recent') then
|
|
|
|
return
|
|
|
|
end
|
2022-08-09 04:20:03 +00:00
|
|
|
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 = {}
|
2023-09-08 21:16:50 +00:00
|
|
|
local f = App.open_for_reading(Directory..'recent')
|
|
|
|
for line in f:lines() do
|
2022-07-26 17:02:27 +00:00
|
|
|
table.insert(filenames, line)
|
2022-07-24 05:38:19 +00:00
|
|
|
end
|
2022-07-26 17:04:07 +00:00
|
|
|
local done, ndone = {}, 0
|
2022-07-26 17:02:27 +00:00
|
|
|
for i=#filenames,1,-1 do
|
|
|
|
local filename = filenames[i]
|
2022-07-26 17:04:07 +00:00
|
|
|
if ndone >= Recently_modified_lookback_window then break end
|
2022-07-24 05:38:19 +00:00
|
|
|
if not done[filename] then
|
|
|
|
done[filename] = true
|
2022-07-26 17:04:07 +00:00
|
|
|
ndone = ndone+1
|
2022-07-24 06:23:59 +00:00
|
|
|
--? print('loading', filename)
|
2022-08-06 19:18:33 +00:00
|
|
|
local pane = load_pane(filename)
|
2022-07-24 05:38:19 +00:00
|
|
|
table.insert(column, pane)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2022-07-24 07:08:52 +00:00
|
|
|
|
2022-10-27 06:13:22 +00:00
|
|
|
function command.errors()
|
|
|
|
local column = {name='errors'}
|
|
|
|
populate_errors_column(column)
|
|
|
|
add_column_to_right_of_cursor(column)
|
|
|
|
plan_draw()
|
|
|
|
end
|
|
|
|
|
|
|
|
function populate_errors_column(column)
|
2022-11-23 06:22:38 +00:00
|
|
|
-- TODO: we might run into some bugs if we have multiple error panes visible
|
|
|
|
-- on the surface; unlike everything else these currently alias.
|
|
|
|
local pane = Error_log
|
2022-10-27 06:13:22 +00:00
|
|
|
pane.font_height = Font_height
|
|
|
|
pane.line_height = Line_height
|
|
|
|
pane.editable = false
|
2023-03-17 17:55:33 +00:00
|
|
|
edit.check_locs(pane)
|
2022-10-27 06:13:22 +00:00
|
|
|
pane.title = '(do not edit)'
|
2022-11-23 06:22:38 +00:00
|
|
|
Text.redraw_all(pane)
|
2022-10-27 06:13:22 +00:00
|
|
|
table.insert(column, pane)
|
|
|
|
end
|
|
|
|
|
2022-08-09 04:20:03 +00:00
|
|
|
function command.open_file_in_next_column(filename)
|
2022-07-24 07:08:52 +00:00
|
|
|
local column = {name=filename}
|
2022-08-06 19:18:33 +00:00
|
|
|
local pane = load_pane(filename)
|
2022-07-24 07:08:52 +00:00
|
|
|
table.insert(column, pane)
|
2022-08-03 01:48:08 +00:00
|
|
|
add_column_to_right_of_cursor(column)
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-07-24 07:08:52 +00:00
|
|
|
end
|
2022-07-30 22:32:19 +00:00
|
|
|
|
2022-08-29 17:04:39 +00:00
|
|
|
function command.commence_find_on_surface()
|
|
|
|
-- save some state
|
|
|
|
clear_selections()
|
|
|
|
Display_settings.search_backup_x = Display_settings.x
|
|
|
|
Display_settings.search_backup_y = Display_settings.y
|
|
|
|
Display_settings.search_backup_cursor_pane = {row=Cursor_pane.row, col=Cursor_pane.col}
|
|
|
|
-- prepare to pass Display_settings to Text.draw_search_bar
|
|
|
|
--? print('entering search mode')
|
|
|
|
Display_settings.mode = 'search'
|
|
|
|
Display_settings.search_term = ''
|
|
|
|
Display_settings.line_height = Line_height
|
|
|
|
end
|
|
|
|
|
2022-08-29 17:56:27 +00:00
|
|
|
function command.commence_search_in_disk()
|
|
|
|
Display_settings.mode = 'search_all'
|
|
|
|
Display_settings.search_all_pane = initialize_search_all_pane()
|
2022-08-29 23:30:46 +00:00
|
|
|
Display_settings.search_all_query = ''
|
2022-08-29 22:26:39 +00:00
|
|
|
Display_settings.search_all_progress_indicator = 'starting search...'
|
2022-08-29 17:56:27 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- search panes are opposites of regular panes
|
|
|
|
-- regular pane: pass in id, load from disk, may be edited
|
|
|
|
-- search pane: create without id, initialize id after search term is typed in, create empty file, slowly append to disk, may not be edited
|
|
|
|
function initialize_search_all_pane()
|
|
|
|
local result = edit.initialize_state(0, 0, math.min(Display_settings.column_width, App.screen.width-Margin_right), Font_height, Line_height)
|
|
|
|
result.font_height = Font_height
|
|
|
|
result.line_height = Line_height
|
|
|
|
result.editable = false
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
|
|
|
function finalize_search_all_pane()
|
2022-09-02 16:36:26 +00:00
|
|
|
if Display_settings.search_all_query:sub(1,1) == '"' then
|
|
|
|
-- support only a single quoted phrase by itself
|
|
|
|
assert(Display_settings.search_all_query:sub(#Display_settings.search_all_query) == '"')
|
|
|
|
Display_settings.search_all_terms = {Display_settings.search_all_query:sub(2, #Display_settings.search_all_query-1)}
|
|
|
|
else
|
|
|
|
Display_settings.search_all_terms = split(Display_settings.search_all_query)
|
|
|
|
end
|
2022-08-29 23:30:46 +00:00
|
|
|
local id = id_for_search_all_pane(Display_settings.search_all_query)
|
2022-08-29 17:56:27 +00:00
|
|
|
--? print(id)
|
2023-09-08 21:16:50 +00:00
|
|
|
App.remove(Directory..id)
|
2022-08-29 17:56:27 +00:00
|
|
|
Display_settings.search_all_pane.id = id
|
|
|
|
Display_settings.search_all_pane.filename = Directory..id
|
|
|
|
Display_settings.editable = false
|
|
|
|
end
|
|
|
|
|
2022-08-29 22:26:39 +00:00
|
|
|
function id_for_search_all_pane(term)
|
|
|
|
return 'search/'..term:gsub('%W', '_')
|
|
|
|
end
|
|
|
|
|
2022-08-29 17:56:27 +00:00
|
|
|
function add_search_all_pane_to_right_of_cursor()
|
2022-08-29 23:30:46 +00:00
|
|
|
local column = {name='search: '..Display_settings.search_all_query}
|
2022-08-29 17:56:27 +00:00
|
|
|
table.insert(column, Display_settings.search_all_pane)
|
|
|
|
add_column_to_right_of_cursor(column)
|
|
|
|
end
|
|
|
|
|
2022-08-29 22:26:39 +00:00
|
|
|
function resume_search_all()
|
|
|
|
-- make a little more progress towards searching the whole disk
|
|
|
|
if Display_settings.search_all_state == nil then
|
|
|
|
Display_settings.search_all_progress_indicator = 'initialized top-level files'
|
|
|
|
Display_settings.search_all_state = {
|
2023-09-08 21:16:50 +00:00
|
|
|
top_level_files = App.files(Directory),
|
2022-08-29 22:26:39 +00:00
|
|
|
top_level_file_index = 1,
|
|
|
|
}
|
|
|
|
elseif Display_settings.search_all_state.top_level_file_index then
|
|
|
|
local current_filename = Display_settings.search_all_state.top_level_files[Display_settings.search_all_state.top_level_file_index]
|
|
|
|
Display_settings.search_all_progress_indicator = current_filename
|
2023-09-08 21:16:50 +00:00
|
|
|
local info = nativefs.getInfo(Directory..current_filename)
|
2022-08-29 22:26:39 +00:00
|
|
|
if info.type == 'file' then
|
2022-08-29 22:51:28 +00:00
|
|
|
search_in_file(current_filename)
|
2022-08-29 22:26:39 +00:00
|
|
|
end
|
|
|
|
Display_settings.search_all_state.top_level_file_index = Display_settings.search_all_state.top_level_file_index+1
|
|
|
|
if Display_settings.search_all_state.top_level_file_index > #Display_settings.search_all_state.top_level_files then
|
|
|
|
Display_settings.search_all_state.top_level_file_index = nil
|
|
|
|
Display_settings.search_all_state.top_level_files = nil
|
|
|
|
Display_settings.search_all_state.time = os.time()
|
|
|
|
Display_settings.search_all_state.year = os.date('%Y', Display_settings.search_all_state.time)
|
|
|
|
Display_settings.search_all_state.date = os.date('%Y/%m/%d/', Display_settings.search_all_state.time)
|
|
|
|
end
|
|
|
|
elseif Display_settings.search_all_state.date then
|
|
|
|
-- search one day's directory per frame
|
|
|
|
-- stop when a whole year is missing
|
|
|
|
Display_settings.search_all_progress_indicator = Display_settings.search_all_state.date
|
|
|
|
local old_year = Display_settings.search_all_state.year
|
|
|
|
local date_dir = Directory..Display_settings.search_all_state.date
|
2023-09-08 21:16:50 +00:00
|
|
|
local info = nativefs.getInfo(date_dir)
|
2022-08-29 22:26:39 +00:00
|
|
|
if info then
|
|
|
|
if info.type == 'directory' then
|
2023-09-08 21:16:50 +00:00
|
|
|
local filenames = App.files(date_dir)
|
2022-08-29 22:51:28 +00:00
|
|
|
for _,filename in ipairs(filenames) do
|
2022-09-01 17:34:30 +00:00
|
|
|
-- hack: to speed up search, only search files created/managed by Pensieve
|
|
|
|
-- I often have other stuff here that's a lot larger (email).
|
|
|
|
if filename:match('^%d%d%-%d%d%-%d%d$') then
|
|
|
|
--? print(date_dir..filename)
|
|
|
|
search_in_file(Display_settings.search_all_state.date..filename)
|
|
|
|
end
|
2022-08-29 22:26:39 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
Display_settings.search_all_state.time = Display_settings.search_all_state.time - 24*60*60
|
|
|
|
Display_settings.search_all_state.year = os.date('%Y', Display_settings.search_all_state.time)
|
|
|
|
Display_settings.search_all_state.date = os.date('%Y/%m/%d/', Display_settings.search_all_state.time)
|
|
|
|
if old_year ~= Display_settings.search_all_state.year then
|
2023-09-08 21:16:50 +00:00
|
|
|
local previous_year_info = nativefs.getInfo(Directory..Display_settings.search_all_state.year)
|
2022-08-29 22:26:39 +00:00
|
|
|
if previous_year_info == nil then
|
|
|
|
Display_settings.search_all_state = nil
|
|
|
|
Display_settings.mode = 'normal'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
assert(false, 'error in search state machine')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-08-29 22:51:28 +00:00
|
|
|
function search_in_file(filename)
|
2022-08-29 23:30:46 +00:00
|
|
|
--? print('searching '..filename..' for '..Display_settings.search_all_query)
|
2023-09-08 21:16:50 +00:00
|
|
|
local f = App.open_for_reading(Directory..filename)
|
|
|
|
local contents = f:read()
|
2022-08-29 22:51:28 +00:00
|
|
|
if contents == nil then
|
|
|
|
error('no contents in '..filename)
|
|
|
|
end
|
2022-08-29 23:40:06 +00:00
|
|
|
if match_all(contents, Display_settings.search_all_terms) then
|
2022-08-29 23:30:46 +00:00
|
|
|
local id = id_for_search_all_pane(Display_settings.search_all_query)
|
2022-08-29 22:51:28 +00:00
|
|
|
local outfilename = Directory..id
|
2023-09-08 21:16:50 +00:00
|
|
|
local success, errmsg = append_to_file(outfilename, '[['..filename..']]\n')
|
2022-08-29 22:51:28 +00:00
|
|
|
if not success then error(errmsg) end
|
2022-08-29 23:40:06 +00:00
|
|
|
local index = 0
|
|
|
|
while true do
|
|
|
|
index = find_any(contents, Display_settings.search_all_terms, index+1)
|
|
|
|
if index == nil then
|
|
|
|
break
|
|
|
|
end
|
2022-09-02 16:28:54 +00:00
|
|
|
local start_offset = find_previous_byte(contents, '\n', index)
|
|
|
|
local end_offset = contents:find('\n', index, --[[literal]] true)
|
|
|
|
local snippet = contents:sub(start_offset, end_offset)
|
2023-09-08 21:16:50 +00:00
|
|
|
local success, errmsg = append_to_file(outfilename, '...'..snippet..'...\n\n')
|
2022-08-29 23:27:33 +00:00
|
|
|
if not success then error(errmsg) end
|
|
|
|
end
|
2022-11-23 06:22:38 +00:00
|
|
|
load_from_disk(Display_settings.search_all_pane)
|
2022-08-29 22:51:28 +00:00
|
|
|
Text.redraw_all(Display_settings.search_all_pane)
|
|
|
|
refresh_pane_height(Display_settings.search_all_pane)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-09-02 16:28:54 +00:00
|
|
|
function find_previous_byte(s, b, index)
|
|
|
|
while index > 1 do
|
|
|
|
if s:sub(index, index) == b then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
index = index-1
|
|
|
|
end
|
|
|
|
return index
|
|
|
|
end
|
|
|
|
|
2022-08-29 22:26:39 +00:00
|
|
|
function interrupt_search_all()
|
|
|
|
if Display_settings.search_all_state then
|
2022-08-29 23:30:46 +00:00
|
|
|
local id = id_for_search_all_pane(Display_settings.search_all_query)
|
2022-08-29 22:26:39 +00:00
|
|
|
local outfilename = Directory..id
|
2023-09-08 21:16:50 +00:00
|
|
|
local success, errmsg = append_to_file(outfilename, 'interrupted at '..Display_settings.search_all_progress_indicator..'\n')
|
2022-08-29 22:26:39 +00:00
|
|
|
if not success then error(errmsg) end
|
2022-11-23 06:22:38 +00:00
|
|
|
load_from_disk(Display_settings.search_all_pane)
|
2022-08-29 22:26:39 +00:00
|
|
|
Text.redraw_all(Display_settings.search_all_pane)
|
|
|
|
end
|
|
|
|
Display_settings.search_all_state = nil
|
|
|
|
Display_settings.mode = 'normal'
|
|
|
|
end
|
|
|
|
|
2022-08-29 23:30:46 +00:00
|
|
|
function populate_search_all_column(column, search_all_query)
|
|
|
|
local id = id_for_search_all_pane(search_all_query)
|
2022-08-29 22:26:39 +00:00
|
|
|
table.insert(column, load_pane(id))
|
|
|
|
end
|
|
|
|
|
2022-08-03 04:31:28 +00:00
|
|
|
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 = {}
|
2022-08-08 18:02:46 +00:00
|
|
|
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
|
2022-08-08 18:02:46 +00:00
|
|
|
-- something's moving us around
|
|
|
|
Display_settings.x = old_viewport.x
|
|
|
|
Display_settings.y = old_viewport.y
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-08-03 04:31:28 +00:00
|
|
|
end
|
|
|
|
|
2022-08-02 20:22:04 +00:00
|
|
|
function command.add_note(rel)
|
2022-07-31 05:44:37 +00:00
|
|
|
if rel == nil then
|
|
|
|
rel = 'next'
|
|
|
|
end
|
2022-08-11 17:07:58 +00:00
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-07-31 05:44:37 +00:00
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
2022-08-11 17:07:58 +00:00
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2023-03-10 21:11:54 +00:00
|
|
|
if Links[pane.id][rel] and not array.find(Non_unique_links, rel) then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error(('%s already has a %s note'):format(pane.id, rel))
|
2022-08-02 20:30:43 +00:00
|
|
|
return
|
|
|
|
end
|
2023-05-09 07:21:55 +00:00
|
|
|
stop_editing_all() -- save any edits
|
2023-03-21 05:52:16 +00:00
|
|
|
if #Surface[Cursor_pane.col] == 1 and not array.find(Non_unique_links, rel) then
|
|
|
|
-- column has a single note; turn it into unroll
|
|
|
|
Surface[Cursor_pane.col] = {name=('%s from %s'):format(rel, pane.id)}
|
|
|
|
populate_unroll_column(Surface[Cursor_pane.col], pane.id, rel)
|
|
|
|
end
|
2022-08-03 01:40:13 +00:00
|
|
|
local new_pane = new_pane()
|
|
|
|
new_pane.editable = true
|
2022-10-29 20:50:00 +00:00
|
|
|
-- connect up links
|
2023-03-10 21:11:54 +00:00
|
|
|
add_link(pane.id, rel, new_pane.id)
|
|
|
|
add_link(new_pane.id, Opposite[rel], pane.id)
|
2022-11-24 17:55:27 +00:00
|
|
|
if string.match(Surface[Cursor_pane.col].name, rel..' from %S+') then
|
2022-08-31 23:26:47 +00:00
|
|
|
-- we're unrolling along the same rel; just append to it
|
2022-08-31 23:19:48 +00:00
|
|
|
-- (we couldn't be inserting in the middle if we didn't return earlier in
|
|
|
|
-- the function)
|
|
|
|
table.insert(Surface[Cursor_pane.col], new_pane)
|
|
|
|
Cursor_pane.row = #Surface[Cursor_pane.col]
|
|
|
|
add_title(new_pane, ('%d/%d'):format(Cursor_pane.row, Cursor_pane.row))
|
|
|
|
refresh_pane_height(pane) -- just in case this is the first link
|
|
|
|
bring_cursor_column_on_screen()
|
|
|
|
bring_cursor_of_cursor_pane_in_view('down')
|
|
|
|
else
|
|
|
|
local column = {name=new_pane.id}
|
|
|
|
table.insert(column, new_pane)
|
|
|
|
add_column_to_right_of_cursor(column)
|
|
|
|
refresh_pane_height(pane) -- just in case this is the first link
|
|
|
|
bring_cursor_of_cursor_pane_in_view('up')
|
|
|
|
end
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-07-31 05:44:37 +00:00
|
|
|
end
|
|
|
|
|
2022-08-30 00:13:36 +00:00
|
|
|
function command.grab()
|
|
|
|
if Cursor_pane.col >= 1 then
|
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
|
|
if pane then
|
|
|
|
Grab_pane = pane
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function command.ungrab()
|
|
|
|
Grab_pane = nil
|
|
|
|
end
|
|
|
|
|
2022-08-30 00:20:01 +00:00
|
|
|
function command.link(rel)
|
|
|
|
if Grab_pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('link: needs something to be in the secondary "grab" cursor but found nothing')
|
2022-08-30 00:20:01 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
if rel == nil then
|
|
|
|
rel = 'next'
|
|
|
|
end
|
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-30 00:20:01 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-30 00:20:01 +00:00
|
|
|
return
|
|
|
|
end
|
2023-03-10 21:11:54 +00:00
|
|
|
if not can_add_link(pane.id, rel, Grab_pane.id) then
|
2022-08-30 00:20:01 +00:00
|
|
|
return
|
|
|
|
end
|
2023-03-10 21:11:54 +00:00
|
|
|
if not can_add_link(Grab_pane.id, Opposite[rel], pane.id) then
|
2022-10-29 20:50:00 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
-- connect up links
|
2023-03-10 21:11:54 +00:00
|
|
|
add_link(pane.id, rel, Grab_pane.id)
|
|
|
|
add_link(Grab_pane.id, Opposite[rel], pane.id)
|
2022-08-30 00:59:10 +00:00
|
|
|
schedule_save(pane)
|
|
|
|
stop_editing(pane)
|
2022-08-30 00:20:01 +00:00
|
|
|
refresh_pane_height(pane) -- just in case this is the first link
|
|
|
|
refresh_pane_height(Grab_pane) -- just in case this is the first link
|
|
|
|
Grab_pane = nil
|
|
|
|
plan_draw()
|
|
|
|
end
|
|
|
|
|
2023-03-10 21:11:54 +00:00
|
|
|
-- Can we add a link called 'rel' from src to target?
|
|
|
|
function can_add_link(src, rel, target)
|
2023-03-17 03:21:31 +00:00
|
|
|
print_and_log(('checking before adding link labeled %s from %s to %s'):format(rel, src, target))
|
2023-03-10 21:11:54 +00:00
|
|
|
if array.find(Non_unique_links, rel) then
|
2022-10-29 20:50:00 +00:00
|
|
|
-- check if already present
|
2022-11-23 05:49:11 +00:00
|
|
|
if Links[src][rel] then
|
|
|
|
for _,id in ipairs(Links[src][rel]) do
|
2022-11-12 01:15:18 +00:00
|
|
|
if id == target then
|
|
|
|
add_error(('%s is already a %s of %s'):format(target, rel, src))
|
|
|
|
return false
|
|
|
|
end
|
2022-10-29 20:50:00 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- check for conflict
|
2022-11-23 05:49:11 +00:00
|
|
|
if Links[src][rel] then
|
2022-10-29 20:50:00 +00:00
|
|
|
add_error(('%s already has a %s note'):format(src, rel))
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2023-03-10 21:11:54 +00:00
|
|
|
function add_link(src, rel, target)
|
2023-03-17 03:21:31 +00:00
|
|
|
print_and_log(('adding link labeled %s from %s to %s'):format(rel, src, target))
|
2023-03-10 21:11:54 +00:00
|
|
|
if array.find(Non_unique_links, rel) then
|
2022-11-23 05:49:11 +00:00
|
|
|
if Links[src][rel] == nil then
|
|
|
|
Links[src][rel] = {target}
|
2022-10-29 20:50:00 +00:00
|
|
|
else
|
2022-11-23 05:49:11 +00:00
|
|
|
table.insert(Links[src][rel], target)
|
2022-10-29 20:50:00 +00:00
|
|
|
end
|
|
|
|
else
|
2023-03-17 03:21:31 +00:00
|
|
|
print_and_log('unique link')
|
2022-11-23 05:49:11 +00:00
|
|
|
Links[src][rel] = target
|
2022-10-29 20:50:00 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-17 03:21:31 +00:00
|
|
|
function links_state(id)
|
|
|
|
local result = {}
|
|
|
|
table.insert(result, id..' -- ')
|
|
|
|
if Links[id] then
|
|
|
|
for rel,val in pairs(Links[id]) do
|
|
|
|
table.insert(result, rel)
|
|
|
|
table.insert(result, ':')
|
|
|
|
if type(val) == 'table' then
|
|
|
|
table.insert(result, '[')
|
|
|
|
for _,dest in ipairs(val) do
|
|
|
|
table.insert(result, dest)
|
|
|
|
table.insert(result, ' ')
|
|
|
|
end
|
|
|
|
table.insert(result, ']')
|
|
|
|
else
|
|
|
|
table.insert(result, val)
|
|
|
|
end
|
|
|
|
table.insert(result, '|')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return table.concat(result)
|
|
|
|
end
|
|
|
|
|
2023-03-10 21:11:54 +00:00
|
|
|
function remove_link(src, rel, target)
|
2022-11-12 06:32:57 +00:00
|
|
|
--? print(('removing %s of %s, to %s'):format(rel, src, target))
|
2023-03-10 21:11:54 +00:00
|
|
|
if array.find(Non_unique_links, rel) then
|
2022-11-12 06:32:57 +00:00
|
|
|
--? print(('%s is non-unique'):format(rel))
|
2022-11-23 05:49:11 +00:00
|
|
|
local arr = Links[src][rel]
|
2022-10-29 23:06:41 +00:00
|
|
|
assert(arr)
|
|
|
|
assert(type(arr) == 'table')
|
|
|
|
local pos = array.find(arr, target)
|
2022-11-12 06:32:57 +00:00
|
|
|
--? print(('contains %s at index %s'):format(target, pos))
|
2022-10-29 23:06:41 +00:00
|
|
|
assert(pos)
|
2022-11-12 06:32:57 +00:00
|
|
|
table.remove(arr, pos)
|
|
|
|
if #arr == 0 then
|
2022-11-23 05:49:11 +00:00
|
|
|
Links[src][rel] = nil
|
2022-11-12 06:32:57 +00:00
|
|
|
end
|
2022-10-29 23:06:41 +00:00
|
|
|
else
|
2022-11-23 05:49:11 +00:00
|
|
|
assert(Links[src][rel] == target)
|
|
|
|
Links[src][rel] = nil
|
2022-10-29 23:06:41 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-11-12 05:43:34 +00:00
|
|
|
function command.copy_id_to_clipboard()
|
|
|
|
if Cursor_pane.col < 1 then
|
|
|
|
add_error('no current note')
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
|
|
if pane == nil then
|
|
|
|
add_error('no current note')
|
|
|
|
return
|
|
|
|
end
|
2023-09-01 03:01:44 +00:00
|
|
|
App.set_clipboard(pane.id)
|
2022-11-12 05:43:34 +00:00
|
|
|
end
|
|
|
|
|
2022-10-18 16:53:33 +00:00
|
|
|
function command.rename_link(args)
|
|
|
|
local from, to = args:match('(%w+)%s+to%s+(%w+)')
|
|
|
|
if from == nil then
|
|
|
|
from, to = args:match('(%w+)%s+(%w+)')
|
|
|
|
end
|
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-10-18 16:53:33 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-10-18 16:53:33 +00:00
|
|
|
return
|
|
|
|
end
|
2022-11-12 06:32:57 +00:00
|
|
|
local target
|
2023-03-10 21:11:54 +00:00
|
|
|
if array.find(Non_unique_links, from) then
|
2022-11-23 05:49:11 +00:00
|
|
|
if #Links[pane.id][from] <= 0 then
|
2022-11-12 06:32:57 +00:00
|
|
|
add_error(('no %s links'):format(from))
|
|
|
|
return
|
|
|
|
end
|
2022-11-23 05:49:11 +00:00
|
|
|
if #Links[pane.id][from] > 1 then
|
2022-11-12 06:32:57 +00:00
|
|
|
add_error(('multiple %s links; not sure which one you mean'):format(from))
|
|
|
|
return
|
|
|
|
end
|
2022-11-23 05:49:11 +00:00
|
|
|
target = Links[pane.id][from][1]
|
2022-11-12 06:32:57 +00:00
|
|
|
else
|
2022-11-23 05:49:11 +00:00
|
|
|
target = Links[pane.id][from]
|
2022-10-29 23:06:41 +00:00
|
|
|
end
|
2023-03-10 21:09:38 +00:00
|
|
|
print_and_log(('renaming link %s of %s to %s'):format(from, pane, to))
|
2023-03-10 21:11:54 +00:00
|
|
|
if Links[pane.id][to] and not array.find(Non_unique_links, to) then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error(('%s already has a %s note'):format(pane.id, to))
|
2022-10-18 16:53:33 +00:00
|
|
|
return
|
|
|
|
end
|
2022-10-29 23:06:41 +00:00
|
|
|
-- forwards direction
|
2023-03-10 21:11:54 +00:00
|
|
|
remove_link(pane.id, from, target)
|
|
|
|
add_link(pane.id, to, target)
|
2022-10-18 16:53:33 +00:00
|
|
|
if Opposite[to] ~= Opposite[from] then
|
2023-03-10 21:11:54 +00:00
|
|
|
remove_link(target, Opposite[from], pane.id)
|
|
|
|
add_link(target, Opposite[to], pane.id)
|
2022-10-18 16:53:33 +00:00
|
|
|
end
|
|
|
|
schedule_save(pane)
|
|
|
|
stop_editing(pane)
|
|
|
|
end
|
|
|
|
|
|
|
|
function command.clear_link(rel)
|
|
|
|
if rel == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('specify a link to clear')
|
|
|
|
return
|
2022-10-18 16:53:33 +00:00
|
|
|
end
|
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-10-18 16:53:33 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-10-18 16:53:33 +00:00
|
|
|
return
|
|
|
|
end
|
2022-11-23 05:49:11 +00:00
|
|
|
if Links[pane.id][rel] == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error(('%s has no %s note'):format(pane.id, rel))
|
2022-10-18 16:53:33 +00:00
|
|
|
return
|
|
|
|
end
|
2022-11-23 05:49:11 +00:00
|
|
|
if type(Links[pane.id][rel]) == 'table' then
|
2023-05-30 16:09:17 +00:00
|
|
|
if #Links[pane.id][rel] > 1 then
|
|
|
|
add_error(('%s is a non-unique link; clearing all %d such links'):format(rel, #Links[pane.id][rel]))
|
|
|
|
end
|
2022-10-29 23:11:33 +00:00
|
|
|
end
|
2023-03-09 02:23:37 +00:00
|
|
|
print_and_log(('clearing link %s of %s (used to point to %s)'):format(rel, pane.id, Links[pane.id][rel]))
|
2022-11-23 05:49:11 +00:00
|
|
|
Links[pane.id][rel] = nil
|
2022-10-18 16:53:33 +00:00
|
|
|
schedule_save(pane)
|
|
|
|
stop_editing(pane)
|
|
|
|
refresh_pane_height(pane) -- just in case this is the final 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
|
2022-08-11 17:07:58 +00:00
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-08-02 20:35:04 +00:00
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
2022-08-11 17:07:58 +00:00
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-11-23 05:49:11 +00:00
|
|
|
if Links[pane.id][rel] == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error(('%s has no %s note'):format(pane.id, rel))
|
2022-08-02 20:35:04 +00:00
|
|
|
return
|
|
|
|
end
|
2023-03-10 21:11:54 +00:00
|
|
|
if array.find(Non_unique_links, rel) then
|
2022-10-29 20:50:00 +00:00
|
|
|
local column = {name=(('%s of %s'):format(rel, pane.id))}
|
|
|
|
populate_step_column(column, pane.id, rel)
|
|
|
|
add_column_to_right_of_cursor(column)
|
|
|
|
plan_draw()
|
|
|
|
else
|
2022-11-23 05:49:11 +00:00
|
|
|
command.open_file_in_next_column(Links[pane.id][rel])
|
2022-10-29 20:50:00 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function populate_step_column(column, start_id, rel)
|
2022-11-23 06:22:38 +00:00
|
|
|
if Links[start_id] == nil then
|
|
|
|
Links[start_id] = load_links(start_id)
|
|
|
|
end
|
2023-05-30 16:09:52 +00:00
|
|
|
if Links[start_id][rel] == nil then
|
|
|
|
return
|
|
|
|
end
|
2022-11-23 05:49:11 +00:00
|
|
|
local n = #Links[start_id][rel]
|
|
|
|
for i, id in ipairs(Links[start_id][rel]) do
|
2022-10-29 20:50:00 +00:00
|
|
|
local pane = load_pane(id)
|
|
|
|
add_title(pane, ('%d/%d'):format(i, n))
|
|
|
|
table.insert(column, pane)
|
|
|
|
end
|
2022-08-02 20:35:04 +00:00
|
|
|
end
|
|
|
|
|
2022-08-03 04:10:48 +00:00
|
|
|
function command.extract(rel)
|
2022-08-11 17:07:58 +00:00
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-08-03 04:10:48 +00:00
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
2022-08-11 17:07:58 +00:00
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-08-09 04:20:03 +00:00
|
|
|
command.open_file_in_next_column(pane.id)
|
2022-08-03 04:10:48 +00:00
|
|
|
end
|
|
|
|
|
2022-08-02 22:46:22 +00:00
|
|
|
function command.unroll(rel)
|
|
|
|
if rel == nil then
|
|
|
|
rel = 'next'
|
|
|
|
end
|
2022-08-11 17:07:58 +00:00
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-08-02 22:46:22 +00:00
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
2022-08-11 17:07:58 +00:00
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-08-05 16:50:22 +00:00
|
|
|
local column = {name=('%s from %s'):format(rel, pane.id)}
|
|
|
|
populate_unroll_column(column, pane.id, rel)
|
|
|
|
if #column == 0 then
|
2022-08-03 17:55:32 +00:00
|
|
|
return
|
|
|
|
end
|
2022-08-03 01:15:20 +00:00
|
|
|
if #Surface[Cursor_pane.col] == 1 then
|
|
|
|
assert(Cursor_pane.row == 1)
|
2023-05-09 07:21:55 +00:00
|
|
|
stop_editing(pane) -- save any edits before we blow it away
|
2022-08-03 01:15:20 +00:00
|
|
|
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()
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-08-02 22:46:22 +00:00
|
|
|
end
|
|
|
|
|
2022-08-05 16:50:22 +00:00
|
|
|
function populate_unroll_column(column, id, rel)
|
2022-10-29 20:50:00 +00:00
|
|
|
if Opposite[rel] == rel then
|
|
|
|
add_error(("link type %s is undirected and can't be unrolled"):format(rel))
|
|
|
|
return
|
|
|
|
end
|
2023-03-10 21:11:54 +00:00
|
|
|
if array.find(Non_unique_links, rel) then
|
2022-10-29 20:50:00 +00:00
|
|
|
add_error(("link type %s is not unique and can't be unrolled"):format(rel))
|
|
|
|
return
|
2022-10-10 06:43:07 +00:00
|
|
|
end
|
2022-08-05 16:50:22 +00:00
|
|
|
-- back out to start of chain
|
2022-11-23 06:22:38 +00:00
|
|
|
while true do
|
|
|
|
if Links[id] == nil then
|
|
|
|
Links[id] = load_links(id)
|
|
|
|
end
|
|
|
|
if Links[id][Opposite[rel]] == nil then
|
|
|
|
break
|
|
|
|
end
|
2022-11-23 05:49:11 +00:00
|
|
|
id = Links[id][Opposite[rel]]
|
2022-08-03 22:45:45 +00:00
|
|
|
end
|
2022-08-05 16:50:22 +00:00
|
|
|
-- unroll from start
|
2022-08-02 23:00:18 +00:00
|
|
|
local curr = id
|
2022-08-05 20:50:59 +00:00
|
|
|
local n=0
|
|
|
|
while curr do
|
2022-11-23 06:22:38 +00:00
|
|
|
if Links[curr] == nil then
|
|
|
|
Links[curr] = load_links(curr)
|
|
|
|
end
|
2022-11-23 05:49:11 +00:00
|
|
|
curr = Links[curr][rel]
|
2022-08-05 20:50:59 +00:00
|
|
|
n = n+1
|
|
|
|
end
|
|
|
|
curr = id
|
|
|
|
local i=1
|
2022-08-02 23:00:18 +00:00
|
|
|
while curr do
|
2022-08-06 19:18:33 +00:00
|
|
|
local pane = load_pane(curr)
|
2022-08-05 20:50:59 +00:00
|
|
|
add_title(pane, ('%d/%d'):format(i, n))
|
2022-08-02 23:00:18 +00:00
|
|
|
table.insert(column, pane)
|
2022-11-23 05:49:11 +00:00
|
|
|
curr = Links[curr][rel]
|
2022-08-05 20:50:59 +00:00
|
|
|
i = i+1
|
2022-08-02 23:00:18 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-08-03 01:26:26 +00:00
|
|
|
function command.append_note(rel)
|
|
|
|
if rel == nil then
|
|
|
|
rel = 'next'
|
|
|
|
end
|
2022-08-11 17:07:58 +00:00
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-08-03 01:26:26 +00:00
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
2022-08-11 17:07:58 +00:00
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2023-03-10 21:11:54 +00:00
|
|
|
if array.find(Non_unique_links, rel) then
|
2022-10-29 21:05:09 +00:00
|
|
|
add_error(('%s is non-unique; which direction should I append?'):format(rel))
|
|
|
|
return
|
|
|
|
end
|
2022-08-03 01:26:26 +00:00
|
|
|
local curr_id = pane.id
|
|
|
|
while true do
|
2022-11-23 06:22:38 +00:00
|
|
|
if Links[curr_id] == nil then
|
|
|
|
Links[curr_id] = load_links(curr_id)
|
|
|
|
end
|
2022-11-23 05:49:11 +00:00
|
|
|
local next_id = Links[curr_id][rel]
|
2022-08-03 01:26:26 +00:00
|
|
|
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-11-23 05:49:11 +00:00
|
|
|
Links[curr_id][rel] = new_pane.id
|
|
|
|
Links[new_pane.id][Opposite[rel]] = curr_id
|
2022-08-31 23:26:47 +00:00
|
|
|
if #Surface[Cursor_pane.col] == 1 then
|
|
|
|
-- column has a single note; turn it into unroll
|
|
|
|
Surface[Cursor_pane.col] = {name=('%s from %s'):format(rel, pane.id)}
|
|
|
|
populate_unroll_column(Surface[Cursor_pane.col], pane.id, rel) -- invalidates new_pane
|
|
|
|
Cursor_pane.row = #Surface[Cursor_pane.col]
|
|
|
|
Surface[Cursor_pane.col][Cursor_pane.row].editable = true
|
|
|
|
bring_cursor_column_on_screen()
|
|
|
|
bring_cursor_of_cursor_pane_in_view('down')
|
|
|
|
elseif string.match(Surface[Cursor_pane.col].name, rel..' from %S+') then
|
|
|
|
-- we're unrolling along the same rel; just append to it
|
|
|
|
-- (we couldn't be inserting in the middle if we didn't return earlier in
|
|
|
|
-- the function)
|
|
|
|
table.insert(Surface[Cursor_pane.col], new_pane)
|
|
|
|
Cursor_pane.row = #Surface[Cursor_pane.col]
|
|
|
|
add_title(new_pane, ('%d/%d'):format(Cursor_pane.row, Cursor_pane.row))
|
|
|
|
refresh_pane_height(pane) -- just in case this is the first link
|
|
|
|
bring_cursor_column_on_screen()
|
|
|
|
bring_cursor_of_cursor_pane_in_view('down')
|
|
|
|
else
|
|
|
|
local column = {name=new_pane.id}
|
|
|
|
table.insert(column, new_pane)
|
|
|
|
add_column_to_right_of_cursor(column)
|
|
|
|
bring_cursor_of_cursor_pane_in_view('up')
|
|
|
|
end
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-08-03 01:26:26 +00:00
|
|
|
end
|
|
|
|
|
2022-08-05 15:56:31 +00:00
|
|
|
function command.neighbors()
|
2022-08-11 17:07:58 +00:00
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-08-05 15:56:31 +00:00
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
2022-08-11 17:07:58 +00:00
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2023-05-09 07:21:55 +00:00
|
|
|
stop_editing(pane) -- save any edits before we blow it away
|
2022-08-05 15:56:31 +00:00
|
|
|
local column = {name=('neighbors of %s'):format(pane.id)}
|
2022-08-05 16:50:22 +00:00
|
|
|
populate_neighbors_column(column, pane.id)
|
2022-08-05 15:56:31 +00:00
|
|
|
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()
|
2022-08-07 23:14:14 +00:00
|
|
|
plan_draw()
|
2022-08-05 15:56:31 +00:00
|
|
|
end
|
|
|
|
|
2022-08-05 16:50:22 +00:00
|
|
|
function populate_neighbors_column(column, start_id)
|
2022-08-06 19:18:33 +00:00
|
|
|
table.insert(column, load_pane(start_id))
|
2022-11-23 05:49:11 +00:00
|
|
|
for rel,x in pairs(Links[start_id]) do
|
2022-10-29 20:57:20 +00:00
|
|
|
process_all_links(x, function(target)
|
|
|
|
local pane = load_pane(target)
|
|
|
|
add_title(pane, rel)
|
|
|
|
table.insert(column, pane)
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- links might contain either a single target or a list of them
|
|
|
|
function process_all_links(x, fn)
|
|
|
|
if type(x) == 'string' then
|
|
|
|
fn(x)
|
|
|
|
else
|
|
|
|
for _,target in ipairs(x) do
|
|
|
|
fn(target)
|
|
|
|
end
|
2022-08-05 16:50:22 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-08-03 04:49:51 +00:00
|
|
|
function command.delete_note()
|
2022-08-11 17:07:58 +00:00
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-08-03 04:49:51 +00:00
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
2022-08-11 17:07:58 +00:00
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:07:58 +00:00
|
|
|
return
|
|
|
|
end
|
2022-11-23 05:49:11 +00:00
|
|
|
if #Links[pane.id] > 0 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('pane has links; giving up')
|
2022-08-03 04:49:51 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
-- TODO: test harness support for file ops below
|
|
|
|
if App.run_tests then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
-- delete from disk
|
2023-09-08 21:16:50 +00:00
|
|
|
App.remove(Directory..pane.id)
|
2022-08-03 04:49:51 +00:00
|
|
|
-- delete from recently modified
|
|
|
|
local filenames = {}
|
2023-09-08 21:16:50 +00:00
|
|
|
local f = App.open_for_reading(Directory..'recent')
|
|
|
|
for line in f:lines() do
|
2022-08-03 04:49:51 +00:00
|
|
|
if line ~= pane.id then
|
|
|
|
table.insert(filenames, line)
|
|
|
|
end
|
|
|
|
end
|
2023-09-08 21:16:50 +00:00
|
|
|
local f = App.open_for_writing(Directory..'recent')
|
2022-08-03 04:49:51 +00:00
|
|
|
for _,filename in ipairs(filenames) do
|
|
|
|
f:write(filename)
|
|
|
|
f:write('\n')
|
|
|
|
end
|
|
|
|
f:close()
|
2022-08-05 21:43:35 +00:00
|
|
|
-- 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
|
2022-08-03 04:49:51 +00:00
|
|
|
--
|
2022-08-08 17:33:47 +00:00
|
|
|
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
|
|
|
|
--
|
2022-08-03 04:49:51 +00:00
|
|
|
command.reload_all()
|
|
|
|
end
|
|
|
|
|
2022-11-16 07:38:08 +00:00
|
|
|
function command.snapshot_memory()
|
|
|
|
-- load library on demand
|
|
|
|
if mri == nil then
|
|
|
|
mri = require('MemoryReferenceInfo')
|
|
|
|
end
|
|
|
|
collectgarbage('collect')
|
|
|
|
print(collectgarbage('count'))
|
|
|
|
mri.m_cMethods.DumpMemorySnapshot('./', 'mem', -1)
|
|
|
|
add_error('snapshot successfully dumped')
|
|
|
|
end
|
|
|
|
|
2022-08-11 17:49:30 +00:00
|
|
|
function command.send_key_to_current_pane(chord, key)
|
|
|
|
if Cursor_pane.col < 1 then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:49:30 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
local pane = Surface[Cursor_pane.col][Cursor_pane.row]
|
|
|
|
if pane == nil then
|
2022-10-27 06:13:22 +00:00
|
|
|
add_error('no current note')
|
2022-08-11 17:49:30 +00:00
|
|
|
return
|
|
|
|
end
|
2023-01-07 01:46:28 +00:00
|
|
|
edit.keychord_press(pane, chord, key)
|
2022-08-11 17:49:30 +00:00
|
|
|
end
|
|
|
|
|
2022-08-03 01:40:13 +00:00
|
|
|
-- return a new pane with a unique filename
|
|
|
|
function new_pane()
|
2023-03-04 18:03:31 +00:00
|
|
|
local t = os.time()
|
|
|
|
local id = os.date('%Y/%m/%d/%H-%M-%S', t)
|
2023-03-09 02:23:37 +00:00
|
|
|
print_and_log('new_pane: creating directory '..Directory..dirname(id))
|
2023-09-08 21:16:50 +00:00
|
|
|
local status = App.mkdir(Directory..dirname(id))
|
2022-08-03 01:40:13 +00:00
|
|
|
assert(status)
|
2022-11-23 05:49:11 +00:00
|
|
|
Links[id] = {}
|
2023-03-04 18:03:31 +00:00
|
|
|
local pane = load_pane(id)
|
2023-08-31 06:33:18 +00:00
|
|
|
if not file_exists(pane.filename) then
|
2023-03-04 22:32:36 +00:00
|
|
|
table.insert(pane.lines, 1, {data=os.date('%Y-%m-%d %H:%M:%S %Z', t), mode='text'})
|
2023-03-04 18:03:31 +00:00
|
|
|
pane.cursor1 = {line=2, pos=1}
|
|
|
|
Text.redraw_all(pane)
|
|
|
|
end
|
|
|
|
return pane
|
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)
|
2022-12-05 16:45:56 +00:00
|
|
|
if col_sx < Display_settings.x or col_sx > Display_settings.x + App.screen.width - Display_settings.column_width then
|
2022-08-03 01:48:08 +00:00
|
|
|
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
|
|
|
|
|
2022-08-05 14:23:46 +00:00
|
|
|
function emit_links_in_json_in_consistent_order(outfile, 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
|
2022-10-29 05:01:45 +00:00
|
|
|
outfile:write(json.encode(label)..':'..json.encode(links[label]))
|
2022-08-05 14:23:46 +00:00
|
|
|
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
|
2022-10-29 05:01:45 +00:00
|
|
|
outfile:write(json.encode(rel)..':'..json.encode(target))
|
2022-08-05 14:23:46 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
outfile:write('}')
|
|
|
|
end
|
|
|
|
|
2022-08-12 15:17:43 +00:00
|
|
|
function concat_all(dir, files)
|
|
|
|
if dir == '' then return files end
|
|
|
|
for i,file in ipairs(files) do
|
|
|
|
files[i] = dir..file
|
|
|
|
end
|
|
|
|
return files
|
|
|
|
end
|
|
|
|
|
|
|
|
function append(arr, b)
|
|
|
|
for _,x in ipairs(b) do
|
|
|
|
table.insert(arr, x)
|
|
|
|
end
|
|
|
|
end
|
2022-08-29 23:40:06 +00:00
|
|
|
|
|
|
|
function split(s)
|
|
|
|
local result = {}
|
|
|
|
for sub in s:gmatch("%S+") do
|
|
|
|
table.insert(result, sub)
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
|
|
|
function match_all(s, subs)
|
|
|
|
for _,sub in ipairs(subs) do
|
|
|
|
if s:find(sub, 1, --[[literal pattern]] true) == nil then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function find_any(s, subs, start)
|
|
|
|
local result = nil
|
|
|
|
for _,sub in ipairs(subs) do
|
|
|
|
local i = s:find(sub, start, --[[literal pattern]] true)
|
|
|
|
if i then
|
|
|
|
if result == nil then
|
|
|
|
result = i
|
|
|
|
elseif i < result then
|
|
|
|
result = i
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
end
|