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
2022-03-07 00:38:49 +00:00
> return str : sub ( i , i )
2022-02-05 07:57:05 +00:00
> else
> return string [ i ]
> end
> end
>
>-- ranges using ( ) , selected bytes using { }
> getmetatable ( ' ' ) . __call = function ( str , i , j )
> if type ( i ) ~= ' table ' then
2022-03-07 00:38:49 +00:00
> return str : sub ( i , j )
2022-02-05 07:57:05 +00:00
> else
> local t = { }
> for k , v in ipairs ( i ) do
2022-03-07 00:38:49 +00:00
> t [ k ] = str : sub ( v , v )
2022-02-05 07:57:05 +00:00
> 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
>
2022-03-18 17:37:29 +00:00
> function string . pos ( s , sub )
> return string.find ( s , sub , 1 , true ) -- plain=true to disable regular expressions
> end
>
2022-02-05 07:57:05 +00:00
>-- 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
2022-03-09 06:42:32 +00:00
- __teliva_timestamp : original
check :
> function check ( x , msg )
> if x then
> Window : addch ( ' . ' )
> else
> print ( ' F - ' .. msg )
> print ( ' ' .. str ( x ) .. ' is false/nil ' )
> 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
2022-02-05 07:57:05 +00:00
- __teliva_timestamp : original
check_eq :
> function check_eq ( x , expected , msg )
2022-03-06 10:42:34 +00:00
> if eq ( x , expected ) then
2022-03-03 06:15:01 +00:00
> Window : addch ( ' . ' )
2022-02-05 07:57:05 +00:00
> else
> print ( ' F - ' .. msg )
2022-03-06 10:42:34 +00:00
> print ( ' expected ' .. str ( expected ) .. ' but got ' .. str ( x ) )
2022-02-05 07:57:05 +00:00
> 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
2022-03-06 10:42:34 +00:00
- __teliva_timestamp : original
eq :
> function eq ( a , b )
> if type ( a ) ~= type ( b ) then return false end
> if type ( a ) == ' table ' then
> if # a ~= # b then return false end
> for k , v in pairs ( a ) do
> if b [ k ] ~= v then
> return false
> end
> end
2022-03-19 07:19:58 +00:00
> for k , v in pairs ( b ) do
> if a [ k ] ~= v then
> return false
> end
> end
> return true
2022-03-06 10:42:34 +00:00
> end
> return a == b
> end
- __teliva_timestamp : original
str :
>-- smarter tostring
>-- slow ; used only for debugging
> function str ( x )
> if type ( x ) == ' table ' then
> local result = ' '
> result = result .. # x .. ' { '
> for k , v in pairs ( x ) do
> result = result .. str ( k ) .. ' = ' .. str ( v ) .. ' , '
> end
> result = result .. ' } '
> return result
2022-03-18 06:30:05 +00:00
> elseif type ( x ) == ' string ' then
> return ' " ' .. x .. ' " '
2022-03-06 10:42:34 +00:00
> end
> return tostring ( x )
> end
2022-03-27 18:42:19 +00:00
- __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
2022-02-05 07:57:05 +00:00
- __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 :
2022-03-19 04:22:18 +00:00
> function filter ( h , f )
> result = { }
> for k , v in pairs ( h ) do
> if f ( k , v ) then
> result [ k ] = v
> end
> end
> return result
> end
- __teliva_timestamp : original
ifilter :
2022-02-05 07:57:05 +00:00
>-- only for arrays
2022-03-19 04:22:18 +00:00
> function ifilter ( l , f )
2022-02-05 07:57:05 +00:00
> result = { }
> for _ , x in ipairs ( l ) do
> if f ( x ) then
> table.insert ( result , x )
> end
> end
> return result
> end
- __teliva_timestamp : original
2022-03-06 10:42:34 +00:00
sort_letters :
> function sort_letters ( s )
> tmp = { }
> for i = 1 , # s do
> table.insert ( tmp , s [ i ] )
> end
> table.sort ( tmp )
> local result = ' '
> for _ , c in pairs ( tmp ) do
> result = result .. c
2022-02-05 07:57:05 +00:00
> end
2022-03-06 10:42:34 +00:00
> return result
> end
>
> function test_sort_letters ( s )
> check_eq ( sort_letters ( ' ' ) , ' ' , ' test_sort_letters: empty ' )
> check_eq ( sort_letters ( ' ba ' ) , ' ab ' , ' test_sort_letters: non-empty ' )
> check_eq ( sort_letters ( ' abba ' ) , ' aabb ' , ' test_sort_letters: duplicates ' )
2022-02-05 07:57:05 +00:00
> end
- __teliva_timestamp : original
2022-03-06 10:42:34 +00:00
count_letters :
2022-03-27 17:53:41 +00:00
>-- TODO : handle unicode
2022-03-06 10:42:34 +00:00
> function count_letters ( s )
> local result = { }
2022-03-07 00:38:49 +00:00
> for i = 1 , s : len ( ) do
2022-03-06 10:42:34 +00:00
> local c = s [ i ]
> if result [ c ] == nil then
> result [ c ] = 1
> else
> result [ c ] = result [ c ] + 1
> end
> end
> return result
> end
2022-03-27 17:53:41 +00:00
- __teliva_timestamp : original
count :
>-- turn an array of elements into a map from elements to their frequency
>-- analogous to count_letters for non - strings
> function count ( a )
> local result = { }
> for i , v in ipairs ( a ) do
> if result [ v ] == nil then
> result [ v ] = 1
> else
> result [ v ] = result [ v ] + 1
> end
> end
> return result
> end
2022-03-19 23:59:30 +00:00
- __teliva_timestamp : original
union :
> function union ( a , b )
> for k , v in pairs ( b ) do
> a [ k ] = v
> end
> return a
> end
- __teliva_timestamp : original
subtract :
>-- set subtraction
> function subtract ( a , b )
> for k , v in pairs ( b ) do
> a [ k ] = nil
> end
> return a
> end
- __teliva_timestamp : original
all :
>-- universal quantifier on sets
> function all ( s , f )
> for k , v in pairs ( s ) do
> if not f ( k , v ) then
> return false
> end
> end
> return true
> end
- __teliva_timestamp : original
to_array :
>-- turn a set into an array
>-- drops values
> function to_array ( h )
> local result = { }
> for k , _ in pairs ( h ) do
> table.insert ( result , k )
> end
> return result
> end
2022-03-06 10:42:34 +00:00
- __teliva_timestamp : original
append :
>-- concatenate list ' elems ' into ' l ' , modifying ' l ' in the process
> function append ( l , elems )
> for i = 1 , # elems do
2022-03-19 03:55:07 +00:00
> table.insert ( l , elems [ i ] )
2022-03-06 10:42:34 +00:00
> end
> end
2022-03-19 23:59:30 +00:00
- __teliva_timestamp : original
prepend :
>-- concatenate list ' elems ' into the start of ' l ' , modifying ' l ' in the process
> function prepend ( l , elems )
> for i = 1 , # elems do
> table.insert ( l , i , elems [ i ] )
> end
> end
2022-03-19 07:19:58 +00:00
- __teliva_timestamp : original
all_but :
> function all_but ( x , idx )
> if type ( x ) == ' table ' then
> local result = { }
> for i , elem in ipairs ( x ) do
> if i ~= idx then
> table.insert ( result , elem )
> end
> end
> return result
> elseif type ( x ) == ' string ' then
> if idx < 1 then return x : sub ( 1 ) end
> return x : sub ( 1 , idx - 1 ) .. x : sub ( idx + 1 )
> else
> error ( ' all_but: unsupported type ' .. type ( x ) )
> end
> end
>
> function test_all_but ( )
> check_eq ( all_but ( ' ' , 0 ) , ' ' , ' all_but: empty ' )
> check_eq ( all_but ( ' abc ' , 0 ) , ' abc ' , ' all_but: invalid low index ' )
> check_eq ( all_but ( ' abc ' , 4 ) , ' abc ' , ' all_but: invalid high index ' )
> check_eq ( all_but ( ' abc ' , 1 ) , ' bc ' , ' all_but: first index ' )
> check_eq ( all_but ( ' abc ' , 3 ) , ' ab ' , ' all_but: final index ' )
> check_eq ( all_but ( ' abc ' , 2 ) , ' ac ' , ' all_but: middle index ' )
> end
- __teliva_timestamp : original
set :
> function set ( l )
> local result = { }
> for i , elem in ipairs ( l ) do
> result [ elem ] = true
> end
> return result
> end
- __teliva_timestamp : original
set_eq :
> function set_eq ( l1 , l2 )
> return eq ( set ( l1 ) , set ( l2 ) )
> end
>
> function test_set_eq ( )
> check ( set_eq ( { 1 } , { 1 } ) , ' set_eq: identical ' )
> check ( not set_eq ( { 1 , 2 } , { 1 , 3 } ) , ' set_eq: different ' )
> check ( set_eq ( { 1 , 2 } , { 2 , 1 } ) , ' set_eq: order ' )
> check ( set_eq ( { 1 , 2 , 2 } , { 2 , 1 } ) , ' set_eq: duplicates ' )
> end
- __teliva_timestamp : original
clear :
> function clear ( lines )
> while # lines > 0 do
> table.remove ( lines )
> end
> end
- __teliva_timestamp : original
zap :
> function zap ( target , src )
> clear ( target )
> append ( target , src )
> end
2022-03-27 18:24:56 +00:00
- __teliva_timestamp : original
mfactorial :
>-- memoized version of factorial
>-- doesn ' t memoize recursive calls, but may be good enough
> mfactorial = memo1 ( factorial )
- __teliva_timestamp : original
factorial :
> function factorial ( n )
> local result = 1
> for i = 1 , n do
> result = result * i
> end
> return result
> end
- __teliva_timestamp : original
memo1 :
>-- a higher - order function that takes a function of a single arg
>-- ( that never returns nil )
>-- and returns a memoized version of it
> function memo1 ( f )
> local memo = { }
> return function ( x )
> if memo [ x ] == nil then
> memo [ x ] = f ( x )
> end
> return memo [ x ]
> end
> end
>
>-- mfactorial doesn ' t seem noticeably faster
> function test_memo1 ( )
> for i = 0 , 30 do
> check_eq ( mfactorial ( i ) , factorial ( i ) , ' memo1 over factorial: ' .. str ( i ) )
> end
> end
- __teliva_timestamp : original
num_permutations :
>-- number of permutations of n distinct objects , taken r at a time
> function num_permutations ( n , r )
> return factorial ( n ) / factorial ( n - r )
> end
>
>-- mfactorial doesn ' t seem noticeably faster
> function test_memo1 ( )
> for i = 0 , 30 do
> for j = 0 , i do
> check_eq ( num_permutations ( i , j ) , mfactorial ( i ) / mfactorial ( i - j ) , ' num_permutations memoizes: ' .. str ( i ) .. ' P ' .. str ( j ) )
> end
> end
> end
2022-02-05 07:57:05 +00:00
- __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-03-06 10:42:34 +00:00
- __teliva_timestamp : original
Window :
> Window = curses.stdscr ( )
- __teliva_timestamp : original
window :
>-- constructor for fake screen and window
>-- call it like this :
>-- local w = window {
>-- kbd = kbd ( ' abc ' ) ,
>-- scr = scr { h = 5 , w = 4 } ,
>-- }
>-- eventually it ' ll do everything a real ncurses window can
> function window ( h )
> h.__index = h
> setmetatable ( h , h )
> h.__index = function ( table , key )
> return rawget ( h , key )
> end
2022-03-09 06:42:32 +00:00
> h.attrset = function ( self , x )
2022-03-10 12:22:49 +00:00
> self.scr . attrs = x
2022-03-09 06:42:32 +00:00
> end
> h.attron = function ( self , x )
> -- currently same as attrset since Lua 5.1 doesn't have bitwise operators
> -- doesn't support multiple attrs at once
>-- local old = self.scr . attrs
>-- self.scr . attrs = old | x
> self.scr . attrs = x
> end
> h.attroff = function ( self , x )
> -- currently borked since Lua 5.1 doesn't have bitwise operators
> -- doesn't support multiple attrs at once
>-- local old = self.scr . attrs
>-- self.scr . attrs = old & ( ~ x )
> self.scr . attrs = curses.A_NORMAL
> end
2022-03-06 10:42:34 +00:00
> h.getch = function ( self )
2022-03-18 06:30:05 +00:00
> local c = table.remove ( h.kbd , 1 )
> if c == nil then return c end
> return string.byte ( c ) -- for verisimilitude with ncurses
2022-03-06 10:42:34 +00:00
> end
> h.addch = function ( self , c )
> local scr = self.scr
2022-03-18 06:30:05 +00:00
> if c == ' \n ' then
> scr.cursy = scr.cursy + 1
> scr.cursx = 0
> return
> end
2022-03-06 10:42:34 +00:00
> if scr.cursy <= scr.h then
2022-03-09 06:42:32 +00:00
> scr [ scr.cursy ] [ scr.cursx ] = { data = c , attrs = scr.attrs }
2022-03-06 10:42:34 +00:00
> scr.cursx = scr.cursx + 1
> if scr.cursx > scr.w then
> scr.cursy = scr.cursy + 1
> scr.cursx = 1
> end
> end
> end
> h.addstr = function ( self , s )
2022-03-07 00:38:49 +00:00
> for i = 1 , s : len ( ) do
2022-03-06 10:42:34 +00:00
> self : addch ( s [ i ] )
> end
> end
> h.mvaddch = function ( self , y , x , c )
> self.scr . cursy = y
> self.scr . cursx = x
2022-03-10 12:22:49 +00:00
> self : addch ( c )
2022-03-06 10:42:34 +00:00
> end
> h.mvaddstr = function ( self , y , x , s )
> self.scr . cursy = y
> self.scr . cursx = x
> self : addstr ( s )
> end
2022-03-10 12:22:49 +00:00
> h.clear = function ( self )
> clear_scr ( self.scr )
> end
2022-03-18 06:30:05 +00:00
> h.refresh = function ( self )
> -- nothing
> end
2022-03-06 10:42:34 +00:00
> return h
> end
- __teliva_timestamp : original
kbd :
> function kbd ( keys )
> local result = { }
2022-03-07 00:38:49 +00:00
> for i = 1 , keys : len ( ) do
2022-03-06 10:42:34 +00:00
> table.insert ( result , keys [ i ] )
> end
> return result
> end
- __teliva_timestamp : original
scr :
> function scr ( props )
> props.cursx = 1
> props.cursy = 1
2022-03-10 12:22:49 +00:00
> clear_scr ( props )
> return props
> end
- __teliva_timestamp : original
clear_scr :
> function clear_scr ( props )
2022-03-18 06:30:05 +00:00
> props.cursy = 1
> props.cursx = 1
2022-03-06 10:42:34 +00:00
> for y = 1 , props.h do
> props [ y ] = { }
> for x = 1 , props.w do
2022-03-09 06:42:32 +00:00
> props [ y ] [ x ] = { data = ' ' , attrs = curses.A_NORMAL }
2022-03-06 10:42:34 +00:00
> end
> end
> return props
> end
- __teliva_timestamp : original
check_screen :
> function check_screen ( window , contents , message )
> local x , y = 1 , 1
2022-03-07 00:38:49 +00:00
> for i = 1 , contents : len ( ) do
2022-03-10 12:22:49 +00:00
> check_eq ( window.scr [ y ] [ x ] . data , contents [ i ] , message .. ' / ' .. y .. ' , ' .. x )
2022-03-06 10:42:34 +00:00
> x = x + 1
> if x > window.scr . w then
> y = y + 1
> x = 1
> end
> end
> end
>
>-- putting it all together , an example test of both keyboard and screen
> function test_check_screen ( )
> local lines = {
> c = ' 123 ' ,
> d = ' 234 ' ,
> a = ' 345 ' ,
> b = ' 456 ' ,
> }
> local w = window {
> kbd = kbd ( ' abc ' ) ,
> scr = scr { h = 3 , w = 5 } ,
> }
> local y = 1
> while true do
2022-03-18 06:30:05 +00:00
> local b = w : getch ( )
> if b == nil then break end
> w : mvaddstr ( y , 1 , lines [ string.char ( b ) ] )
2022-03-06 10:42:34 +00:00
> y = y + 1
> end
> check_screen ( w , ' 345 ' ..
> ' 456 ' ..
> ' 123 ' ,
> ' test_check_screen ' )
> end
2022-03-09 06:42:32 +00:00
- __teliva_timestamp : original
check_reverse :
> function check_reverse ( window , contents , message )
> local x , y = 1 , 1
> for i = 1 , contents : len ( ) do
> if contents [ i ] ~= ' ' then
> -- hacky version while we're without bitwise operators on Lua 5.1
>-- check ( window.scr [ y ] [ x ] . attrs & curses.A_REVERSE , message .. ' / ' .. y .. ' , ' .. x )
> check_eq ( window.scr [ y ] [ x ] . attrs , curses.A_REVERSE , message .. ' / ' .. y .. ' , ' .. x )
> else
> -- hacky version while we're without bitwise operators on Lua 5.1
>-- check ( window.scr [ y ] [ x ] . attrs & ( ~ curses.A_REVERSE ) , message .. ' / ' .. y .. ' , ' .. x )
> check ( window.scr [ y ] [ x ] . attrs ~= curses.A_REVERSE , message .. ' / ' .. y .. ' , ' .. x )
> end
> x = x + 1
> if x > window.scr . w then
> y = y + 1
> x = 1
> end
> end
> end
- __teliva_timestamp : original
check_bold :
> function check_bold ( window , contents , message )
> local x , y = 1 , 1
> for i = 1 , contents : len ( ) do
> if contents [ i ] ~= ' ' then
> -- hacky version while we're without bitwise operators on Lua 5.1
>-- check ( window.scr [ y ] [ x ] . attrs & curses.A_BOLD , message .. ' / ' .. y .. ' , ' .. x )
> check_eq ( window.scr [ y ] [ x ] . attrs , curses.A_BOLD , message .. ' / ' .. y .. ' , ' .. x )
> else
> -- hacky version while we're without bitwise operators on Lua 5.1
>-- check ( window.scr [ y ] [ x ] . attrs & ( ~ curses.A_BOLD ) , message .. ' / ' .. y .. ' , ' .. x )
> check ( window.scr [ y ] [ x ] . attrs ~= curses.A_BOLD , message .. ' / ' .. y .. ' , ' .. x )
> end
> x = x + 1
> if x > window.scr . w then
> y = y + 1
> x = 1
> end
> end
> end
- __teliva_timestamp : original
check_color :
>-- check which parts of a screen have the given color_pair
> function check_color ( window , cp , contents , message )
> local x , y = 1 , 1
> for i = 1 , contents : len ( ) do
> if contents [ i ] ~= ' ' then
> -- hacky version while we're without bitwise operators on Lua 5.1
>-- check ( window.scr [ y ] [ x ] . attrs & curses.color_pair ( cp ) , message .. ' / ' .. y .. ' , ' .. x )
> check_eq ( window.scr [ y ] [ x ] . attrs , curses.color_pair ( cp ) , message .. ' / ' .. y .. ' , ' .. x )
> else
> -- hacky version while we're without bitwise operators on Lua 5.1
>-- check ( window.scr [ y ] [ x ] . attrs & ( ~ curses.A_BOLD ) , message .. ' / ' .. y .. ' , ' .. x )
> check ( window.scr [ y ] [ x ] . attrs ~= curses.color_pair ( cp ) , message .. ' / ' .. y .. ' , ' .. x )
> end
> x = x + 1
> if x > window.scr . w then
> y = y + 1
> x = 1
> end
> end
> end
2022-03-06 10:42:34 +00:00
- __teliva_timestamp : original
spaces :
> function spaces ( n )
> for i = 1 , n do
> Window : addch ( ' ' )
> end
> end
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
2022-03-03 06:15:01 +00:00
> render ( Window )
> update ( Window )
2022-02-05 07:57:05 +00:00
> 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 )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-05 07:57:05 +00:00
> 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 , ' ? ' )
2022-02-27 16:38:48 +00:00
> window : refresh ( )
2022-02-05 07:57:05 +00:00
> 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 :
2022-03-03 06:15:01 +00:00
> function editz ( window )
2022-02-08 08:28:20 +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
> 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 , ' ' )
2022-03-07 00:38:49 +00:00
> for i = 1 , s : len ( ) do
2022-02-08 08:28:20 +00:00
> -- 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
2022-03-07 00:38:49 +00:00
> if cursor > s : len ( ) then
2022-02-08 08:28:20 +00:00
> 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 )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-08 08:28:20 +00:00
> 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 )
2022-03-07 00:38:49 +00:00
> local max = s : len ( )
2022-02-08 08:28:20 +00:00
> 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 )
2022-03-07 00:38:49 +00:00
> local max = s : len ( )
2022-02-08 08:28:20 +00:00
> 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
2022-02-27 16:38:48 +00:00
> window : refresh ( )
2022-02-09 16:30:19 +00:00
> 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
2022-03-03 06:15:01 +00:00
> render ( Window )
> update ( Window )
2022-02-09 16:30:19 +00:00
> 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 :
2022-03-03 06:15:01 +00:00
> function editz ( window )
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
> 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 , ' ' )
2022-03-07 00:38:49 +00:00
> for i = 1 , s : len ( ) do
2022-02-09 16:30:19 +00:00
> 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 :
2022-03-03 06:15:01 +00:00
> function editz ( window )
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-03-07 00:38:49 +00:00
> local cursor = zettels [ current_zettel_id ] . data : len ( ) + 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 )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-09 16:30:19 +00:00
> 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 ' } ,
2022-02-27 17:58:05 +00:00
> { ' l,h ' , ' next/prev sib ' } ,
2022-02-10 07:49:59 +00:00
> { ' 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 )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-10 07:49:59 +00:00
> 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 ' } ,
2022-02-27 17:58:05 +00:00
> { ' l,h ' , ' next/prev sib ' } ,
2022-02-10 07:49:59 +00:00
> { ' e ' , ' edit ' } ,
2022-02-27 17:58:05 +00:00
> { ' a,b ' , ' insert sib ' } ,
2022-02-10 07:49:59 +00:00
> { ' 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 )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-10 07:49:59 +00:00
> 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 )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-10 08:04:12 +00:00
> 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 ' } ,
2022-02-27 17:58:05 +00:00
> { ' l,h ' , ' next/prev sib ' } ,
2022-02-10 08:04:12 +00:00
> { ' e ' , ' edit ' } ,
2022-02-27 17:58:05 +00:00
> { ' a,b ' , ' insert sib ' } ,
2022-02-10 08:04:12 +00:00
> { ' c ' , ' insert child ' } ,
2022-02-27 17:58:05 +00:00
> { ' x,X,y,Y ' , ' resize ' } ,
2022-02-10 08:04:12 +00:00
> }
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 = {
2022-02-27 17:58:05 +00:00
> { ' a,b,c ' , ' insert sib/child ' } ,
2022-02-10 15:35:28 +00:00
> { ' e ' , ' edit ' } ,
2022-02-27 17:58:05 +00:00
> { ' j,k,l,h ' , ' move to child/parent/sib ' } ,
> { ' x,X,y,Y ' , ' resize ' } ,
2022-02-10 15:35:28 +00:00
> }
- __teliva_timestamp :
> Thu Feb 10 07 : 00 : 46 2022
__teliva_note :
> bugfix : handle missing parent / child / sib
update :
> function update ( window )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-10 15:35:28 +00:00
> 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
2022-03-03 06:15:01 +00:00
> render ( Window )
> update ( Window )
2022-02-10 15:35:54 +00:00
>
> -- 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
2022-03-03 06:15:01 +00:00
> render ( Window )
> update ( Window )
2022-02-10 15:35:54 +00:00
>
> -- 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 = {
2022-02-27 17:58:05 +00:00
> { ' a,b,c ' , ' insert ' } ,
2022-02-11 05:33:21 +00:00
> { ' e ' , ' edit ' } ,
2022-02-27 17:58:05 +00:00
> { ' j,k,l,h ' , ' move ' } ,
> { ' x,X,y,Y ' , ' resize ' } ,
2022-02-11 05:33:21 +00:00
> { ' 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 )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-11 05:33:21 +00:00
> 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
2022-02-27 16:38:48 +00:00
> window : refresh ( )
2022-02-11 05:33:21 +00:00
> 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
2022-02-27 16:38:48 +00:00
> window : refresh ( )
2022-02-11 05:33:21 +00:00
> 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
2022-02-27 16:38:48 +00:00
> window : refresh ( )
2022-02-11 05:33:21 +00:00
> 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 = {
2022-02-27 17:58:05 +00:00
> { ' a,b,c ' , ' insert ' } ,
2022-02-11 05:33:21 +00:00
> { ' e ' , ' edit ' } ,
2022-02-27 17:58:05 +00:00
> { ' j,k,l,h ' , ' move ' } ,
> { ' x,X,y,Y ' , ' resize ' } ,
2022-02-11 05:33:21 +00:00
> { ' 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
2022-03-03 06:15:01 +00:00
> render ( Window )
> update ( Window )
2022-02-11 05:33:21 +00:00
>
> -- 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
2022-02-27 16:38:48 +00:00
> window : refresh ( )
2022-02-11 05:33:21 +00:00
> end
- __teliva_timestamp :
> Thu Feb 10 21 : 19 : 26 2022
__teliva_note :
> bugfix : cross - links should be bidirectional
update :
> function update ( window )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-11 05:33:21 +00:00
> 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 )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-11 05:33:21 +00:00
> 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 , ' ' )
2022-03-07 00:38:49 +00:00
> for i = 1 , s : len ( ) do
2022-02-11 07:40:51 +00:00
> 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
2022-03-03 06:15:01 +00:00
> render ( Window )
> update ( Window )
2022-02-11 07:40:51 +00:00
>
> -- 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
2022-02-11 18:40:24 +00:00
- __teliva_timestamp :
> Fri Feb 11 07 : 51 : 42 2022
__teliva_note :
> bugfix in parent link when inserting child
update :
> function update ( window )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-11 18:40:24 +00:00
> 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 = current_zettel_id
> 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-12 23:56:12 +00:00
- __teliva_timestamp :
> Sat Feb 12 15 : 11 : 15 2022
editz_update :
> function editz_update ( window , prose , cursor , original_prose )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-12 23:56:12 +00:00
> local h , w = window : getmaxyx ( )
> -- cursor movement
> 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 == 1 then -- ctrl-a
> elseif key == 12 then -- ctrl-l
> elseif key == 6 then -- ctrl-f
> elseif key == 2 then -- ctrl-b
> -- delete
> elseif key == 11 then -- ctrl-k
> -- exit
> elseif key == 5 then -- ctrl-e
> return true , prose , cursor
> elseif key == 7 then -- ctrl-g
> return true , original_prose , cursor
> -- insert
> 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 :
> Sat Feb 12 15 : 11 : 33 2022
editz :
2022-03-03 06:15:01 +00:00
> function editz ( window )
2022-02-12 23:56:12 +00:00
> local old_menu = menu
> menu = {
> { ' ^e ' , ' finish edit ' } ,
> { ' ^g ' , ' cancel edit ' } ,
> { ' ^a ' , ' <<line ' } ,
> { ' ^b ' , ' <word ' } ,
> { ' ^f ' , ' word> ' } ,
> { ' ^l ' , ' line>> ' } ,
> { ' ^k ' , ' del to line>> ' } ,
> }
2022-03-07 00:38:49 +00:00
> local old_data = zettels [ current_zettel_id ] . data : sub ( 1 )
2022-02-12 23:56:12 +00:00
> 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-03-07 00:38:49 +00:00
> local cursor = zettels [ current_zettel_id ] . data : len ( ) + 1
2022-02-12 23:56:12 +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 , old_data )
> end
> curses.curs_set ( 0 )
> menu = old_menu
> end
- __teliva_timestamp :
2022-02-27 17:47:09 +00:00
> Sat Feb 12 15 : 55 : 10 2022
__teliva_note :
> editor : move to start of line , move / delete to end of line
2022-02-12 23:56:12 +00:00
editz_update :
> function editz_update ( window , prose , cursor , original_prose )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-12 23:56:12 +00:00
> local h , w = window : getmaxyx ( )
> -- cursor movement
> 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 == 1 then -- ctrl-a
> while cursor > 1 do
> if prose [ cursor - 1 ] == ' \n ' then break end
> cursor = cursor - 1
> end
> elseif key == 12 then -- ctrl-l
2022-03-07 00:38:49 +00:00
> local max = prose : len ( )
2022-02-12 23:56:12 +00:00
> while cursor <= max and prose [ cursor ] ~= ' \n ' do
> cursor = cursor + 1
> end
> elseif key == 6 then -- ctrl-f
> elseif key == 2 then -- ctrl-b
> -- delete
> elseif key == 11 then -- ctrl-k
2022-03-07 00:38:49 +00:00
> while cursor <= prose : len ( ) and prose [ cursor ] ~= ' \n ' do
2022-02-27 17:47:09 +00:00
> prose = prose : remove ( cursor )
> end
2022-02-12 23:56:12 +00:00
> -- exit
> elseif key == 5 then -- ctrl-e
> return true , prose , cursor
> elseif key == 7 then -- ctrl-g
> return true , original_prose , cursor
> -- insert
> 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 :
2022-02-27 17:47:09 +00:00
> Sat Feb 12 17 : 01 : 45 2022
__teliva_note :
> editor : word - movement shortcuts
2022-02-12 23:56:12 +00:00
editz_update :
> function editz_update ( window , prose , cursor , original_prose )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-12 23:56:12 +00:00
> local h , w = window : getmaxyx ( )
> -- cursor movement
> 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 == 1 then -- ctrl-a
2022-02-27 17:47:09 +00:00
> -- to start of line
2022-02-12 23:56:12 +00:00
> while cursor > 1 do
> if prose [ cursor - 1 ] == ' \n ' then break end
> cursor = cursor - 1
> end
> elseif key == 12 then -- ctrl-l
2022-02-27 17:47:09 +00:00
> -- to end of line
2022-03-07 00:38:49 +00:00
> local max = prose : len ( )
2022-02-12 23:56:12 +00:00
> while cursor <= max and prose [ cursor ] ~= ' \n ' do
> cursor = cursor + 1
> end
> elseif key == 6 then -- ctrl-f
2022-02-27 17:47:09 +00:00
> -- to next word
2022-03-07 00:38:49 +00:00
> local max = prose : len ( )
> while cursor <= max and prose [ cursor ] : match ( ' %w ' ) do
2022-02-12 23:56:12 +00:00
> cursor = cursor + 1
> end
2022-03-07 00:38:49 +00:00
> while cursor <= max and prose [ cursor ] : match ( ' %W ' ) do
2022-02-27 17:47:09 +00:00
> cursor = cursor + 1
2022-02-13 01:59:30 +00:00
> end
> elseif key == 2 then -- ctrl-b
> -- to previous word
2022-03-07 00:38:49 +00:00
> if cursor > prose : len ( ) then
> cursor = prose : len ( )
2022-02-13 01:59:30 +00:00
> end
2022-03-07 00:38:49 +00:00
> while cursor > 1 and prose [ cursor ] : match ( ' %W ' ) do
2022-02-27 17:47:09 +00:00
> cursor = cursor - 1
2022-02-13 01:59:30 +00:00
> end
2022-03-07 00:38:49 +00:00
> while cursor > 1 and prose [ cursor ] : match ( ' %w ' ) do
2022-02-27 17:47:09 +00:00
> cursor = cursor - 1
2022-02-13 01:59:30 +00:00
> end
2022-02-27 17:47:09 +00:00
> -- delete
> elseif key == 11 then -- ctrl-k
2022-03-07 00:38:49 +00:00
> while cursor <= prose : len ( ) and prose [ cursor ] ~= ' \n ' do
2022-02-27 17:47:09 +00:00
> prose = prose : remove ( cursor )
2022-02-13 01:59:30 +00:00
> end
2022-02-27 17:47:09 +00:00
> -- exit
> elseif key == 5 then -- ctrl-e
> return true , prose , cursor
> elseif key == 7 then -- ctrl-g
> return true , original_prose , cursor
> -- insert
> 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 :
> Sat Feb 12 17 : 12 : 27 2022
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 , ' ' )
2022-02-13 01:59:30 +00:00
> end
2022-02-27 17:47:09 +00:00
> end
> for x = left , right - 1 do
> window : mvaddch ( minbottom , x , ' ' )
> end
> local y , x = top , left + 1 -- left padding; TODO: indent
> window : mvaddstr ( y , x , ' ' )
2022-03-07 00:38:49 +00:00
> for i = 1 , s : len ( ) do
2022-02-27 17:47:09 +00:00
> if i == cursor then
> cursor_y = y
> cursor_x = x
2022-02-13 01:59:30 +00:00
> end
2022-02-27 17:47:09 +00:00
> 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 , ' ' )
2022-02-13 01:59:30 +00:00
> end
2022-02-27 17:47:09 +00:00
> 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 :
> Sat Feb 12 17 : 15 : 15 2022
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 = { } ,
> -- list of zettels currently displayed
> displayed = { } ,
> }
- __teliva_timestamp :
> Sat Feb 12 17 : 16 : 20 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 = { { } }
> render_state.displayed = { }
> while # inprogress > 0 do
> local curr = table.remove ( inprogress )
> if not done [ curr.id ] then
> done [ curr.id ] = true
> render_state.displayed [ 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 } )
2022-02-13 01:59:30 +00:00
> end
> end
2022-02-27 17:47:09 +00:00
> 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
2022-02-13 01:59:30 +00:00
> end
2022-02-27 17:47:09 +00:00
> 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 ]
2022-02-13 01:59:30 +00:00
> end
2022-02-27 17:47:09 +00:00
> 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
2022-02-13 01:59:30 +00:00
> end
2022-02-27 17:47:09 +00:00
> window : refresh ( )
2022-02-13 01:59:30 +00:00
> end
- __teliva_timestamp :
2022-02-27 17:47:09 +00:00
> Sat Feb 12 17 : 18 : 34 2022
__teliva_note :
> scroll as needed when moving along the graph
2022-02-13 01:59:30 +00:00
update :
> function update ( window )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-13 01:59:30 +00:00
> 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
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> end
> elseif key == string.byte ( ' k ' ) then
> if curr.parent then
> current_zettel_id = curr.parent
> end
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> 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
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> 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
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> 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 = current_zettel_id
> 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
- __teliva_timestamp :
2022-02-27 17:47:09 +00:00
> Sat Feb 12 17 : 23 : 33 2022
__teliva_note :
> editor ' k ' shortcut : fall back to next sibling if needed
>
> Now we should be able to navigate either with j / k or h / l .
2022-02-13 01:59:30 +00:00
update :
> function update ( window )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-13 01:59:30 +00:00
> 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
> -- child or next sibling
> 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
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> end
> elseif key == string.byte ( ' k ' ) then
> -- parent or previous sibling
> if curr.parent then
> current_zettel_id = curr.parent
> elseif curr.prev then
> current_zettel_id = curr.prev
> end
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> end
> elseif key == string.byte ( ' h ' ) then
> -- previous sibling or parent
> if curr.prev then
> current_zettel_id = curr.prev
> elseif curr.parent then
> current_zettel_id = curr.parent
> end
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> end
> elseif key == string.byte ( ' l ' ) then
> -- next sibling or next sibling of parent
> 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
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> 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 = current_zettel_id
> 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-27 17:47:09 +00:00
- __teliva_timestamp :
> Sat Feb 12 17 : 27 : 18 2022
menu :
>-- To show app - specific hotkeys in the menu bar , add hotkey / command
>-- arrays of strings to the menu array .
> menu = {
2022-02-27 17:58:05 +00:00
> { ' a,b,c ' , ' insert ' } ,
2022-02-27 17:47:09 +00:00
> { ' e ' , ' edit ' } ,
2022-02-27 17:58:05 +00:00
> { ' j,k,l,h ' , ' move ' } ,
2022-02-27 17:47:09 +00:00
> { ' < ' , ' back ' } ,
2022-02-27 17:58:05 +00:00
> { ' x,X,y,Y ' , ' resize ' } ,
2022-02-27 17:47:09 +00:00
> { ' s ' , ' stash ' } ,
> { ' t ' , ' link with stash ' } ,
> { ' z ' , ' scroll ' } ,
> }
2022-02-13 01:59:30 +00:00
- __teliva_timestamp :
> Sat Feb 12 17 : 57 : 15 2022
update :
> function update ( window )
2022-02-27 16:38:48 +00:00
> local key = window : getch ( )
2022-02-13 01:59:30 +00:00
> 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 ) ) )
> -- read from or write to render_state.history
> if key == string.byte ( ' < ' ) then
> -- previous zettel moved to
> -- does NOT undo mutations
> if # render_state.history > 0 then
> local previous_state = render_state.history [ # render_state.history ]
> view_settings.first_zettel = previous_state.first_zettel
> current_zettel_id = previous_state.cursor
> table.remove ( render_state.history )
> end
> return
> end
> if key ~= string.byte ( ' e ' ) then
> table.insert ( render_state.history , { first_zettel = view_settings.first_zettel , cursor = current_zettel_id } )
> end
> -- move along the graph
> if key == string.byte ( ' j ' ) then
> -- child or next sibling
> 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
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> end
> elseif key == string.byte ( ' k ' ) then
> -- parent or previous sibling
> if curr.parent then
> current_zettel_id = curr.parent
> elseif curr.prev then
> current_zettel_id = curr.prev
> end
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> end
> elseif key == string.byte ( ' h ' ) then
> -- previous sibling or parent
> if curr.prev then
> current_zettel_id = curr.prev
> elseif curr.parent then
> current_zettel_id = curr.parent
> end
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> end
> elseif key == string.byte ( ' l ' ) then
> -- next sibling or next sibling of parent
> 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
> -- scroll if necessary
> if not render_state.displayed [ current_zettel_id ] then
> view_settings.first_zettel = current_zettel_id
> 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 = current_zettel_id
> 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
- __teliva_timestamp :
> Sat Feb 12 17 : 58 : 01 2022
__teliva_note :
> make cursor movements less risky using a back button ' < '
render_state :
>-- some information about what ' s been drawn on screen
>-- not saved between app restarts
> 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 = { } ,
> -- list of zettels currently displayed
> displayed = { } ,
> -- history of screen render state
> history = { } , -- elems {first_zettel=view_settings.first_zettel, cursor=current_zettel_id}
> }
2022-02-18 04:16:36 +00:00
- __teliva_timestamp :
> Thu Feb 17 20 : 15 : 14 2022
doc : blurb :
> A rudimentary Zettelkasten app trying to hew very close to the original analog setup , as described by abramdemski :
>
> https : // www.lesswrong . com / posts / NfdHG6oHBJ8Qxc26s / the - zettelkasten - method - 1
>
> The key attributes of Zettelkasten seem to be :
>- notes organized in small fragments called ' cards ' that can ' t hold much text
>- a tree - based organization using sibling and child cards , with the ability to insert children and siblings to any card , any time
>- ability to cross - link any card to any other , turning the tree into a graph ( but still with a strong sense of hierarchy )
>
> zet.tlv satisfies these properties , but isn ' t very intuitive or usable yet. Contributions appreciated.
2022-03-07 16:22:37 +00:00
- __teliva_timestamp :
> Mon Mar 7 07 : 50 : 32 2022
main :
> function main ( )
> init_colors ( )
> curses.curs_set ( 0 ) -- hide cursor except when editing
>
> local infile = start_reading ( nil , ' zet ' )
> 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
- __teliva_timestamp :
> Mon Mar 7 07 : 51 : 06 2022
__teliva_note :
> switch to new file API for reading
read_zettels :
> function read_zettels ( infile )
> zettels = jsonf.decode ( infile )
> end
2022-03-07 18:35:23 +00:00
- __teliva_timestamp :
> Mon Mar 7 10 : 31 : 27 2022
main :
> function main ( )
> init_colors ( )
> curses.curs_set ( 0 ) -- hide cursor except when editing
>
> -- load any saved zettels
> local infile = start_reading ( nil , ' zet ' )
> 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
> local outfile = start_writing ( nil , ' zet ' )
> if outfile then
> write_zettels ( outfile )
> end
> -- TODO: what if io.open failed for a non-sandboxing related reason?!
> -- We could silently fail to save.
> end
> end
- __teliva_timestamp :
> Mon Mar 7 10 : 32 : 08 2022
__teliva_note :
> switch to new file API for writing
write_zettels :
> function write_zettels ( outfile )
2022-03-17 00:03:38 +00:00
> outfile.write ( json.encode ( zettels ) )
> outfile.close ( )
2022-03-07 18:35:23 +00:00
> end
2022-03-10 12:22:49 +00:00
- __teliva_timestamp :
> Thu Mar 10 04 : 21 : 28 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 >= 2 then -- need at least 2 spaces to be able to print edge_label
> 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
>
2022-03-10 23:38:12 +00:00
> function test_render_zettel_single_line_topleft ( )
2022-03-10 12:22:49 +00:00
> local w = window { scr = scr { h = 5 , w = 10 } }
> render_zettel ( w , 34 , 1 , -- color 34, indent 1
> ' * ' , 1 , 1 , -- startx, starty
> { data = ' abc ' } )
> check_screen ( w , ' abc ' ..
> ' ' ..
> ' ' ..
> ' ' ..
> ' ' ,
2022-03-10 23:38:12 +00:00
> ' render_zettel: single line zettel from top-left of screen ' )
2022-03-10 12:22:49 +00:00
> -- entire width is used by the single zettel
> -- column 1 = margin, column 2 = indent
> check_color ( w , 34 , ' ########## ' ..
> ' ########## ' ..
> ' ########## ' ..
> ' ' ..
> ' ' ,
2022-03-10 23:38:12 +00:00
> ' render_zettel: single line zettel from top-left of screen, background ' )
> end
>
> function test_render_zettel_from_middle_of_screen ( )
> local w = window { scr = scr { h = 5 , w = 10 } }
2022-03-10 12:22:49 +00:00
> render_zettel ( w , 34 , 1 , ' * ' , 3 , 4 , { data = ' abc ' } ) -- startx=3, starty=4
> check_screen ( w , ' ' ..
> ' ' ..
> ' abc ' ..
> ' ' ..
> ' ' ,
2022-03-10 23:38:12 +00:00
> ' render_zettel from middle of screen ' )
2022-03-10 12:22:49 +00:00
> check_color ( w , 34 , ' ' ..
> ' ' ..
> ' ####### ' ..
> ' ####### ' ..
> ' ####### ' ,
2022-03-10 23:38:12 +00:00
> ' render_zettel from middle of screen, background ' )
> end
>
> function test_render_zettel_indented_prints_edge_label ( )
> local w = window { scr = scr { h = 5 , w = 10 } }
2022-03-10 12:22:49 +00:00
> render_zettel ( w , 34 , 2 , ' * ' , 3 , 4 , { data = ' abc ' } ) -- startx=3, starty=4
> check_screen ( w , ' ' ..
> ' ' ..
> ' * abc ' ..
> ' ' ..
> ' ' ,
2022-03-10 23:38:12 +00:00
> ' render_zettel: indent >= 2 prints edge label ' )
> end
>
> function test_render_zettel_crops_long_lines ( )
> local w = window { scr = scr { h = 5 , w = 10 } }
> render_zettel ( w , 34 , 2 , ' * ' , 3 , 4 , { data = ' abc def ' } ) -- startx=3, starty=4
> check_screen ( w , ' ' ..
> ' ' ..
> ' * abc ' ..
> ' ' ..
> ' ' ,
> ' render_zettel: crops long lines ' )
> end
>
> function test_render_zettel_multiple_lines ( )
> local w = window { scr = scr { h = 5 , w = 10 } }
> render_zettel ( w , 34 , 2 , ' * ' , 3 , 4 , { data = ' abc \n def ' } ) -- startx=3, starty=4
> check_screen ( w , ' ' ..
> ' ' ..
> ' * abc ' ..
> ' def ' ..
> ' ' ,
> ' render_zettel: multiple lines ' )
> end
>
> function test_render_zettel_truncates_extra_lines ( )
> local w = window { scr = scr { h = 5 , w = 10 } }
> render_zettel ( w , 34 , 2 , ' * ' , 3 , 4 , { data = ' a \n b \n c \n d ' } ) -- startx=3, starty=4
> check_screen ( w , ' ' ..
> ' ' ..
> ' * a ' ..
> ' b ' ..
> ' c ' ,
> ' render_zettel: truncates extra lines ' )
2022-03-10 12:22:49 +00:00
> end