2022-02-05 07:57:05 +00:00
# . tlv file generated by https : // github.com / akkartik / teliva
# You may edit it if you are careful ; however , you may see cryptic errors if you
# violate Teliva ' s assumptions.
#
# . tlv files are representations of Teliva programs . Teliva programs consist of
# sequences of definitions . Each definition is a table of key / value pairs . Keys
# and values are both strings .
#
# Lines in . tlv files always follow exactly one of the following forms :
# - comment lines at the top of the file starting with ' # ' at column 0
# - beginnings of definitions starting with ' - ' at column 0 , followed by a
# key / value pair
# - key / value pairs consisting of ' ' at column 0 , containing either a
# spaceless value on the same line , or a multi - line value
# - multiline values indented by more than 2 spaces , starting with a ' > '
#
# If these constraints are violated , Teliva may unceremoniously crash . Please
# report bugs at http : // akkartik.name / contact
- __teliva_timestamp : original
str_helpers :
>-- some string helpers from http : // lua - users.org / wiki / StringIndexing
>
>-- index characters using [ ]
> getmetatable ( ' ' ) . __index = function ( str , i )
> if type ( i ) == ' number ' then
> return string.sub ( str , i , i )
> else
> return string [ i ]
> end
> end
>
>-- ranges using ( ) , selected bytes using { }
> getmetatable ( ' ' ) . __call = function ( str , i , j )
> if type ( i ) ~= ' table ' then
> return string.sub ( str , i , j )
> else
> local t = { }
> for k , v in ipairs ( i ) do
> t [ k ] = string.sub ( str , v , v )
> end
> return table.concat ( t )
> end
> end
>
>-- iterate over an ordered sequence
> function q ( x )
> if type ( x ) == ' string ' then
> return x : gmatch ( ' . ' )
> else
> return ipairs ( x )
> end
> end
>
>-- insert within string
> function string . insert ( str1 , str2 , pos )
> return str1 : sub ( 1 , pos ) .. str2 .. str1 : sub ( pos + 1 )
> end
>
> function string . remove ( s , pos )
> return s : sub ( 1 , pos - 1 ) .. s : sub ( pos + 1 )
> end
>
>-- TODO : backport utf - 8 support from Lua 5.3
- __teliva_timestamp : original
debugy :
> debugy = 5
- __teliva_timestamp : original
dbg :
>-- helper for debug by print ; overlay debug information towards the right
>-- reset debugy every time you refresh screen
> function dbg ( window , s )
> local oldy = 0
> local oldx = 0
> oldy , oldx = window : getyx ( )
> window : mvaddstr ( debugy , 60 , s )
> debugy = debugy + 1
> window : mvaddstr ( oldy , oldx , ' ' )
> end
- __teliva_timestamp : original
check_eq :
> function check_eq ( x , expected , msg )
> if x == expected then
> curses.addch ( ' . ' )
> else
> print ( ' F - ' .. msg )
> print ( ' expected ' .. tostring ( expected ) .. ' but got ' .. x )
> teliva_num_test_failures = teliva_num_test_failures + 1
> -- overlay first test failure on editors
> if teliva_first_failure == nil then
> teliva_first_failure = msg
> end
> end
> end
- __teliva_timestamp : original
map :
>-- only for arrays
> function map ( l , f )
> result = { }
> for _ , x in ipairs ( l ) do
> table.insert ( result , f ( x ) )
> end
> return result
> end
- __teliva_timestamp : original
reduce :
>-- only for arrays
> function reduce ( l , f , init )
> result = init
> for _ , x in ipairs ( l ) do
> result = f ( result , x )
> end
> return result
> end
- __teliva_timestamp : original
filter :
>-- only for arrays
> function filter ( l , f )
> result = { }
> for _ , x in ipairs ( l ) do
> if f ( x ) then
> table.insert ( result , x )
> end
> end
> return result
> end
- __teliva_timestamp : original
find_index :
> function find_index ( arr , x )
> for n , y in ipairs ( arr ) do
> if x == y then
> return n
> end
> end
> end
- __teliva_timestamp : original
trim :
> function trim ( s )
> return s : gsub ( ' ^%s* ' , ' ' ) : gsub ( ' %s*$ ' , ' ' )
> end
- __teliva_timestamp : original
split :
> function split ( s , d )
> result = { }
> for match in ( s .. d ) : gmatch ( " (.-) " .. d ) do
> table.insert ( result , match ) ;
> end
> return result
> end
- __teliva_timestamp : original
spaces :
> function spaces ( n )
> for i = 1 , n do
> curses.addch ( ' ' )
> end
> end
- __teliva_timestamp : original
window :
> window = curses.stdscr ( )
- __teliva_timestamp : original
menu :
>-- To show app - specific hotkeys in the menu bar , add hotkey / command
>-- arrays of strings to the menu array .
2022-02-08 08:28:20 +00:00
> menu = {
> { ' ^e ' , ' edit ' } ,
> }
2022-02-05 07:57:05 +00:00
- __teliva_timestamp : original
init_colors :
> function init_colors ( )
> -- light background
2022-02-08 07:17:03 +00:00
> curses.init_pair ( view_settings.current_zettel_bg , 236 , 230 )
2022-02-05 07:57:05 +00:00
> curses.init_pair ( 1 , 236 , 250 )
> curses.init_pair ( 2 , 236 , 252 )
> -- dark background
2022-02-08 07:17:03 +00:00
>-- ? curses.init_pair ( view_settings.current_zettel_bg , 252 , 130 )
2022-02-05 07:57:05 +00:00
>-- ? curses.init_pair ( 1 , 252 , 240 )
>-- ? curses.init_pair ( 2 , 252 , 242 )
> end
- __teliva_timestamp : original
main :
> function main ( )
> init_colors ( )
2022-02-08 07:17:03 +00:00
> current_zettel_id = zettels.root
2022-02-05 07:57:05 +00:00
>
> while true do
> render ( window )
> update ( window )
> end
> end
- __teliva_timestamp : original
depth :
> function depth ( zettel )
> local result = 0
> while zettel.parent do
> result = result + 1
2022-02-08 07:17:03 +00:00
> zettel = zettel.parent
2022-02-05 07:57:05 +00:00
> end
2022-02-08 07:17:03 +00:00
> return result
2022-02-05 07:57:05 +00:00
> end
2022-02-08 07:17:03 +00:00
- __teliva_timestamp : original
render_zettel :
> function render_zettel ( window , bg , indent , starty , startx , zettel )
> window : attrset ( curses.color_pair ( bg ) )
> for y = 0 , view_settings.height - 1 do
> for x = 0 , view_settings.width - 1 do
> window : mvaddch ( y + starty , x + startx , ' ' )
2022-02-05 07:57:05 +00:00
> end
> end
2022-02-08 07:17:03 +00:00
> local y , x = 0 , indent + 1
> for i = 1 , # zettel.data do
> local c = zettel.data [ i ]
> if c == ' \n ' then
> y = y + 1
> x = indent + 1
> else
2022-02-09 03:17:56 +00:00
> window : mvaddstr ( y + starty , x + startx , c )
2022-02-08 07:17:03 +00:00
> x = x + 1
> if x >= startx + view_settings.width then
> y = y + 1
> x = indent + 1
> end
2022-02-05 07:57:05 +00:00
> end
2022-02-09 03:20:13 +00:00
> if y >= view_settings.height then
2022-02-08 07:17:03 +00:00
> break
2022-02-05 07:57:05 +00:00
> end
> end
> end
2022-02-08 07:17:03 +00:00
- __teliva_timestamp : original
current_zettel_id :
> current_zettel_id = ' '
- __teliva_timestamp : original
view_settings :
> view_settings = {
> -- dimensions for rendering a single zettel; extra text gets truncated
> width = 50 ,
> height = 3 ,
> -- spacing between zettels
> hmargin = 1 ,
> vmargin = 1 ,
> --
> indent = 2 , -- how children of a zettel are indicated
> current_zettel_bg = 3 , -- color pair index initialized in init_colors
> }
- __teliva_timestamp : original
zettels :
> zettels = {
> root = " a " ,
> a = {
> data = " abc \n def " ,
> child = " c " ,
> next = " b " ,
> } ,
> b = {
> data = " ghi \n jklm " ,
> prev = " a " ,
> } ,
> c = {
> data = " c " ,
> parent = " a " ,
> next = " d " ,
> } ,
> d = {
> data = " d " ,
> parent = " a " ,
> prev = " c " ,
> }
> }
- __teliva_timestamp : original
render_state :
>-- some information about what ' s been drawn on screen
> render_state = {
> -- where the current zettel is, in units of zettels
> curr_h = 1 ,
> curr_w = 1 ,
> -- what zettel is at each position on screen, in units of zettels
> hw2id = { } ,
> }
- __teliva_timestamp : original
update :
2022-02-05 07:57:05 +00:00
> function update ( window )
> local key = curses.getch ( )
> local curr = zettels [ current_zettel_id ]
> -- graph-based navigation
> if key == string.byte ( ' j ' ) then
> if curr.child then
> current_zettel_id = curr.child
> elseif curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> elseif key == string.byte ( ' k ' ) then
> if curr.parent then current_zettel_id = curr.parent end
> elseif key == string.byte ( ' h ' ) then
> if curr.prev then
> current_zettel_id = curr.prev
> elseif curr.parent then
> current_zettel_id = curr.parent
> end
> elseif key == string.byte ( ' l ' ) then
> if curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> -- screen-based navigation
> elseif key == curses.KEY_UP then
> if render_state.curr_h > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h - 1 ]
> end
> elseif key == curses.KEY_DOWN then
> if render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ]
> end
> elseif key == curses.KEY_LEFT then
> if render_state.curr_w > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w - 1 ] [ render_state.curr_h ]
> end
> elseif key == curses.KEY_RIGHT then
> if render_state.wh2id [ render_state.curr_w + 1 ] and render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ]
> end
2022-02-08 08:28:20 +00:00
> --
> elseif key == 5 then -- ctrl-e
> editz ( window )
2022-02-05 07:57:05 +00:00
> end
> end
2022-02-08 07:17:03 +00:00
- __teliva_timestamp : original
render :
2022-02-05 07:57:05 +00:00
> function render ( window )
> window : clear ( )
> local lines , cols = window : getmaxyx ( )
> local bg = 1
> local y , x = 0 , 0 -- units of characters (0-based)
> local w , h = 1 , 1 -- units of zettels (1-based)
> -- render zettels depth-first, while tracking relative positions
> local done = { }
> local inprogress = { zettels.root }
> render_state.wh2id = { { } }
> while # inprogress > 0 do
> local currid = table.remove ( inprogress )
> if not done [ currid ] then
> done [ currid ] = true
> table.insert ( render_state.wh2id [ w ] , currid )
> local zettel = zettels [ currid ]
> if currid == current_zettel_id then
> render_state.curr_w = w
> render_state.curr_h = h
> end
> local currbg = ( currid == current_zettel_id ) and view_settings.current_zettel_bg or bg
> render_zettel ( window , currbg , depth ( zettel ) * view_settings.indent , y , x , zettel )
> if zettel.next then table.insert ( inprogress , zettel.next ) end
> if zettel.child then table.insert ( inprogress , zettel.child ) end
> bg = 3 - bg -- toggle between color pairs 1 and 2
> y = y + view_settings.height + view_settings.vmargin
> h = h + 1
> if y + view_settings.height > lines then
> y = 0
> h = 1
> x = x + view_settings.width + view_settings.hmargin
> w = w + 1
> if x + view_settings.width > cols then break end
> table.insert ( render_state.wh2id , { } )
> end
> end
> end
> window : mvaddstr ( lines - 2 , 0 , ' ' )
> for i = 1 , 3 do
> window : attrset ( curses.color_pair ( i % 2 + 1 ) )
> window : addstr ( ' ' )
> spaces ( view_settings.width - string.len ( ' ' ) )
> window : attrset ( curses.color_pair ( 0 ) )
> window : addstr ( ' ' ) -- margin
> end
> window : mvaddstr ( lines - 1 , 0 , ' ? ' )
> curses.refresh ( )
> end
2022-02-08 07:17:03 +00:00
- __teliva_timestamp : original
2022-02-05 07:57:05 +00:00
view_settings :
> view_settings = {
> -- dimensions for rendering a single zettel; extra text gets truncated
> width = 50 ,
> height = 3 ,
> -- spacing between zettels
> hmargin = 1 ,
> vmargin = 1 ,
> --
> indent = 2 , -- how children of a zettel are indicated
> current_zettel_bg = 3 , -- color pair index initialized in init_colors
> }
2022-02-08 08:28:20 +00:00
- __teliva_timestamp : original
editz :
> function editz ( )
> menu = { { ' ^e ' , ' back to browsing ' } , }
> local top = ( render_state.curr_h - 1 ) * ( view_settings.height + view_settings.vmargin )
> local bottom = top + view_settings.height
> local left = ( render_state.curr_w - 1 ) * ( view_settings.width + view_settings.hmargin )
> local right = left + view_settings.width
> local cursor = 1
> curses.curs_set ( 0 )
> local quit = false
> while not quit do
> editz_render ( window , zettels [ current_zettel_id ] . data , cursor , top , bottom , left , right )
> quit , zettels [ current_zettel_id ] . data , cursor = editz_update ( window , zettels [ current_zettel_id ] . data , cursor )
> end
> curses.curs_set ( 1 )
> end
- __teliva_timestamp : original
editz_render :
2022-02-09 02:40:59 +00:00
> function editz_render ( window , s , cursor , top , minbottom , left , right )
> local h , w = window : getmaxyx ( )
2022-02-08 08:28:20 +00:00
> window : attrset ( curses.color_pair ( view_settings.current_zettel_bg ) )
2022-02-09 02:40:59 +00:00
> for y = top , minbottom - 1 do
2022-02-08 08:28:20 +00:00
> for x = left , right - 1 do
> window : mvaddch ( y , x , ' ' )
> end
> end
2022-02-09 02:40:59 +00:00
> local y , x = top , left + 1 -- left padding; TODO: indent
2022-02-08 08:28:20 +00:00
> window : mvaddstr ( y , x , ' ' )
> for i = 1 , string.len ( s ) do
> -- render character
> if i == cursor then
> if s [ i ] == ' \n ' then
> -- newline at cursor = render extra space in reverse video before jumping to new line
> window : attron ( curses.A_REVERSE )
> window : addch ( ' ' )
> window : attroff ( curses.A_REVERSE )
> else
> -- most characters at cursor = render in reverse video
> window : attron ( curses.A_REVERSE )
> window : addstr ( s [ i ] )
> window : attroff ( curses.A_REVERSE )
> end
> else
> if s [ i ] ~= ' \n ' then
> window : addstr ( s [ i ] )
> end
> end
> -- update cursor position
> if s [ i ] == ' \n ' then
2022-02-09 02:40:59 +00:00
> if i == cursor then x = x + 1 ; end
> for col = x , right - 1 do window : addch ( ' ' ) ; end
2022-02-08 08:28:20 +00:00
> x = left
> y = y + 1
2022-02-09 02:40:59 +00:00
> if y >= h - 2 then return end
> window : mvaddstr ( y , x , ' ' )
> for col = x , right - 1 do window : addch ( ' ' ) ; end
> x = left + 1 -- left padding; TODO: indent
2022-02-08 08:28:20 +00:00
> window : mvaddstr ( y , x , ' ' )
> else
> x = x + 1
> if x >= right then
> y = y + 1
2022-02-09 02:40:59 +00:00
> if y >= h - 2 then return end
> x = left + 1 -- left padding; TODO: indent
2022-02-08 08:28:20 +00:00
> window : mvaddstr ( y , x , ' ' )
> end
> end
> end
> if cursor > string.len ( s ) then
> window : attron ( curses.A_REVERSE )
> window : addch ( ' ' )
> window : attroff ( curses.A_REVERSE )
> else
> window : addch ( ' ' )
> end
> end
- __teliva_timestamp : original
editz_update :
> function editz_update ( window , prose , cursor )
> local key = curses.getch ( )
> local h , w = window : getmaxyx ( )
> if key == curses.KEY_LEFT then
> if cursor > 1 then
> cursor = cursor - 1
> end
> elseif key == curses.KEY_RIGHT then
> if cursor <= # prose then
> cursor = cursor + 1
> end
> elseif key == curses.KEY_DOWN then
> cursor = cursor_down ( prose , cursor , w )
> elseif key == curses.KEY_UP then
> cursor = cursor_up ( prose , cursor , w )
> elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete
> if cursor > 1 then
> cursor = cursor - 1
> prose = prose : remove ( cursor )
> end
> elseif key == 5 then -- ctrl-e
> return true , prose , cursor
> elseif key == 10 or ( key >= 32 and key < 127 ) then
> prose = prose : insert ( string.char ( key ) , cursor - 1 )
> cursor = cursor + 1
> end
> return false , prose , cursor
> end
- __teliva_timestamp : original
cursor_down :
> function cursor_down ( s , old_idx , width )
> local max = string.len ( s )
> local i = 1
> -- compute oldcol, the screen column of old_idx
> local oldcol = 0
> local col = 0
> while true do
> if i > max then
> -- abnormal old_idx
> return old_idx
> end
> if i == old_idx then
> oldcol = col
> break
> end
> if s [ i ] == ' \n ' then
> col = 0
> else
> col = col + 1
> end
> i = i + 1
> end
> -- skip rest of line
> while true do
> if i > max then
> -- current line is at bottom
> if col >= width then
> return i
> end
> return old_idx
> end
> if s [ i ] == ' \n ' then
> break
> end
> if i - old_idx >= width then
> return i
> end
> col = col + 1
> i = i + 1
> end
> -- compute index at same column on next line
> -- i is at a newline
> i = i + 1
> col = 0
> while true do
> if i > max then
> -- next line is at bottom and is too short; position at end of it
> return i
> end
> if s [ i ] == ' \n ' then
> -- next line is too short; position at end of it
> return i
> end
> if col == oldcol then
> return i
> end
> col = col + 1
> i = i + 1
> end
> end
>
> function test_cursor_down ( )
> -- lines that don't wrap
> check_eq ( cursor_down ( ' abc \n def ' , 1 , 5 ) , 5 , ' cursor_down: non-bottom line first char ' )
> check_eq ( cursor_down ( ' abc \n def ' , 2 , 5 ) , 6 , ' cursor_down: non-bottom line mid char ' )
> check_eq ( cursor_down ( ' abc \n def ' , 3 , 5 ) , 7 , ' cursor_down: non-bottom line final char ' )
> check_eq ( cursor_down ( ' abc \n def ' , 4 , 5 ) , 8 , ' cursor_down: non-bottom line end ' )
> check_eq ( cursor_down ( ' abc \n def ' , 5 , 5 ) , 5 , ' cursor_down: bottom line first char ' )
> check_eq ( cursor_down ( ' abc \n def ' , 6 , 5 ) , 6 , ' cursor_down: bottom line mid char ' )
> check_eq ( cursor_down ( ' abc \n def ' , 7 , 5 ) , 7 , ' cursor_down: bottom line final char ' )
> check_eq ( cursor_down ( ' abc \n \n def ' , 2 , 5 ) , 5 , ' cursor_down: to shorter line ' )
>
> -- within a single wrapping line
> -- |abcde| <-- wrap, no newline
> -- |fgh |
> check_eq ( cursor_down ( ' abcdefgh ' , 1 , 5 ) , 6 , ' cursor_down from wrapping line: first char ' )
> check_eq ( cursor_down ( ' abcdefgh ' , 2 , 5 ) , 7 , ' cursor_down from wrapping line: mid char ' )
> check_eq ( cursor_down ( ' abcdefgh ' , 5 , 5 ) , 9 , ' cursor_down from wrapping line: to shorter line ' )
>
> -- within a single very long wrapping line
> -- |abcde| <-- wrap, no newline
> -- |fghij| <-- wrap, no newline
> -- |klm |
> check_eq ( cursor_down ( ' abcdefghijklm ' , 1 , 5 ) , 6 , ' cursor_down within wrapping line: first char ' )
> check_eq ( cursor_down ( ' abcdefghijklm ' , 2 , 5 ) , 7 , ' cursor_down within wrapping line: mid char ' )
> check_eq ( cursor_down ( ' abcdefghijklm ' , 5 , 5 ) , 10 , ' cursor_down within wrapping line: final char ' )
> end
- __teliva_timestamp : original
2022-02-09 16:30:19 +00:00
__teliva_note :
> initial commit : show / edit zettels
2022-02-08 08:28:20 +00:00
cursor_up :
> function cursor_up ( s , old_idx , width )
> local max = string.len ( s )
> local i = 1
> -- compute oldcol, the screen column of old_idx
> local oldcol = 0
> local col = 0
> local newline_before_current_line = 0
> while true do
> if i > max or i == old_idx then
> oldcol = col
> break
> end
> if s [ i ] == ' \n ' then
> col = 0
> newline_before_current_line = i
> else
> col = col + 1
> if col == width then
> col = 0
> end
> end
> i = i + 1
> end
> -- find previous newline
> i = i - col - 1
> if old_idx - newline_before_current_line > width then
> -- we're in a wrapped line
> return old_idx - width
> end
> -- scan back to start of previous line
> if s [ i ] == ' \n ' then
> i = i - 1
> end
> while true do
> if i < 1 then
> -- current line is at top
> break
> end
> if s [ i ] == ' \n ' then
> break
> end
> i = i - 1
> end
> -- i is at a newline
> i = i + 1
> -- skip whole screen lines within previous line
> while newline_before_current_line - i > width do
> i = i + width
> end
> -- compute index at same column on previous screen line
> col = 0
> while true do
> if i > max then
> -- next line is at bottom and is too short; position at end of it
> return i
> end
> if s [ i ] == ' \n ' then
> -- next line is too short; position at end of it
> return i
> end
> if col == oldcol then
> return i
> end
> col = col + 1
> i = i + 1
> end
> end
>
> function test_cursor_up ( )
> -- lines that don't wrap
> check_eq ( cursor_up ( ' abc \n def ' , 1 , 5 ) , 1 , ' cursor_up: top line first char ' )
> check_eq ( cursor_up ( ' abc \n def ' , 2 , 5 ) , 2 , ' cursor_up: top line mid char ' )
> check_eq ( cursor_up ( ' abc \n def ' , 3 , 5 ) , 3 , ' cursor_up: top line final char ' )
> check_eq ( cursor_up ( ' abc \n def ' , 4 , 5 ) , 4 , ' cursor_up: top line end ' )
> check_eq ( cursor_up ( ' abc \n def ' , 5 , 5 ) , 1 , ' cursor_up: non-top line first char ' )
> check_eq ( cursor_up ( ' abc \n def ' , 6 , 5 ) , 2 , ' cursor_up: non-top line mid char ' )
> check_eq ( cursor_up ( ' abc \n def ' , 7 , 5 ) , 3 , ' cursor_up: non-top line final char ' )
> check_eq ( cursor_up ( ' abc \n def \n ' , 8 , 5 ) , 4 , ' cursor_up: non-top line end ' )
> check_eq ( cursor_up ( ' ab \n def \n ' , 7 , 5 ) , 3 , ' cursor_up: to shorter line ' )
>
> -- within a single wrapping line
> -- |abcde| <-- wrap, no newline
> -- |fgh |
> check_eq ( cursor_up ( ' abcdefgh ' , 6 , 5 ) , 1 , ' cursor_up from wrapping line: first char ' )
> check_eq ( cursor_up ( ' abcdefgh ' , 7 , 5 ) , 2 , ' cursor_up from wrapping line: mid char ' )
> check_eq ( cursor_up ( ' abcdefgh ' , 8 , 5 ) , 3 , ' cursor_up from wrapping line: final char ' )
> check_eq ( cursor_up ( ' abcdefgh ' , 9 , 5 ) , 4 , ' cursor_up from wrapping line: wrapped line end ' )
>
> -- within a single very long wrapping line
> -- |abcde| <-- wrap, no newline
> -- |fghij| <-- wrap, no newline
> -- |klm |
> check_eq ( cursor_up ( ' abcdefghijklm ' , 11 , 5 ) , 6 , ' cursor_up within wrapping line: first char ' )
> check_eq ( cursor_up ( ' abcdefghijklm ' , 12 , 5 ) , 7 , ' cursor_up within wrapping line: mid char ' )
> check_eq ( cursor_up ( ' abcdefghijklm ' , 13 , 5 ) , 8 , ' cursor_up within wrapping line: final char ' )
> check_eq ( cursor_up ( ' abcdefghijklm ' , 14 , 5 ) , 9 , ' cursor_up within wrapping line: wrapped line end ' )
>
> -- from below to (the bottom of) a wrapping line
> -- |abcde| <-- wrap, no newline
> -- |fg |
> -- |hij |
> check_eq ( cursor_up ( ' abcdefg \n hij ' , 9 , 5 ) , 6 , ' cursor_up to wrapping line: first char ' )
> check_eq ( cursor_up ( ' abcdefg \n hij ' , 10 , 5 ) , 7 , ' cursor_up to wrapping line: mid char ' )
> check_eq ( cursor_up ( ' abcdefg \n hij ' , 11 , 5 ) , 8 , ' cursor_up to wrapping line: final char ' )
> check_eq ( cursor_up ( ' abcdefg \n hij ' , 12 , 5 ) , 8 , ' cursor_up to wrapping line: to shorter line ' )
> end
2022-02-09 17:18:05 +00:00
- __teliva_timestamp :
> Wed Feb 9 08 : 15 : 25 2022
render :
2022-02-09 16:30:19 +00:00
> function render ( window )
> window : clear ( )
> local lines , cols = window : getmaxyx ( )
> local bg = 1
> local y , x = 0 , 0 -- units of characters (0-based)
> local w , h = 1 , 1 -- units of zettels (1-based)
> -- render zettels depth-first, while tracking relative positions
> local done = { }
> local inprogress = { zettels.root }
> render_state.wh2id = { { } }
> while # inprogress > 0 do
> local currid = table.remove ( inprogress )
> if not done [ currid ] then
> done [ currid ] = true
> table.insert ( render_state.wh2id [ w ] , currid )
> local zettel = zettels [ currid ]
> if currid == current_zettel_id then
> render_state.curr_w = w
> render_state.curr_h = h
> end
> local currbg = ( currid == current_zettel_id ) and view_settings.current_zettel_bg or bg
> render_zettel ( window , currbg , depth ( zettel ) * view_settings.indent , y , x , zettel )
> if zettel.next then table.insert ( inprogress , zettel.next ) end
> if zettel.child then table.insert ( inprogress , zettel.child ) end
> bg = 3 - bg -- toggle between color pairs 1 and 2
> y = y + view_settings.height + view_settings.vmargin
> h = h + 1
> if y + view_settings.height > lines then
> y = 0
> h = 1
> x = x + view_settings.width + view_settings.hmargin
> w = w + 1
> if x + view_settings.width > cols then break end
> table.insert ( render_state.wh2id , { } )
> end
> end
> end
> window : mvaddstr ( lines - 1 , 0 , ' ' )
> for i = 1 , 3 do
> window : attrset ( curses.color_pair ( i % 2 + 1 ) )
> window : addstr ( ' ' )
> spaces ( view_settings.width - string.len ( ' ' ) )
> window : attrset ( curses.color_pair ( 0 ) )
> window : addstr ( ' ' ) -- margin
> end
> curses.refresh ( )
> end
2022-02-09 17:18:05 +00:00
- __teliva_timestamp :
> Wed Feb 9 08 : 15 : 35 2022
main :
2022-02-09 16:30:19 +00:00
> function main ( )
> init_colors ( )
> current_zettel_id = zettels.root
>
> curses.curs_set ( 0 )
> while true do
> render ( window )
> update ( window )
> end
> end
2022-02-09 17:18:05 +00:00
- __teliva_timestamp :
> Wed Feb 9 08 : 16 : 24 2022
__teliva_note :
2022-02-09 16:30:19 +00:00
> get rid of commandline
>
> There ' s a reason vim hides it. Confusing to have two cursors on screen.
editz :
> function editz ( )
> menu = { { ' ^e ' , ' back to browsing ' } , }
> local top = ( render_state.curr_h - 1 ) * ( view_settings.height + view_settings.vmargin )
> local bottom = top + view_settings.height
> local left = ( render_state.curr_w - 1 ) * ( view_settings.width + view_settings.hmargin )
> local right = left + view_settings.width
> local cursor = 1
> local quit = false
> while not quit do
> editz_render ( window , zettels [ current_zettel_id ] . data , cursor , top , bottom , left , right )
> quit , zettels [ current_zettel_id ] . data , cursor = editz_update ( window , zettels [ current_zettel_id ] . data , cursor )
> end
> end
2022-02-09 17:18:05 +00:00
- __teliva_timestamp :
> Wed Feb 9 08 : 22 : 20 2022
editz_render :
2022-02-09 16:30:19 +00:00
> function editz_render ( window , s , cursor , top , minbottom , left , right )
> local h , w = window : getmaxyx ( )
2022-02-10 07:41:36 +00:00
> local cursor_y , cursor_x = 0 , 0
2022-02-09 16:30:19 +00:00
> window : attrset ( curses.color_pair ( view_settings.current_zettel_bg ) )
> for y = top , minbottom - 1 do
> for x = left , right - 1 do
> window : mvaddch ( y , x , ' ' )
> end
> end
> local y , x = top , left + 1 -- left padding; TODO: indent
> window : mvaddstr ( y , x , ' ' )
> for i = 1 , string.len ( s ) do
> if i == cursor then
> cursor_y = y
> cursor_x = x
> end
> if s [ i ] ~= ' \n ' then
> window : addstr ( s [ i ] )
> x = x + 1
> if x >= right then
> y = y + 1
> if y >= h - 2 then return end
> x = left + 1 -- left padding; TODO: indent
> window : mvaddstr ( y , x , ' ' )
> end
> else
> for col = x + 1 , right - 1 do window : addch ( ' ' ) ; end
> x = left
> y = y + 1
> if y >= h - 2 then return end
> window : mvaddstr ( y , x , ' ' )
> for col = x , right - 1 do window : addch ( ' ' ) ; end
> x = left + 1 -- left padding; TODO: indent
> window : mvaddstr ( y , x , ' ' )
> end
> end
> if cursor_y == 0 and cursor_x == 0 then
> cursor_y = y
> cursor_x = x
> end
2022-02-10 07:41:36 +00:00
> window : mvaddstr ( cursor_y , cursor_x , ' ' )
2022-02-09 16:30:19 +00:00
> end
- __teliva_timestamp :
> Wed Feb 9 08 : 25 : 05 2022
editz :
> function editz ( )
2022-02-10 07:27:26 +00:00
> local old_menu = menu
2022-02-09 16:30:19 +00:00
> menu = { { ' ^e ' , ' back to browsing ' } , }
> local top = ( render_state.curr_h - 1 ) * ( view_settings.height + view_settings.vmargin )
> local bottom = top + view_settings.height
> local left = ( render_state.curr_w - 1 ) * ( view_settings.width + view_settings.hmargin )
> local right = left + view_settings.width
2022-02-10 07:41:36 +00:00
> local cursor = string.len ( zettels [ current_zettel_id ] . data ) + 1
2022-02-09 16:30:19 +00:00
> local quit = false
> curses.curs_set ( 1 )
> while not quit do
> editz_render ( window , zettels [ current_zettel_id ] . data , cursor , top , bottom , left , right )
> quit , zettels [ current_zettel_id ] . data , cursor = editz_update ( window , zettels [ current_zettel_id ] . data , cursor )
> end
> curses.curs_set ( 0 )
2022-02-10 07:27:26 +00:00
> menu = old_menu
2022-02-09 16:30:19 +00:00
> end
2022-02-09 17:18:05 +00:00
- __teliva_timestamp :
> Wed Feb 9 08 : 28 : 13 2022
__teliva_note :
2022-02-09 16:30:19 +00:00
> stop simulating the cursor
>
> editz_render is now much simpler
editz_update :
> function editz_update ( window , prose , cursor )
> local key = curses.getch ( )
> local h , w = window : getmaxyx ( )
> if key == curses.KEY_LEFT then
> if cursor > 1 then
> cursor = cursor - 1
> end
> elseif key == curses.KEY_RIGHT then
> if cursor <= # prose then
> cursor = cursor + 1
> end
> elseif key == curses.KEY_DOWN then
> cursor = cursor_down ( prose , cursor , w )
> elseif key == curses.KEY_UP then
> cursor = cursor_up ( prose , cursor , w )
> elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete
> if cursor > 1 then
> cursor = cursor - 1
> prose = prose : remove ( cursor )
> end
> elseif key == 5 then -- ctrl-e
> return true , prose , cursor
> elseif key == 10 or ( key >= 32 and key < 127 ) then
2022-02-10 07:41:36 +00:00
> prose = prose : insert ( string.char ( key ) , cursor - 1 )
2022-02-09 16:30:19 +00:00
> cursor = cursor + 1
> end
> return false , prose , cursor
> end
2022-02-10 07:49:59 +00:00
- __teliva_timestamp :
> Wed Feb 9 17 : 55 : 52 2022
menu :
>-- To show app - specific hotkeys in the menu bar , add hotkey / command
>-- arrays of strings to the menu array .
> menu = {
> { ' j ' , ' child ' } ,
> { ' k ' , ' parent ' } ,
> { ' l/h ' , ' next/prev sib ' } ,
> { ' e ' , ' edit ' } ,
> }
- __teliva_timestamp :
> Wed Feb 9 17 : 56 : 18 2022
__teliva_note :
> no need for chords once we drop the commandline
update :
> function update ( window )
> local key = curses.getch ( )
> local curr = zettels [ current_zettel_id ]
> -- graph-based navigation
> if key == string.byte ( ' j ' ) then
> if curr.child then
> current_zettel_id = curr.child
> elseif curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> elseif key == string.byte ( ' k ' ) then
> if curr.parent then current_zettel_id = curr.parent end
> elseif key == string.byte ( ' h ' ) then
> if curr.prev then
> current_zettel_id = curr.prev
> elseif curr.parent then
> current_zettel_id = curr.parent
> end
> elseif key == string.byte ( ' l ' ) then
> if curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> -- screen-based navigation
> elseif key == curses.KEY_UP then
> if render_state.curr_h > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h - 1 ]
> end
> elseif key == curses.KEY_DOWN then
> if render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ]
> end
> elseif key == curses.KEY_LEFT then
> if render_state.curr_w > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w - 1 ] [ render_state.curr_h ]
> end
> elseif key == curses.KEY_RIGHT then
> if render_state.wh2id [ render_state.curr_w + 1 ] and render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ]
> end
> --
> elseif key == string.byte ( ' e ' ) then
> local old_menu = menu
> editz ( window )
> menu = old_menu
> end
> end
- __teliva_timestamp :
> Wed Feb 9 18 : 00 : 42 2022
menu :
>-- To show app - specific hotkeys in the menu bar , add hotkey / command
>-- arrays of strings to the menu array .
> menu = {
> { ' j ' , ' child ' } ,
> { ' k ' , ' parent ' } ,
> { ' l/h ' , ' next/prev sib ' } ,
> { ' e ' , ' edit ' } ,
> { ' a/b ' , ' insert sib ' } ,
> { ' c ' , ' insert child ' } ,
> }
- __teliva_timestamp :
> Wed Feb 9 18 : 16 : 23 2022
zettels :
> zettels = {
> root = " id1 " ,
> final = 4 ,
> id1 = {
2022-02-10 15:40:04 +00:00
> data = " this is zettel A \n \n it has some text " ,
2022-02-10 07:49:59 +00:00
> child = " id3 " ,
> next = " id2 " ,
> } ,
> id2 = {
2022-02-10 15:40:04 +00:00
> data = " this is a sibling of zettel A at the top level " ,
2022-02-10 07:49:59 +00:00
> prev = " id1 " ,
> } ,
> id3 = {
2022-02-10 15:40:04 +00:00
> data = " this is zettel B, a child of A " ,
2022-02-10 07:49:59 +00:00
> parent = " id1 " ,
> next = " id4 " ,
> } ,
> id4 = {
2022-02-10 15:40:04 +00:00
> data = " this is another child of zettel A, a sibling of B " ,
2022-02-10 07:49:59 +00:00
> parent = " id1 " ,
> prev = " id3 " ,
> }
> }
- __teliva_timestamp :
> Wed Feb 9 23 : 04 : 49 2022
new_id :
> function new_id ( )
> zettels.final = zettels.final + 1
> local result = ' id ' .. tostring ( zettels.final )
> zettels [ result ] = { }
> return result
> end
- __teliva_timestamp :
> Wed Feb 9 23 : 10 : 57 2022
__teliva_note :
> creating new zettels
>
> feels natural to immediately start editing them
update :
> function update ( window )
> local key = curses.getch ( )
> local curr = zettels [ current_zettel_id ]
> assert ( curr , string.format ( ' cursor fell off the edge of the world: %s ' , type ( current_zettel_id ) ) )
> -- graph-based navigation
> if key == string.byte ( ' j ' ) then
> if curr.child then
> current_zettel_id = curr.child
> elseif curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> elseif key == string.byte ( ' k ' ) then
> if curr.parent then current_zettel_id = curr.parent end
> elseif key == string.byte ( ' h ' ) then
> if curr.prev then
> current_zettel_id = curr.prev
> elseif curr.parent then
> current_zettel_id = curr.parent
> end
> elseif key == string.byte ( ' l ' ) then
> if curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> -- screen-based navigation
> elseif key == curses.KEY_UP then
> if render_state.curr_h > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h - 1 ]
> end
> elseif key == curses.KEY_DOWN then
> if render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ]
> end
> elseif key == curses.KEY_LEFT then
> if render_state.curr_w > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w - 1 ] [ render_state.curr_h ]
> end
> elseif key == curses.KEY_RIGHT then
> if render_state.wh2id [ render_state.curr_w + 1 ] and render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ]
> end
> --
> elseif key == string.byte ( ' e ' ) then
> editz ( window )
> elseif key == string.byte ( ' a ' ) then
> -- insert sibling after
> local old = curr.next
> curr.next = new_id ( )
> local new = zettels [ curr.next ]
> new.data = ' '
> new.next = old
> zettels [ old ] . prev = curr.next
> new.prev = current_zettel_id
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> new.parent = curr.parent
> current_zettel_id = curr.next
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' b ' ) then
> -- insert sibling before
> local old = curr.prev
> curr.prev = new_id ( )
> local new = zettels [ curr.prev ]
> new.data = ' '
> new.prev = old
> zettels [ old ] . next = curr.prev
> new.next = current_zettel_id
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> new.parent = curr.parent
> current_zettel_id = curr.prev
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' c ' ) then
> -- insert child
> local old = curr.child
> curr.child = new_id ( )
> local new = zettels [ curr.child ]
> new.data = ' '
> new.next = old
> assert ( zettels [ old ] . prev == nil , " first child shouldn't have a previous sibling " )
> zettels [ old ] . prev = curr.child
> new.parent = curr
> current_zettel_id = curr.child
> render ( window ) -- recompute render_state
> editz ( window )
> end
> end
2022-02-10 08:04:12 +00:00
- __teliva_timestamp :
> Thu Feb 10 00 : 01 : 58 2022
update :
> function update ( window )
> local key = curses.getch ( )
> local h , w = window : getmaxyx ( )
> local curr = zettels [ current_zettel_id ]
> assert ( curr , string.format ( ' cursor fell off the edge of the world: %s ' , type ( current_zettel_id ) ) )
> -- graph-based navigation
> if key == string.byte ( ' j ' ) then
> if curr.child then
> current_zettel_id = curr.child
> elseif curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> elseif key == string.byte ( ' k ' ) then
> if curr.parent then current_zettel_id = curr.parent end
> elseif key == string.byte ( ' h ' ) then
> if curr.prev then
> current_zettel_id = curr.prev
> elseif curr.parent then
> current_zettel_id = curr.parent
> end
> elseif key == string.byte ( ' l ' ) then
> if curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> -- screen-based navigation
> elseif key == curses.KEY_UP then
> if render_state.curr_h > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h - 1 ]
> end
> elseif key == curses.KEY_DOWN then
> if render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ]
> end
> elseif key == curses.KEY_LEFT then
> if render_state.curr_w > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w - 1 ] [ render_state.curr_h ]
> end
> elseif key == curses.KEY_RIGHT then
> if render_state.wh2id [ render_state.curr_w + 1 ] and render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ]
> end
> --
> elseif key == string.byte ( ' e ' ) then
> editz ( window )
> elseif key == string.byte ( ' a ' ) then
> -- insert sibling after
> local old = curr.next
> curr.next = new_id ( )
> local new = zettels [ curr.next ]
> new.data = ' '
> new.next = old
> zettels [ old ] . prev = curr.next
> new.prev = current_zettel_id
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> new.parent = curr.parent
> current_zettel_id = curr.next
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' b ' ) then
> -- insert sibling before
> local old = curr.prev
> curr.prev = new_id ( )
> local new = zettels [ curr.prev ]
> new.data = ' '
> new.prev = old
> zettels [ old ] . next = curr.prev
> new.next = current_zettel_id
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> new.parent = curr.parent
> current_zettel_id = curr.prev
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' c ' ) then
> -- insert child
> local old = curr.child
> curr.child = new_id ( )
> local new = zettels [ curr.child ]
> new.data = ' '
> new.next = old
> assert ( zettels [ old ] . prev == nil , " first child shouldn't have a previous sibling " )
> zettels [ old ] . prev = curr.child
> new.parent = curr
> current_zettel_id = curr.child
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' x ' ) then
> if view_settings.width > 5 then
> view_settings.width = view_settings.width - 5
> end
> elseif key == string.byte ( ' X ' ) then
> if view_settings.width < w - 5 then
> view_settings.width = view_settings.width + 5
> end
> elseif key == string.byte ( ' y ' ) then
> if view_settings.height > 0 then
> view_settings.height = view_settings.height - 1
> end
> elseif key == string.byte ( ' Y ' ) then
> if view_settings.height < h - 2 then
> view_settings.height = view_settings.height + 1
> end
> end
> end
- __teliva_timestamp :
> Thu Feb 10 00 : 02 : 35 2022
menu :
>-- To show app - specific hotkeys in the menu bar , add hotkey / command
>-- arrays of strings to the menu array .
> menu = {
> { ' j ' , ' child ' } ,
> { ' k ' , ' parent ' } ,
> { ' l/h ' , ' next/prev sib ' } ,
> { ' e ' , ' edit ' } ,
> { ' a/b ' , ' insert sib ' } ,
> { ' c ' , ' insert child ' } ,
> { ' x/X/y/Y ' , ' resize ' } ,
> }
2022-02-10 15:35:28 +00:00
- __teliva_timestamp :
> Thu Feb 10 06 : 57 : 51 2022
__teliva_note :
> squeeze menu to make way for next feature
menu :
>-- To show app - specific hotkeys in the menu bar , add hotkey / command
>-- arrays of strings to the menu array .
> menu = {
> { ' a/b/c ' , ' insert sib/child ' } ,
> { ' e ' , ' edit ' } ,
> { ' j/k/l/h ' , ' move to child/parent/sib ' } ,
> { ' x/X/y/Y ' , ' resize ' } ,
> }
- __teliva_timestamp :
> Thu Feb 10 07 : 00 : 46 2022
__teliva_note :
> bugfix : handle missing parent / child / sib
update :
> function update ( window )
> local key = curses.getch ( )
> local h , w = window : getmaxyx ( )
> local curr = zettels [ current_zettel_id ]
> assert ( curr , string.format ( ' cursor fell off the edge of the world: %s ' , type ( current_zettel_id ) ) )
> -- graph-based navigation
> if key == string.byte ( ' j ' ) then
> if curr.child then
> current_zettel_id = curr.child
> elseif curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> elseif key == string.byte ( ' k ' ) then
> if curr.parent then current_zettel_id = curr.parent end
> elseif key == string.byte ( ' h ' ) then
> if curr.prev then
> current_zettel_id = curr.prev
> elseif curr.parent then
> current_zettel_id = curr.parent
> end
> elseif key == string.byte ( ' l ' ) then
> if curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> -- screen-based navigation
> elseif key == curses.KEY_UP then
> if render_state.curr_h > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h - 1 ]
> end
> elseif key == curses.KEY_DOWN then
> if render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ]
> end
> elseif key == curses.KEY_LEFT then
> if render_state.curr_w > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w - 1 ] [ render_state.curr_h ]
> end
> elseif key == curses.KEY_RIGHT then
> if render_state.wh2id [ render_state.curr_w + 1 ] and render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ]
> end
> --
> elseif key == string.byte ( ' e ' ) then
> editz ( window )
> elseif key == string.byte ( ' a ' ) then
> -- insert sibling after
> local old = curr.next
> curr.next = new_id ( )
> local new = zettels [ curr.next ]
> new.data = ' '
> new.next = old
> new.prev = current_zettel_id
> if old then
> zettels [ old ] . prev = curr.next
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> end
> new.parent = curr.parent
> current_zettel_id = curr.next
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' b ' ) then
> -- insert sibling before
> local old = curr.prev
> curr.prev = new_id ( )
> local new = zettels [ curr.prev ]
> new.data = ' '
> new.prev = old
> new.next = current_zettel_id
> if old then
> zettels [ old ] . next = curr.prev
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> end
> new.parent = curr.parent
> current_zettel_id = curr.prev
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' c ' ) then
> -- insert child
> local old = curr.child
> curr.child = new_id ( )
> local new = zettels [ curr.child ]
> new.data = ' '
> new.next = old
> if old then
> assert ( zettels [ old ] . prev == nil , " first child shouldn't have a previous sibling " )
> zettels [ old ] . prev = curr.child
> end
> new.parent = curr
> current_zettel_id = curr.child
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' x ' ) then
> if view_settings.width > 5 then
> view_settings.width = view_settings.width - 5
> end
> elseif key == string.byte ( ' X ' ) then
> if view_settings.width < w - 5 then
> view_settings.width = view_settings.width + 5
> end
> elseif key == string.byte ( ' y ' ) then
> if view_settings.height > 0 then
> view_settings.height = view_settings.height - 1
> end
> elseif key == string.byte ( ' Y ' ) then
> if view_settings.height < h - 2 then
> view_settings.height = view_settings.height + 1
> end
> end
> end
2022-02-10 15:35:54 +00:00
- __teliva_timestamp :
> Thu Feb 10 07 : 27 : 43 2022
write_zettels :
> function write_zettels ( outfile )
> outfile : write ( json.encode ( zettels ) )
> outfile : close ( )
> end
- __teliva_timestamp :
> Thu Feb 10 07 : 28 : 30 2022
read_zettels :
> function read_zettels ( infile )
> zettels = json.decode ( infile : read ( ' *a ' ) )
> infile : close ( )
> end
- __teliva_timestamp :
> Thu Feb 10 07 : 30 : 25 2022
__teliva_note :
> saving / loading zettels to / from disk
main :
> function main ( )
> init_colors ( )
> curses.curs_set ( 0 ) -- hide cursor except when editing
>
> -- read zettels from disk if possible
> local infile = io.open ( ' zet ' , ' r ' )
> if infile then
> read_zettels ( infile )
> else
> local outfile = io.open ( ' zet ' , ' w ' )
> if outfile then
> write_zettels ( outfile )
> end
> end
> current_zettel_id = zettels.root
>
> while true do
> render ( window )
> update ( window )
>
> -- save zettels, but hold on to previous state on disk
> -- until last possible second
> local filename = os.tmpname ( )
> local outfile = io.open ( filename , ' w ' )
> write_zettels ( outfile )
> os.rename ( filename , ' zet ' )
> end
> end
- __teliva_timestamp :
> Thu Feb 10 07 : 32 : 46 2022
__teliva_note :
> stop writing sample zettels to disk
>
> That was just for ease of testing write_zettels ( )
main :
> function main ( )
> init_colors ( )
> curses.curs_set ( 0 ) -- hide cursor except when editing
>
> local infile = io.open ( ' zet ' , ' r ' )
> if infile then
> read_zettels ( infile )
> end
> current_zettel_id = zettels.root
>
> while true do
> render ( window )
> update ( window )
>
> -- save zettels, but hold on to previous state on disk
> -- until last possible second
> local filename = os.tmpname ( )
> local outfile = io.open ( filename , ' w ' )
2022-02-11 00:30:00 +00:00
> if outfile then
> write_zettels ( outfile )
> os.rename ( filename , ' zet ' )
> end
2022-02-10 15:35:54 +00:00
> end
> end
2022-02-10 16:00:47 +00:00
- __teliva_timestamp :
> Thu Feb 10 07 : 43 : 39 2022
zettels :
>-- initial state of the zettels
>-- if you came here to clear the zettels ,
>-- delete everything ( ctrl - k and ctrl - u will delete a whole line at a time )
>-- until it looks like this :
>--
>-- zettels = {
>-- root = ' id1 ' ,
>-- final = 1 ,
>-- id1 = {
>-- data = ' ' ,
>-- } ,
>-- }
>--
>-- I don ' t yet trust any deletion feature I create to not mess up your data.
>-- Besides , this is a good excuse to start making this app your own .
>
> zettels = {
> root = ' id1 ' ,
> final = 5 ,
> id1 = {
> data = ' this is zettel A \n \n it has some text ' ,
> child = ' id3 ' ,
> next = ' id2 ' ,
> } ,
> id2 = {
> data = ' this is a sibling of zettel A at the top level ' ,
> prev = ' id1 ' ,
> next = ' id5 ' ,
> } ,
> id3 = {
> data = ' this is zettel B, a child of A ' ,
> parent = ' id1 ' ,
> next = ' id4 ' ,
> } ,
> id4 = {
> data = ' this is another child of zettel A, a sibling of B ' ,
> parent = ' id1 ' ,
> prev = ' id3 ' ,
> } ,
> id5 = {
> data = " (To clean up these sample zettels, hit ctrl-u and edit 'zettels') \n \n I don't yet trust any deletion feature I create to not mess up your data. \n Besides, this is a good excuse to start making this app your own.) " ,
> prev = ' id2 ' ,
> } ,
> }
2022-02-11 05:33:21 +00:00
- __teliva_timestamp :
> Thu Feb 10 20 : 24 : 13 2022
menu :
>-- To show app - specific hotkeys in the menu bar , add hotkey / command
>-- arrays of strings to the menu array .
> menu = {
> { ' a/b/c ' , ' insert ' } ,
> { ' e ' , ' edit ' } ,
> { ' j/k/l/h ' , ' move ' } ,
> { ' x/X/y/Y ' , ' resize ' } ,
> { ' s ' , ' stash ' } ,
> { ' t ' , ' link with stash ' } ,
> }
- __teliva_timestamp :
> Thu Feb 10 20 : 25 : 14 2022
stash :
> stash = nil
- __teliva_timestamp :
> Thu Feb 10 20 : 32 : 38 2022
update :
> function update ( window )
> local key = curses.getch ( )
> local h , w = window : getmaxyx ( )
> local curr = zettels [ current_zettel_id ]
> assert ( curr , string.format ( ' cursor fell off the edge of the world: %s ' , type ( current_zettel_id ) ) )
> -- move along the graph
> if key == string.byte ( ' j ' ) then
> if curr.child then
> current_zettel_id = curr.child
> elseif curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> elseif key == string.byte ( ' k ' ) then
> if curr.parent then current_zettel_id = curr.parent end
> elseif key == string.byte ( ' h ' ) then
> if curr.prev then
> current_zettel_id = curr.prev
> elseif curr.parent then
> current_zettel_id = curr.parent
> end
> elseif key == string.byte ( ' l ' ) then
> if curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> -- move along the screen
> elseif key == curses.KEY_UP then
> if render_state.curr_h > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h - 1 ]
> end
> elseif key == curses.KEY_DOWN then
> if render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ]
> end
> elseif key == curses.KEY_LEFT then
> if render_state.curr_w > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w - 1 ] [ render_state.curr_h ]
> end
> elseif key == curses.KEY_RIGHT then
> if render_state.wh2id [ render_state.curr_w + 1 ] and render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ]
> end
> -- mutations
> elseif key == string.byte ( ' e ' ) then
> editz ( window )
> elseif key == string.byte ( ' a ' ) then
> -- insert sibling after
> local old = curr.next
> curr.next = new_id ( )
> local new = zettels [ curr.next ]
> new.data = ' '
> new.next = old
> new.prev = current_zettel_id
> if old then
> zettels [ old ] . prev = curr.next
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> end
> new.parent = curr.parent
> current_zettel_id = curr.next
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' b ' ) then
> -- insert sibling before
> local old = curr.prev
> curr.prev = new_id ( )
> local new = zettels [ curr.prev ]
> new.data = ' '
> new.prev = old
> new.next = current_zettel_id
> if old then
> zettels [ old ] . next = curr.prev
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> end
> new.parent = curr.parent
> current_zettel_id = curr.prev
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' c ' ) then
> -- insert child
> local old = curr.child
> curr.child = new_id ( )
> local new = zettels [ curr.child ]
> new.data = ' '
> new.next = old
> if old then
> assert ( zettels [ old ] . prev == nil , " first child shouldn't have a previous sibling " )
> zettels [ old ] . prev = curr.child
> end
> new.parent = curr
> current_zettel_id = curr.child
> render ( window ) -- recompute render_state
> editz ( window )
> -- cross-links
> elseif key == string.byte ( ' s ' ) then
> -- save zettel to a stash
> stash = current_zettel_id
> elseif key == string.byte ( ' t ' ) then
> -- cross-link a zettel bidirectionally with what's on the stash
> if curr.crosslinks then
> curr.crosslinks . a = stash
> else
> curr.crosslinks = { a = stash }
> end
> -- view settings
> elseif key == string.byte ( ' x ' ) then
> if view_settings.width > 5 then
> view_settings.width = view_settings.width - 5
> end
> elseif key == string.byte ( ' X ' ) then
> if view_settings.width < w - 5 then
> view_settings.width = view_settings.width + 5
> end
> elseif key == string.byte ( ' y ' ) then
> if view_settings.height > 0 then
> view_settings.height = view_settings.height - 1
> end
> elseif key == string.byte ( ' Y ' ) then
> if view_settings.height < h - 2 then
> view_settings.height = view_settings.height + 1
> end
> end
> end
- __teliva_timestamp :
> Thu Feb 10 20 : 39 : 15 2022
render :
> function render ( window )
> window : clear ( )
> local lines , cols = window : getmaxyx ( )
> local bg = 1
> local y , x = 0 , 0 -- units of characters (0-based)
> local w , h = 1 , 1 -- units of zettels (1-based)
> -- render zettels depth-first, while tracking relative positions
> local done = { }
> local inprogress = { zettels.root }
> render_state.wh2id = { { } }
> while # inprogress > 0 do
> local currid = table.remove ( inprogress )
> if not done [ currid ] then
> done [ currid ] = true
> table.insert ( render_state.wh2id [ w ] , currid )
> local zettel = zettels [ currid ]
> if currid == current_zettel_id then
> render_state.curr_w = w
> render_state.curr_h = h
> end
> local currbg = ( currid == current_zettel_id ) and view_settings.current_zettel_bg or bg
> render_zettel ( window , currbg , depth ( zettel ) * view_settings.indent , y , x , zettel )
> if zettel.next then table.insert ( inprogress , zettel.next ) end
> if zettel.child then table.insert ( inprogress , zettel.child ) end
> if zettel.crosslinks then
> for relation , target in pairs ( zettel.crosslinks ) do
> table.insert ( inprogress , target )
> end
> end
> bg = 3 - bg -- toggle between color pairs 1 and 2
> y = y + view_settings.height + view_settings.vmargin
> h = h + 1
> if y + view_settings.height > lines then
> y = 0
> h = 1
> x = x + view_settings.width + view_settings.hmargin
> w = w + 1
> if x + view_settings.width > cols then break end
> table.insert ( render_state.wh2id , { } )
> end
> end
> end
> window : mvaddstr ( lines - 1 , 0 , ' ' )
> bg = 1
> x = 0
> for i = 1 , 3 do
> local zettel = nil
> if i == 1 and stash then
> zettel = zettels [ stash ]
> end
> render_zettel ( window , bg , 0 , lines - 1 , x , zettel )
> bg = 3 - bg
> x = x + view_settings.width + view_settings.hmargin
> end
> curses.refresh ( )
> end
- __teliva_timestamp :
> Thu Feb 10 20 : 40 : 08 2022
__teliva_note :
> initial support for cross - links
>
> Kinda confusing because zettels still show indent based on their
> hierarchical location rather than the path they ' re rendered in.
render_zettel :
> function render_zettel ( window , bg , indent , starty , startx , zettel )
> window : attrset ( curses.color_pair ( bg ) )
> for y = 0 , view_settings.height - 1 do
> for x = 0 , view_settings.width - 1 do
> window : mvaddch ( y + starty , x + startx , ' ' )
> end
> end
> local y , x = 0 , indent + 1
> local data = ' '
> if zettel then
> data = zettel.data
> end
> for i = 1 , # data do
> local c = data [ i ]
> if c == ' \n ' then
> y = y + 1
> x = indent + 1
> else
> window : mvaddstr ( y + starty , x + startx , c )
> x = x + 1
> if x >= startx + view_settings.width then
> y = y + 1
> x = indent + 1
> end
> end
> if y >= view_settings.height then
> break
> end
> end
> end
- __teliva_timestamp :
> Thu Feb 10 20 : 44 : 29 2022
__teliva_note :
> looks better after dynamically recomputing depth while rendering
render :
> function render ( window )
> window : clear ( )
> local lines , cols = window : getmaxyx ( )
> local bg = 1
> local y , x = 0 , 0 -- units of characters (0-based)
> local w , h = 1 , 1 -- units of zettels (1-based)
> -- render zettels depth-first, while tracking relative positions
> local done = { }
> local inprogress = { { id = zettels.root , depth = 0 } }
> render_state.wh2id = { { } }
> while # inprogress > 0 do
> local curr = table.remove ( inprogress )
> if not done [ curr.id ] then
> done [ curr.id ] = true
> table.insert ( render_state.wh2id [ w ] , curr.id )
> local zettel = zettels [ curr.id ]
> if curr.id == current_zettel_id then
> render_state.curr_w = w
> render_state.curr_h = h
> end
> local currbg = ( curr.id == current_zettel_id ) and view_settings.current_zettel_bg or bg
> render_zettel ( window , currbg , curr.depth * view_settings.indent , y , x , zettel )
> if zettel.next then table.insert ( inprogress , { id = zettel.next , depth = curr.depth } ) end
> if zettel.child then table.insert ( inprogress , { id = zettel.child , depth = curr.depth + 1 } ) end
> if zettel.crosslinks then
> for relation , target in pairs ( zettel.crosslinks ) do
> table.insert ( inprogress , { id = target , depth = curr.depth + 1 } )
> end
> end
> bg = 3 - bg -- toggle between color pairs 1 and 2
> y = y + view_settings.height + view_settings.vmargin
> h = h + 1
> if y + view_settings.height > lines then
> y = 0
> h = 1
> x = x + view_settings.width + view_settings.hmargin
> w = w + 1
> if x + view_settings.width > cols then break end
> table.insert ( render_state.wh2id , { } )
> end
> end
> end
> window : mvaddstr ( lines - 1 , 0 , ' ' )
> bg = 1
> x = 0
> for i = 1 , 3 do
> local zettel = nil
> if i == 1 and stash then
> zettel = zettels [ stash ]
> end
> render_zettel ( window , bg , 0 , lines - 1 , x , zettel )
> bg = 3 - bg
> x = x + view_settings.width + view_settings.hmargin
> end
> curses.refresh ( )
> end
- __teliva_timestamp :
> Thu Feb 10 20 : 55 : 19 2022
render_zettel :
> function render_zettel ( window , bg , indent , edge_label , starty , startx , zettel )
> window : attrset ( curses.color_pair ( bg ) )
> for y = 0 , view_settings.height - 1 do
> for x = 0 , view_settings.width - 1 do
> window : mvaddch ( y + starty , x + startx , ' ' )
> end
> end
> if indent > 1 then
> window : attrset ( curses.color_pair ( bg + 1 ) ) -- go from zettel color to its edge color
> window : mvaddstr ( starty , startx + indent - 1 , edge_label )
> window : attrset ( curses.color_pair ( bg ) )
> end
> local y , x = 0 , indent + 1
> local data = ' '
> if zettel then
> data = zettel.data
> end
> for i = 1 , # data do
> local c = data [ i ]
> if c == ' \n ' then
> y = y + 1
> x = indent + 1
> else
> window : mvaddstr ( y + starty , x + startx , c )
> x = x + 1
> if x >= startx + view_settings.width then
> y = y + 1
> x = indent + 1
> end
> end
> if y >= view_settings.height then
> break
> end
> end
> end
- __teliva_timestamp :
> Thu Feb 10 20 : 58 : 49 2022
view_settings :
> view_settings = {
> -- dimensions for rendering a single zettel; extra text gets truncated
> width = 50 ,
> height = 3 ,
> -- spacing between zettels
> hmargin = 1 ,
> vmargin = 1 ,
> --
> indent = 2 , -- how children of a zettel are indicated
> }
- __teliva_timestamp :
> Thu Feb 10 20 : 59 : 18 2022
render :
> function render ( window )
> window : clear ( )
> local lines , cols = window : getmaxyx ( )
> local bg = 3
> local y , x = 0 , 0 -- units of characters (0-based)
> local w , h = 1 , 1 -- units of zettels (1-based)
> -- render zettels depth-first, while tracking relative positions
> local done = { }
> local inprogress = { { id = zettels.root , depth = 0 , edge = ' ' } }
> render_state.wh2id = { { } }
> while # inprogress > 0 do
> local curr = table.remove ( inprogress )
> if not done [ curr.id ] then
> done [ curr.id ] = true
> table.insert ( render_state.wh2id [ w ] , curr.id )
> local zettel = zettels [ curr.id ]
> if curr.id == current_zettel_id then
> render_state.curr_w = w
> render_state.curr_h = h
> end
> local currbg = ( curr.id == current_zettel_id ) and 1 or bg -- 1 is the color pair for the current zettel
> render_zettel ( window , currbg , curr.depth * view_settings.indent , curr.edge , y , x , zettel )
> if zettel.next then table.insert ( inprogress , { id = zettel.next , depth = curr.depth , edge = ' | ' } ) end
> if zettel.child then table.insert ( inprogress , { id = zettel.child , depth = curr.depth + 1 , edge = ' \\ ' } ) end
> if zettel.crosslinks then
> for relation , target in pairs ( zettel.crosslinks ) do
> table.insert ( inprogress , { id = target , depth = curr.depth + 1 , edge = relation } )
> end
> end
> bg = 8 - bg -- toggle between color pairs 3 and 5
> y = y + view_settings.height + view_settings.vmargin
> h = h + 1
> if y + view_settings.height > lines then
> y = 0
> h = 1
> x = x + view_settings.width + view_settings.hmargin
> w = w + 1
> if x + view_settings.width > cols then break end
> table.insert ( render_state.wh2id , { } )
> end
> end
> end
> window : mvaddstr ( lines - 1 , 0 , ' ' )
> bg = 3
> x = 0
> for i = 1 , 3 do
> local zettel = nil
> if i == 1 and stash then
> zettel = zettels [ stash ]
> end
> render_zettel ( window , bg , 0 , ' ' , lines - 1 , x , zettel )
> bg = 8 - bg -- toggle between color pairs 3 and 5
> x = x + view_settings.width + view_settings.hmargin
> end
> curses.refresh ( )
> end
- __teliva_timestamp :
> Thu Feb 10 21 : 02 : 41 2022
__teliva_note :
> label the incoming edge for each zettel
>
> Is it a child , sibling or other cross - link ?
init_colors :
> function init_colors ( )
> -- light background
> -- current zettel
> curses.init_pair ( 1 , 236 , 230 )
> curses.init_pair ( 2 , 1 , 230 ) -- edge label for current zettel
> -- non-current zettel #1
> curses.init_pair ( 3 , 236 , 250 )
> curses.init_pair ( 4 , 1 , 250 ) -- edge label for pair 3
> -- non-current zettel #2
> curses.init_pair ( 5 , 236 , 252 )
> curses.init_pair ( 6 , 1 , 252 ) -- edge label for pair 5
> -- dark background
>-- ? -- current zettel
>-- ? curses.init_pair ( 7 , 252 , 130 )
>-- ? -- other zettels
>-- ? curses.init_pair ( 1 , 252 , 240 )
>-- ? curses.init_pair ( 2 , 252 , 242 )
>-- ? -- edge labels
>-- ? curses.init_pair ( 3 , 1 , 240 ) -- same bg as pair 1
>-- ? curses.init_pair ( 4 , 1 , 242 ) -- same bg as pair 2
>-- ? curses.init_pair ( 9 , 1 , 130 ) -- same bg as pair 7 for current zettel
> end
- __teliva_timestamp :
> Thu Feb 10 21 : 11 : 35 2022
menu :
>-- To show app - specific hotkeys in the menu bar , add hotkey / command
>-- arrays of strings to the menu array .
> menu = {
> { ' a/b/c ' , ' insert ' } ,
> { ' e ' , ' edit ' } ,
> { ' j/k/l/h ' , ' move ' } ,
> { ' x/X/y/Y ' , ' resize ' } ,
> { ' s ' , ' stash ' } ,
> { ' t ' , ' link with stash ' } ,
> { ' z ' , ' scroll ' } ,
> }
- __teliva_timestamp :
> Thu Feb 10 21 : 13 : 19 2022
main :
> function main ( )
> init_colors ( )
> curses.curs_set ( 0 ) -- hide cursor except when editing
>
> local infile = io.open ( ' zet ' , ' r ' )
> if infile then
> read_zettels ( infile )
> end
> current_zettel_id = zettels.root -- cursor
> view_settings.first_zettel = zettels.root -- start rendering here
>
> while true do
> render ( window )
> update ( window )
>
> -- save zettels, but hold on to previous state on disk
> -- until last possible second
> local filename = os.tmpname ( )
> local outfile = io.open ( filename , ' w ' )
> if outfile then
> write_zettels ( outfile )
> os.rename ( filename , ' zet ' )
> end
> end
> end
- __teliva_timestamp :
> Thu Feb 10 21 : 13 : 36 2022
render :
> function render ( window )
> window : clear ( )
> local lines , cols = window : getmaxyx ( )
> local bg = 3
> local y , x = 0 , 0 -- units of characters (0-based)
> local w , h = 1 , 1 -- units of zettels (1-based)
> -- render zettels depth-first, while tracking relative positions
> local done = { }
> local inprogress = { { id = view_settings.first_zettel , depth = 0 , edge = ' ' } }
> render_state.wh2id = { { } }
> while # inprogress > 0 do
> local curr = table.remove ( inprogress )
> if not done [ curr.id ] then
> done [ curr.id ] = true
> table.insert ( render_state.wh2id [ w ] , curr.id )
> local zettel = zettels [ curr.id ]
> if curr.id == current_zettel_id then
> render_state.curr_w = w
> render_state.curr_h = h
> end
> local currbg = ( curr.id == current_zettel_id ) and 1 or bg -- 1 is the color pair for the current zettel
> render_zettel ( window , currbg , curr.depth * view_settings.indent , curr.edge , y , x , zettel )
> if zettel.next then table.insert ( inprogress , { id = zettel.next , depth = curr.depth , edge = ' | ' } ) end
> if zettel.child then table.insert ( inprogress , { id = zettel.child , depth = curr.depth + 1 , edge = ' \\ ' } ) end
> if zettel.crosslinks then
> for relation , target in pairs ( zettel.crosslinks ) do
> table.insert ( inprogress , { id = target , depth = curr.depth + 1 , edge = relation } )
> end
> end
> bg = 8 - bg -- toggle between color pairs 3 and 5
> y = y + view_settings.height + view_settings.vmargin
> h = h + 1
> if y + view_settings.height > lines then
> y = 0
> h = 1
> x = x + view_settings.width + view_settings.hmargin
> w = w + 1
> if x + view_settings.width > cols then break end
> table.insert ( render_state.wh2id , { } )
> end
> end
> end
> window : mvaddstr ( lines - 1 , 0 , ' ' )
> bg = 3
> x = 0
> for i = 1 , 3 do
> local zettel = nil
> if i == 1 and stash then
> zettel = zettels [ stash ]
> end
> render_zettel ( window , bg , 0 , ' ' , lines - 1 , x , zettel )
> bg = 8 - bg -- toggle between color pairs 3 and 5
> x = x + view_settings.width + view_settings.hmargin
> end
> curses.refresh ( )
> end
- __teliva_timestamp :
> Thu Feb 10 21 : 19 : 26 2022
__teliva_note :
> bugfix : cross - links should be bidirectional
update :
> function update ( window )
> local key = curses.getch ( )
> local h , w = window : getmaxyx ( )
> local curr = zettels [ current_zettel_id ]
> assert ( curr , string.format ( ' cursor fell off the edge of the world: %s ' , type ( current_zettel_id ) ) )
> -- move along the graph
> if key == string.byte ( ' j ' ) then
> if curr.child then
> current_zettel_id = curr.child
> elseif curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> elseif key == string.byte ( ' k ' ) then
> if curr.parent then current_zettel_id = curr.parent end
> elseif key == string.byte ( ' h ' ) then
> if curr.prev then
> current_zettel_id = curr.prev
> elseif curr.parent then
> current_zettel_id = curr.parent
> end
> elseif key == string.byte ( ' l ' ) then
> if curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> -- move along the screen
> elseif key == curses.KEY_UP then
> if render_state.curr_h > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h - 1 ]
> end
> elseif key == curses.KEY_DOWN then
> if render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ]
> end
> elseif key == curses.KEY_LEFT then
> if render_state.curr_w > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w - 1 ] [ render_state.curr_h ]
> end
> elseif key == curses.KEY_RIGHT then
> if render_state.wh2id [ render_state.curr_w + 1 ] and render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ]
> end
> -- mutations
> elseif key == string.byte ( ' e ' ) then
> editz ( window )
> elseif key == string.byte ( ' a ' ) then
> -- insert sibling after
> local old = curr.next
> curr.next = new_id ( )
> local new = zettels [ curr.next ]
> new.data = ' '
> new.next = old
> new.prev = current_zettel_id
> if old then
> zettels [ old ] . prev = curr.next
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> end
> new.parent = curr.parent
> current_zettel_id = curr.next
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' b ' ) then
> -- insert sibling before
> local old = curr.prev
> curr.prev = new_id ( )
> local new = zettels [ curr.prev ]
> new.data = ' '
> new.prev = old
> new.next = current_zettel_id
> if old then
> zettels [ old ] . next = curr.prev
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> end
> new.parent = curr.parent
> current_zettel_id = curr.prev
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' c ' ) then
> -- insert child
> local old = curr.child
> curr.child = new_id ( )
> local new = zettels [ curr.child ]
> new.data = ' '
> new.next = old
> if old then
> assert ( zettels [ old ] . prev == nil , " first child shouldn't have a previous sibling " )
> zettels [ old ] . prev = curr.child
> end
> new.parent = curr
> current_zettel_id = curr.child
> render ( window ) -- recompute render_state
> editz ( window )
> -- cross-links
> elseif key == string.byte ( ' s ' ) then
> -- save zettel to a stash
> stash = current_zettel_id
> elseif key == string.byte ( ' t ' ) then
> -- cross-link a zettel bidirectionally with what's on the stash
> local insert_crosslink =
> function ( a , rel , b_id )
> if a.crosslinks == nil then
> a.crosslinks = { }
> end
> a.crosslinks [ rel ] = b_id
> end
> insert_crosslink ( curr , ' a ' , stash )
> insert_crosslink ( zettels [ stash ] , ' a ' , current_zettel_id )
> -- view settings
> elseif key == string.byte ( ' x ' ) then
> if view_settings.width > 5 then
> view_settings.width = view_settings.width - 5
> end
> elseif key == string.byte ( ' X ' ) then
> if view_settings.width < w - 5 then
> view_settings.width = view_settings.width + 5
> end
> elseif key == string.byte ( ' y ' ) then
> if view_settings.height > 0 then
> view_settings.height = view_settings.height - 1
> end
> elseif key == string.byte ( ' Y ' ) then
> if view_settings.height < h - 2 then
> view_settings.height = view_settings.height + 1
> end
> elseif key == string.byte ( ' z ' ) then
> -- scroll to show the current zettel at top of screen
> -- often has the effect of zooming in on its hierarchy
> view_settings.first_zettel = current_zettel_id
> end
> end
- __teliva_timestamp :
> Thu Feb 10 21 : 20 : 45 2022
__teliva_note :
> clear stash after linking
update :
> function update ( window )
> local key = curses.getch ( )
> local h , w = window : getmaxyx ( )
> local curr = zettels [ current_zettel_id ]
> assert ( curr , string.format ( ' cursor fell off the edge of the world: %s ' , type ( current_zettel_id ) ) )
> -- move along the graph
> if key == string.byte ( ' j ' ) then
> if curr.child then
> current_zettel_id = curr.child
> elseif curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> elseif key == string.byte ( ' k ' ) then
> if curr.parent then current_zettel_id = curr.parent end
> elseif key == string.byte ( ' h ' ) then
> if curr.prev then
> current_zettel_id = curr.prev
> elseif curr.parent then
> current_zettel_id = curr.parent
> end
> elseif key == string.byte ( ' l ' ) then
> if curr.next then
> current_zettel_id = curr.next
> elseif curr.parent and zettels [ curr.parent ] . next then
> current_zettel_id = zettels [ curr.parent ] . next
> end
> -- move along the screen
> elseif key == curses.KEY_UP then
> if render_state.curr_h > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h - 1 ]
> end
> elseif key == curses.KEY_DOWN then
> if render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w ] [ render_state.curr_h + 1 ]
> end
> elseif key == curses.KEY_LEFT then
> if render_state.curr_w > 1 then
> current_zettel_id = render_state.wh2id [ render_state.curr_w - 1 ] [ render_state.curr_h ]
> end
> elseif key == curses.KEY_RIGHT then
> if render_state.wh2id [ render_state.curr_w + 1 ] and render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ] then
> current_zettel_id = render_state.wh2id [ render_state.curr_w + 1 ] [ render_state.curr_h ]
> end
> -- mutations
> elseif key == string.byte ( ' e ' ) then
> editz ( window )
> elseif key == string.byte ( ' a ' ) then
> -- insert sibling after
> local old = curr.next
> curr.next = new_id ( )
> local new = zettels [ curr.next ]
> new.data = ' '
> new.next = old
> new.prev = current_zettel_id
> if old then
> zettels [ old ] . prev = curr.next
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> end
> new.parent = curr.parent
> current_zettel_id = curr.next
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' b ' ) then
> -- insert sibling before
> local old = curr.prev
> curr.prev = new_id ( )
> local new = zettels [ curr.prev ]
> new.data = ' '
> new.prev = old
> new.next = current_zettel_id
> if old then
> zettels [ old ] . next = curr.prev
> assert ( curr.parent == zettels [ old ] . parent , ' siblings should have same parent ' )
> end
> new.parent = curr.parent
> current_zettel_id = curr.prev
> render ( window ) -- recompute render_state
> editz ( window )
> elseif key == string.byte ( ' c ' ) then
> -- insert child
> local old = curr.child
> curr.child = new_id ( )
> local new = zettels [ curr.child ]
> new.data = ' '
> new.next = old
> if old then
> assert ( zettels [ old ] . prev == nil , " first child shouldn't have a previous sibling " )
> zettels [ old ] . prev = curr.child
> end
> new.parent = curr
> current_zettel_id = curr.child
> render ( window ) -- recompute render_state
> editz ( window )
> -- cross-links
> elseif key == string.byte ( ' s ' ) then
> -- save zettel to a stash
> stash = current_zettel_id
> elseif key == string.byte ( ' t ' ) then
> -- cross-link a zettel bidirectionally with what's on the stash
> local insert_crosslink =
> function ( a , rel , b_id )
> if a.crosslinks == nil then
> a.crosslinks = { }
> end
> a.crosslinks [ rel ] = b_id
> end
> insert_crosslink ( curr , ' a ' , stash )
> insert_crosslink ( zettels [ stash ] , ' a ' , current_zettel_id )
> stash = nil
> -- view settings
> elseif key == string.byte ( ' x ' ) then
> if view_settings.width > 5 then
> view_settings.width = view_settings.width - 5
> end
> elseif key == string.byte ( ' X ' ) then
> if view_settings.width < w - 5 then
> view_settings.width = view_settings.width + 5
> end
> elseif key == string.byte ( ' y ' ) then
> if view_settings.height > 0 then
> view_settings.height = view_settings.height - 1
> end
> elseif key == string.byte ( ' Y ' ) then
> if view_settings.height < h - 2 then
> view_settings.height = view_settings.height + 1
> end
> elseif key == string.byte ( ' z ' ) then
> -- scroll to show the current zettel at top of screen
> -- often has the effect of zooming in on its hierarchy
> view_settings.first_zettel = current_zettel_id
> end
> end
2022-02-11 07:40:51 +00:00
- __teliva_timestamp :
> Thu Feb 10 21 : 51 : 09 2022
__teliva_note :
> fix regression in editor
editz_render :
> function editz_render ( window , s , cursor , top , minbottom , left , right )
> local h , w = window : getmaxyx ( )
> local cursor_y , cursor_x = 0 , 0
> window : attrset ( curses.color_pair ( 1 ) ) -- 1 is the color combination for the current zettel
> for y = top , minbottom - 1 do
> for x = left , right - 1 do
> window : mvaddch ( y , x , ' ' )
> end
> end
> local y , x = top , left + 1 -- left padding; TODO: indent
> window : mvaddstr ( y , x , ' ' )
> for i = 1 , string.len ( s ) do
> if i == cursor then
> cursor_y = y
> cursor_x = x
> end
> if s [ i ] ~= ' \n ' then
> window : addstr ( s [ i ] )
> x = x + 1
> if x >= right then
> y = y + 1
> if y >= h - 2 then return end
> x = left + 1 -- left padding; TODO: indent
> window : mvaddstr ( y , x , ' ' )
> end
> else
> for col = x + 1 , right - 1 do window : addch ( ' ' ) ; end
> x = left
> y = y + 1
> if y >= h - 2 then return end
> window : mvaddstr ( y , x , ' ' )
> for col = x , right - 1 do window : addch ( ' ' ) ; end
> x = left + 1 -- left padding; TODO: indent
> window : mvaddstr ( y , x , ' ' )
> end
> end
> if cursor_y == 0 and cursor_x == 0 then
> cursor_y = y
> cursor_x = x
> end
> window : mvaddstr ( cursor_y , cursor_x , ' ' )
> end
- __teliva_timestamp :
> Fri Feb 11 01 : 33 : 31 2022
__teliva_note :
> support / tmp being on a separate volume
>
> also better error - checking
main :
> function main ( )
> init_colors ( )
> curses.curs_set ( 0 ) -- hide cursor except when editing
>
> local infile = io.open ( ' zet ' , ' r ' )
> if infile then
> read_zettels ( infile )
> end
> current_zettel_id = zettels.root -- cursor
> view_settings.first_zettel = zettels.root -- start rendering here
>
> while true do
> render ( window )
> update ( window )
>
> -- save zettels, but hold on to previous state on disk
> -- until last possible second
> local outfile = io.open ( ' teliva_tmp ' , ' w ' )
> if outfile then
> write_zettels ( outfile )
> local status , message = os.rename ( ' teliva_tmp ' , ' zet ' )
> assert ( status , message ) -- unceremoniously abort, but we hopefully only lost a little
> end
> -- TODO: what if io.open failed for a non-sandboxing related reason?!
> -- We could silently fail to save.
> end
> end