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-08-05 14:06:07 +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) ' ,
2023-07-25 17:02:48 +00:00
' left one column (ctrl+left) ' ,
' right one column (ctrl+right) ' ,
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 )
2024-03-02 01:52:16 +00:00
if Display_settings.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
2023-11-18 20:17:17 +00:00
assert ( pane.cursor_y , ' cursor fell off viewport ' )
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 )
2024-01-15 10:20:18 +00:00
local width = Display_settings.font : getWidth ( 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
2024-03-02 01:52:16 +00:00
Display_settings.palette = nil
2022-07-24 04:00:49 +00:00
elseif chord == ' backspace ' then
2024-03-02 01:52:16 +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 )
Display_settings.palette . alternative_index = 1
Display_settings.palette . candidates = candidates ( )
2022-07-24 05:21:00 +00:00
elseif chord == ' tab ' then
-- select top candidate, but don't submit
2024-03-02 01:52:16 +00:00
local p = Display_settings.palette
p.command = command_string ( p.candidates [ p.alternative_index ] )
2022-08-11 19:37:56 +00:00
elseif chord == ' C-v ' then
2024-03-02 01:52:16 +00:00
local p = Display_settings.palette
p.command = p.command .. App.get_clipboard ( )
p.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
2024-03-02 01:52:16 +00:00
local p = Display_settings.palette
local candidates = Display_settings.palette . candidates
if # p.candidates > 0 then
if file_exists ( Directory .. p.candidates [ p.alternative_index ] ) then
command.open_file_in_next_column ( p.candidates [ p.alternative_index ] )
2022-07-24 04:26:35 +00:00
else
2024-03-02 01:52:16 +00:00
run_command ( command_string ( p.candidates [ p.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
2024-03-02 01:52:16 +00:00
run_command_with_args ( p.command )
2022-07-24 04:00:49 +00:00
end
2022-07-27 17:01:32 +00:00
-- forget text for next command
2024-03-02 01:52:16 +00:00
Display_settings.palette = nil
2022-12-04 05:09:08 +00:00
-- clean up some columns if possible
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
2024-03-02 01:52:16 +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
2024-03-02 01:52:16 +00:00
if Display_settings.palette . alternative_index < # Display_settings.palette . candidates then
Display_settings.palette . alternative_index = Display_settings.palette . alternative_index + 1
2022-08-11 05:23:27 +00:00
end
elseif chord == ' left ' then
2024-03-02 01:52:16 +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
2024-03-02 01:52:16 +00:00
if Display_settings.palette . alternative_index <= # Display_settings.palette . candidates - 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
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 }
2024-03-02 01:52:16 +00:00
for i , cmd in ipairs ( Display_settings.palette . candidates ) do
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
2024-01-15 10:20:18 +00:00
x = x + Display_settings.font : getWidth ( 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 )
2024-01-15 10:20:18 +00:00
local width = Display_settings.font : getWidth ( 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 )
2024-03-02 01:52:16 +00:00
love.graphics . print ( Display_settings.palette . command , x , y )
x = x + Display_settings.font : getWidth ( 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 ( )
2024-03-02 01:52:16 +00:00
if Display_settings.palette . command == ' ' then
2022-07-30 22:32:19 +00:00
return candidates
2024-03-02 01:52:16 +00:00
elseif Display_settings.palette . command : sub ( 1 , 1 ) == ' / ' then
2022-07-30 22:32:19 +00:00
return { }
else
2024-03-02 01:52:16 +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
2024-03-02 01:52:16 +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-12-18 20:33:47 +00:00
local info = App.file_info ( 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-12-18 20:33:47 +00:00
info [ file ] = App.file_info ( 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 ( )
2023-07-25 17:02:48 +00:00
elseif cmd == ' left one column ' then
command.left_one_column ( )
elseif cmd == ' right one column ' then
command.right_one_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
2022-09-10 05:43:40 +00:00
-- columns are always filenames. techmeet.love won't ever create filenames
2022-08-11 18:05:38 +00:00
-- 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
2023-11-18 20:17:17 +00:00
assert ( not pane.editable , ' pane already 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 ( )
2023-11-18 20:17:17 +00:00
assert ( Cursor_pane.col >= 1 , ' no current pane ' )
2022-07-29 04:55:01 +00:00
local pane = Surface [ Cursor_pane.col ] [ Cursor_pane.row ]
2023-11-18 20:17:17 +00:00
assert ( pane , ' no current pane ' )
assert ( pane.editable , ' current pane not 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
2023-07-25 17:02:48 +00:00
-- imperfect commands for left/right navigation
-- these can be improved
function command . right_one_column ( )
if Cursor_pane.col >= # Surface then
return
end
Cursor_pane.col = Cursor_pane.col + 1
Cursor_pane.row = 1 -- always bounce back to top of column
Display_settings.y = 0
local xlo = left_edge_sx ( Cursor_pane.col ) - Margin_left - Padding_horizontal
Display_settings.x = math.max ( xlo , Display_settings.x )
local xmax = left_edge_sx ( Cursor_pane.col + 1 ) - App.screen . width
Display_settings.x = math.min ( xmax , Display_settings.x )
Display_settings.x = math.max ( 0 , Display_settings.x )
plan_draw ( )
end
function command . left_one_column ( )
if Cursor_pane.col <= 1 then
return
end
Cursor_pane.col = Cursor_pane.col - 1
Cursor_pane.row = 1 -- always bounce back to top of column
Display_settings.y = 0
local xlo = left_edge_sx ( Cursor_pane.col ) - Margin_left - Padding_horizontal
Display_settings.x = math.min ( xlo , Display_settings.x )
plan_draw ( )
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 ( )
2024-01-15 10:20:18 +00:00
Display_settings.column_width = Display_settings.column_width + 5 * Display_settings.font : getWidth ( ' 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 ( )
2024-01-15 10:20:18 +00:00
Display_settings.column_width = Display_settings.column_width - 5 * Display_settings.font : getWidth ( ' 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
2023-09-08 23:03:28 +00:00
f : close ( )
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 ( )
2023-12-29 22:24:04 +00:00
local result = edit.initialize_state ( 0 , 0 , math.min ( Display_settings.column_width , App.screen . width - Margin_right ) , love.graphics . getFont ( ) , Font_height , Line_height )
2022-08-29 17:56:27 +00:00
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
2023-11-18 20:17:17 +00:00
assert ( Display_settings.search_all_query : sub ( # Display_settings.search_all_query ) == ' " ' , ' you can search for strings in quotes, but only one of them by itself ' )
2022-09-02 16:36:26 +00:00
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
2023-09-08 22:27:28 +00:00
local id = ' search '
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
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
2024-03-02 02:28:00 +00:00
if Display_settings.search_all_progress == nil then
2022-08-29 22:26:39 +00:00
Display_settings.search_all_progress_indicator = ' initialized top-level files '
2024-03-02 02:28:00 +00:00
Display_settings.search_all_progress = {
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 ,
}
2024-03-02 02:28:00 +00:00
elseif Display_settings.search_all_progress . top_level_file_index then
local current_filename = Display_settings.search_all_progress . top_level_files [ Display_settings.search_all_progress . top_level_file_index ]
2022-08-29 22:26:39 +00:00
Display_settings.search_all_progress_indicator = current_filename
2023-09-08 23:03:28 +00:00
if current_filename ~= ' search ' and current_filename ~= ' config ' and current_filename ~= ' recent ' then -- ignore some housekeeping files for pensieve.love
2023-12-18 20:33:47 +00:00
local info = App.file_info ( Directory .. current_filename )
2023-09-08 23:03:28 +00:00
if info.type == ' file ' then
search_in_file ( current_filename )
end
2022-08-29 22:26:39 +00:00
end
2024-03-02 02:28:00 +00:00
Display_settings.search_all_progress . top_level_file_index = Display_settings.search_all_progress . top_level_file_index + 1
if Display_settings.search_all_progress . top_level_file_index > # Display_settings.search_all_progress . top_level_files then
Display_settings.search_all_progress . top_level_file_index = nil
Display_settings.search_all_progress . top_level_files = nil
Display_settings.search_all_progress . time = os.time ( )
Display_settings.search_all_progress . year = os.date ( ' %Y ' , Display_settings.search_all_progress . time )
Display_settings.search_all_progress . date = os.date ( ' %Y/%m/%d/ ' , Display_settings.search_all_progress . time )
2022-08-29 22:26:39 +00:00
end
2024-03-02 02:28:00 +00:00
elseif Display_settings.search_all_progress . date then
2022-08-29 22:26:39 +00:00
-- search one day's directory per frame
-- stop when a whole year is missing
2024-03-02 02:28:00 +00:00
Display_settings.search_all_progress_indicator = Display_settings.search_all_progress . date
local old_year = Display_settings.search_all_progress . year
local date_dir = Directory .. Display_settings.search_all_progress . date
2023-12-18 20:33:47 +00:00
local info = App.file_info ( 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)
2024-03-02 02:28:00 +00:00
search_in_file ( Display_settings.search_all_progress . date .. filename )
2022-09-01 17:34:30 +00:00
end
2022-08-29 22:26:39 +00:00
end
end
end
2024-03-02 02:28:00 +00:00
Display_settings.search_all_progress . time = Display_settings.search_all_progress . time - 24 * 60 * 60
Display_settings.search_all_progress . year = os.date ( ' %Y ' , Display_settings.search_all_progress . time )
Display_settings.search_all_progress . date = os.date ( ' %Y/%m/%d/ ' , Display_settings.search_all_progress . time )
if old_year ~= Display_settings.search_all_progress . year then
local previous_year_info = App.file_info ( Directory .. Display_settings.search_all_progress . year )
2022-08-29 22:26:39 +00:00
if previous_year_info == nil then
2024-03-02 02:28:00 +00:00
Display_settings.search_all_progress = nil
2022-08-29 22:26:39 +00:00
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-09 16:34:44 +00:00
local contents , err = App.read_file ( Directory .. filename )
2023-09-08 23:03:28 +00:00
if err then
error ( err )
end
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
2023-09-08 22:27:28 +00:00
local outfilename = Directory .. ' search '
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 ( )
2024-03-02 02:28:00 +00:00
if Display_settings.search_all_progress then
2023-09-08 22:27:28 +00:00
local outfilename = Directory .. ' search '
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
2024-03-02 02:28:00 +00:00
Display_settings.search_all_progress = nil
2022-08-29 22:26:39 +00:00
Display_settings.mode = ' normal '
end
2022-08-29 23:30:46 +00:00
function populate_search_all_column ( column , search_all_query )
2023-09-08 22:27:28 +00:00
table.insert ( column , load_pane ( ' search ' ) )
2022-08-29 22:26:39 +00:00
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-07-25 17:02:48 +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 )
2023-07-25 17:02:48 +00:00
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 )
2023-03-17 03:21:31 +00:00
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 ]
2023-11-18 20:17:17 +00:00
assert ( arr , ' found no link ' )
assert ( type ( arr ) == ' table ' , ' links not arranged in a table ' )
2022-10-29 23:06:41 +00:00
local pos = array.find ( arr , target )
2022-11-12 06:32:57 +00:00
--? print(('contains %s at index %s'):format(target, pos))
2023-11-18 20:17:17 +00:00
assert ( pos , " couldn't find link to remove in links table " )
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
2023-11-18 20:17:17 +00:00
assert ( Links [ src ] [ rel ] == target , ' link at this rel is not the target; giving up ' )
2022-11-23 05:49:11 +00:00
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
2023-11-18 20:17:17 +00:00
assert ( Cursor_pane.row == 1 , " couldn't set current pane after unrolling " )
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 ) )
2022-08-11 17:07:58 +00:00
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
2023-11-18 20:17:17 +00:00
assert ( Cursor_pane.row == 1 , " couldn't repurpose column for neighbors " )
2022-08-05 15:56:31 +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
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 23:03:28 +00:00
f : close ( )
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
2023-11-18 20:17:17 +00:00
assert ( not delete_cursor_column , " failed to delete note's column " )
2022-08-05 21:43:35 +00:00
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 ) )
2023-11-18 20:17:17 +00:00
assert ( status , " failed to create directory for note " )
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