1 # .tlv file generated by https://github.com/akkartik/teliva
2 # You may edit it if you are careful; however, you may see cryptic errors if you
3 # violate Teliva's assumptions.
5 # .tlv files are representations of Teliva programs. Teliva programs consist of
6 # sequences of definitions. Each definition is a table of key/value pairs. Keys
7 # and values are both strings.
9 # Lines in .tlv files always follow exactly one of the following forms:
10 # - comment lines at the top of the file starting with '#' at column 0
11 # - beginnings of definitions starting with '- ' at column 0, followed by a
13 # - key/value pairs consisting of ' ' at column 0, containing either a
14 # spaceless value on the same line, or a multi-line value
15 # - multiline values indented by more than 2 spaces, starting with a '>'
17 # If these constraints are violated, Teliva may unceremoniously crash. Please
18 # report bugs at http://akkartik.name/contact
19 - __teliva_timestamp: original
21 >-- some string helpers from http://lua-users.org/wiki/StringIndexing
23 >-- index characters using []
24 >getmetatable('').__index = function(str,i)
25 > if type(i) == 'number' then
32 >-- ranges using (), selected bytes using {}
33 >getmetatable('').__call = function(str,i,j)
34 > if type(i)~='table' then
38 > for k,v in ipairs(i) do
41 > return table.concat(t)
45 >-- iterate over an ordered sequence
47 > if type(x) == 'string' then
48 > return x:gmatch('.')
54 >-- insert within string
55 >function string.insert(str1, str2, pos)
56 > return str1:sub(1,pos)..str2..str1:sub(pos+1)
59 >function string.remove(s, pos)
60 > return s:sub(1,pos-1)..s:sub(pos+1)
63 >function string.pos(s, sub)
64 > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions
67 >-- TODO: backport utf-8 support from Lua 5.3
68 - __teliva_timestamp: original
71 - __teliva_timestamp: original
73 >-- helper for debug by print; overlay debug information towards the right
74 >-- reset debugy every time you refresh screen
75 >function dbg(window, s)
78 > oldy, oldx = window:getyx()
79 > window:mvaddstr(debugy, 60, s)
81 > window:mvaddstr(oldy, oldx, '')
83 - __teliva_timestamp: original
85 >function check(x, msg)
90 > print(' '..str(x)..' is false/nil')
91 > teliva_num_test_failures = teliva_num_test_failures + 1
92 > -- overlay first test failure on editors
93 > if teliva_first_failure == nil then
94 > teliva_first_failure = msg
98 - __teliva_timestamp: original
100 >function check_eq(x, expected, msg)
101 > if eq(x, expected) then
105 > print(' expected '..str(expected)..' but got '..str(x))
106 > teliva_num_test_failures = teliva_num_test_failures + 1
107 > -- overlay first test failure on editors
108 > if teliva_first_failure == nil then
109 > teliva_first_failure = msg
113 - __teliva_timestamp: original
116 > if type(a) ~= type(b) then return false end
117 > if type(a) == 'table' then
118 > if #a ~= #b then return false end
119 > for k, v in pairs(a) do
124 > for k, v in pairs(b) do
133 - __teliva_timestamp: original
136 >-- slow; used only for debugging
138 > if type(x) == 'table' then
140 > result = result..#x..'{'
141 > for k, v in pairs(x) do
142 > result = result..str(k)..'='..str(v)..', '
144 > result = result..'}'
146 > elseif type(x) == 'string' then
151 - __teliva_timestamp: original
153 >function find_index(arr, x)
154 > for n, y in ipairs(arr) do
160 - __teliva_timestamp: original
163 > return s:gsub('^%s*', ''):gsub('%s*$', '')
165 - __teliva_timestamp: original
167 >function split(s, d)
169 > for match in (s..d):gmatch("(.-)"..d) do
170 > table.insert(result, match);
174 - __teliva_timestamp: original
179 > for _, x in ipairs(l) do
180 > table.insert(result, f(x))
184 - __teliva_timestamp: original
187 >function reduce(l, f, init)
189 > for _, x in ipairs(l) do
190 > result = f(result, x)
194 - __teliva_timestamp: original
196 >function filter(h, f)
198 > for k, v in pairs(h) do
205 - __teliva_timestamp: original
208 >function ifilter(l, f)
210 > for _, x in ipairs(l) do
212 > table.insert(result, x)
217 - __teliva_timestamp: original
219 >function sort_letters(s)
222 > table.insert(tmp, s[i])
226 > for _, c in pairs(tmp) do
232 >function test_sort_letters(s)
233 > check_eq(sort_letters(''), '', 'test_sort_letters: empty')
234 > check_eq(sort_letters('ba'), 'ab', 'test_sort_letters: non-empty')
235 > check_eq(sort_letters('abba'), 'aabb', 'test_sort_letters: duplicates')
237 - __teliva_timestamp: original
239 >-- TODO: handle unicode
240 >function count_letters(s)
244 > if result[c] == nil then
247 > result[c] = result[c] + 1
252 - __teliva_timestamp: original
254 >-- turn an array of elements into a map from elements to their frequency
255 >-- analogous to count_letters for non-strings
258 > for i, v in ipairs(a) do
259 > if result[v] == nil then
262 > result[v] = result[v] + 1
267 - __teliva_timestamp: original
269 >function union(a, b)
270 > for k, v in pairs(b) do
275 - __teliva_timestamp: original
278 >function subtract(a, b)
279 > for k, v in pairs(b) do
284 - __teliva_timestamp: original
286 >-- universal quantifier on sets
288 > for k, v in pairs(s) do
289 > if not f(k, v) then
295 - __teliva_timestamp: original
297 >-- turn a set into an array
299 >function to_array(h)
301 > for k, _ in pairs(h) do
302 > table.insert(result, k)
306 - __teliva_timestamp: original
308 >-- concatenate list 'elems' into 'l', modifying 'l' in the process
309 >function append(l, elems)
311 > table.insert(l, elems[i])
314 - __teliva_timestamp: original
316 >-- concatenate list 'elems' into the start of 'l', modifying 'l' in the process
317 >function prepend(l, elems)
319 > table.insert(l, i, elems[i])
322 - __teliva_timestamp: original
324 >function all_but(x, idx)
325 > if type(x) == 'table' then
327 > for i, elem in ipairs(x) do
329 > table.insert(result,elem)
333 > elseif type(x) == 'string' then
334 > if idx < 1 then return x:sub(1) end
335 > return x:sub(1, idx-1) .. x:sub(idx+1)
337 > error('all_but: unsupported type '..type(x))
341 >function test_all_but()
342 > check_eq(all_but('', 0), '', 'all_but: empty')
343 > check_eq(all_but('abc', 0), 'abc', 'all_but: invalid low index')
344 > check_eq(all_but('abc', 4), 'abc', 'all_but: invalid high index')
345 > check_eq(all_but('abc', 1), 'bc', 'all_but: first index')
346 > check_eq(all_but('abc', 3), 'ab', 'all_but: final index')
347 > check_eq(all_but('abc', 2), 'ac', 'all_but: middle index')
349 - __teliva_timestamp: original
353 > for i, elem in ipairs(l) do
354 > result[elem] = true
358 - __teliva_timestamp: original
360 >function set_eq(l1, l2)
361 > return eq(set(l1), set(l2))
364 >function test_set_eq()
365 > check(set_eq({1}, {1}), 'set_eq: identical')
366 > check(not set_eq({1, 2}, {1, 3}), 'set_eq: different')
367 > check(set_eq({1, 2}, {2, 1}), 'set_eq: order')
368 > check(set_eq({1, 2, 2}, {2, 1}), 'set_eq: duplicates')
370 - __teliva_timestamp: original
372 >function clear(lines)
373 > while #lines > 0 do
374 > table.remove(lines)
377 - __teliva_timestamp: original
379 >function zap(target, src)
381 > append(target, src)
383 - __teliva_timestamp: original
385 >-- memoized version of factorial
386 >-- doesn't memoize recursive calls, but may be good enough
387 >mfactorial = memo1(factorial)
388 - __teliva_timestamp: original
390 >function factorial(n)
397 - __teliva_timestamp: original
399 >-- a higher-order function that takes a function of a single arg
400 >-- (that never returns nil)
401 >-- and returns a memoized version of it
405 > if memo[x] == nil then
412 >-- mfactorial doesn't seem noticeably faster
413 >function test_memo1()
415 > check_eq(mfactorial(i), factorial(i), 'memo1 over factorial: '..str(i))
418 - __teliva_timestamp: original
420 >-- number of permutations of n distinct objects, taken r at a time
421 >function num_permutations(n, r)
422 > return factorial(n)/factorial(n-r)
425 >-- mfactorial doesn't seem noticeably faster
426 >function test_memo1()
429 > check_eq(num_permutations(i, j), mfactorial(i)/mfactorial(i-j), 'num_permutations memoizes: '..str(i)..'P'..str(j))
433 - __teliva_timestamp: original
435 >-- To show app-specific hotkeys in the menu bar, add hotkey/command
436 >-- arrays of strings to the menu array.
439 > {'^w', 'write prose to file "toot" (edit hotkey does NOT save)'},
441 - __teliva_timestamp: original
443 >Window = curses.stdscr()
444 >curses.curs_set(0) -- we'll simulate our own cursor
445 - __teliva_timestamp: original
455 - __teliva_timestamp: original
457 >function init_colors()
459 > curses.init_pair(i, i, -1)
461 > curses.init_pair(8, 7, 0)
462 > curses.init_pair(9, 7, 1)
463 > curses.init_pair(10, 7, 2)
464 > curses.init_pair(11, 7, 3)
465 > curses.init_pair(12, 7, 4)
466 > curses.init_pair(13, 7, 5)
467 > curses.init_pair(14, 7, 6)
468 > curses.init_pair(15, -1, 15)
470 - __teliva_timestamp: original
473 - __teliva_timestamp: original
476 - __teliva_timestamp: original
478 >function render(window)
481 > local toots = split(prose, '\n\n===\n\n')
483 > for i, toot in ipairs(toots) do
485 > pos = render_delimiter(window, '\n\n===\n\n', pos, cursor)
487 > pos = render_text(window, toot, pos, cursor)
489 > window:attron(curses.A_BOLD)
490 > window:addstr(toot:len())
491 > window:attroff(curses.A_BOLD)
495 - __teliva_timestamp: original
497 >function render_delimiter(window, s, pos, cursor)
500 > if newpos == cursor and i ~= 1 then
501 > if s[i] == '\n' then
502 > -- newline at cursor = render extra space in reverse video before jumping to new line
503 > window:attron(curses.A_REVERSE)
505 > window:attroff(curses.A_REVERSE)
506 > window:addstr(s[i])
508 > -- most characters at cursor = render in reverse video
509 > window:attron(curses.A_REVERSE)
510 > window:addstr(s[i])
511 > window:attroff(curses.A_REVERSE)
514 > window:addstr(s[i])
520 - __teliva_timestamp: original
522 >-- https://gankra.github.io/blah/text-hates-you
523 >-- https://lord.io/text-editing-hates-you-too
526 >-- cursor on some character
527 >-- cursor on (within) '\n\n===\n\n' delimiter (delimiter is hardcoded; things may break if you change it)
528 >-- cursor at end of each line
531 >-- positions serve two purposes:
532 >-- character to index into prose
535 >-- sequence of stories
536 >-- focus on rendering a single piece of text, first get that rock-solid
537 >-- split prose into toots, manage transitions between toots in response to cursor movements
538 >-- cursor movement: left/right vs up/down
540 >-- what is the ideal representation?
541 >-- prose + cursor has issues in multi-toot context. when to display cursor?
542 >function render_text(window, s, pos, cursor)
544 >--? dbg(window, '--')
546 >--? dbg(window, tostring(newpos)..' '..tostring(string.byte(s[i])))
547 > if newpos == cursor then
548 >--? dbg(window, 'cursor: '..tostring(cursor))
549 > if s[i] == '\n' then
550 > -- newline at cursor = render extra space in reverse video before jumping to new line
551 > window:attron(curses.A_REVERSE)
553 > window:attroff(curses.A_REVERSE)
554 > window:addstr(s[i])
556 > -- most characters at cursor = render in reverse video
557 > window:attron(curses.A_REVERSE)
558 > window:addstr(s[i])
559 > window:attroff(curses.A_REVERSE)
562 > window:addstr(s[i])
566 > if newpos == cursor then
567 > window:attron(curses.A_REVERSE)
569 > window:attroff(curses.A_REVERSE)
573 - __teliva_timestamp: original
575 >function update(window)
576 > local key = window:getch()
577 > local h, w = window:getmaxyx()
578 > if key == curses.KEY_LEFT then
582 > elseif key == curses.KEY_RIGHT then
583 > if cursor <= #prose then
586 > elseif key == curses.KEY_DOWN then
587 > cursor = cursor_down(prose, cursor, w)
588 > elseif key == curses.KEY_UP then
589 > cursor = cursor_up(prose, cursor, w)
590 > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete
593 > prose = prose:remove(cursor)
595 > elseif key == 11 then -- ctrl-k
598 > elseif key == 23 then -- ctrl-w
599 > local out = io.open('toot', 'w')
601 > out:write(prose, '\n')
604 > elseif key == 10 or (key >= 32 and key < 127) then
605 > prose = prose:insert(string.char(key), cursor-1)
609 - __teliva_timestamp: original
611 >function cursor_down(s, old_idx, width)
612 > local max = string.len(s)
614 > -- compute oldcol, the screen column of old_idx
619 > -- abnormal old_idx
622 > if i == old_idx then
626 > if s[i] == '\n' then
633 > -- skip rest of line
636 > -- current line is at bottom
637 > if col >= width then
642 > if s[i] == '\n' then
645 > if i - old_idx >= width then
651 > -- compute index at same column on next line
652 > -- i is at a newline
657 > -- next line is at bottom and is too short; position at end of it
660 > if s[i] == '\n' then
661 > -- next line is too short; position at end of it
664 > if col == oldcol then
672 >function test_cursor_down()
673 > -- lines that don't wrap
674 > check_eq(cursor_down('abc\ndef', 1, 5), 5, 'cursor_down: non-bottom line first char')
675 > check_eq(cursor_down('abc\ndef', 2, 5), 6, 'cursor_down: non-bottom line mid char')
676 > check_eq(cursor_down('abc\ndef', 3, 5), 7, 'cursor_down: non-bottom line final char')
677 > check_eq(cursor_down('abc\ndef', 4, 5), 8, 'cursor_down: non-bottom line end')
678 > check_eq(cursor_down('abc\ndef', 5, 5), 5, 'cursor_down: bottom line first char')
679 > check_eq(cursor_down('abc\ndef', 6, 5), 6, 'cursor_down: bottom line mid char')
680 > check_eq(cursor_down('abc\ndef', 7, 5), 7, 'cursor_down: bottom line final char')
681 > check_eq(cursor_down('abc\n\ndef', 2, 5), 5, 'cursor_down: to shorter line')
683 > -- within a single wrapping line
684 > -- |abcde| <-- wrap, no newline
686 > check_eq(cursor_down('abcdefgh', 1, 5), 6, 'cursor_down from wrapping line: first char')
687 > check_eq(cursor_down('abcdefgh', 2, 5), 7, 'cursor_down from wrapping line: mid char')
688 > check_eq(cursor_down('abcdefgh', 5, 5), 9, 'cursor_down from wrapping line: to shorter line')
690 > -- within a single very long wrapping line
691 > -- |abcde| <-- wrap, no newline
692 > -- |fghij| <-- wrap, no newline
694 > check_eq(cursor_down('abcdefghijklm', 1, 5), 6, 'cursor_down within wrapping line: first char')
695 > check_eq(cursor_down('abcdefghijklm', 2, 5), 7, 'cursor_down within wrapping line: mid char')
696 > check_eq(cursor_down('abcdefghijklm', 5, 5), 10, 'cursor_down within wrapping line: final char')
698 - __teliva_timestamp: original
700 >function cursor_up(s, old_idx, width)
701 > local max = string.len(s)
703 > -- compute oldcol, the screen column of old_idx
706 > local newline_before_current_line = 0
708 > if i > max or i == old_idx then
712 > if s[i] == '\n' then
714 > newline_before_current_line = i
717 > if col == width then
723 > -- find previous newline
725 > if old_idx - newline_before_current_line > width then
726 > -- we're in a wrapped line
727 > return old_idx - width
729 > -- scan back to start of previous line
730 > if s[i] == '\n' then
735 > -- current line is at top
738 > if s[i] == '\n' then
743 > -- i is at a newline
745 > -- skip whole screen lines within previous line
746 > while newline_before_current_line - i > width do
749 > -- compute index at same column on previous screen line
753 > -- next line is at bottom and is too short; position at end of it
756 > if s[i] == '\n' then
757 > -- next line is too short; position at end of it
760 > if col == oldcol then
768 >function test_cursor_up()
769 > -- lines that don't wrap
770 > check_eq(cursor_up('abc\ndef', 1, 5), 1, 'cursor_up: top line first char')
771 > check_eq(cursor_up('abc\ndef', 2, 5), 2, 'cursor_up: top line mid char')
772 > check_eq(cursor_up('abc\ndef', 3, 5), 3, 'cursor_up: top line final char')
773 > check_eq(cursor_up('abc\ndef', 4, 5), 4, 'cursor_up: top line end')
774 > check_eq(cursor_up('abc\ndef', 5, 5), 1, 'cursor_up: non-top line first char')
775 > check_eq(cursor_up('abc\ndef', 6, 5), 2, 'cursor_up: non-top line mid char')
776 > check_eq(cursor_up('abc\ndef', 7, 5), 3, 'cursor_up: non-top line final char')
777 > check_eq(cursor_up('abc\ndef\n', 8, 5), 4, 'cursor_up: non-top line end')
778 > check_eq(cursor_up('ab\ndef\n', 7, 5), 3, 'cursor_up: to shorter line')
780 > -- within a single wrapping line
781 > -- |abcde| <-- wrap, no newline
783 > check_eq(cursor_up('abcdefgh', 6, 5), 1, 'cursor_up from wrapping line: first char')
784 > check_eq(cursor_up('abcdefgh', 7, 5), 2, 'cursor_up from wrapping line: mid char')
785 > check_eq(cursor_up('abcdefgh', 8, 5), 3, 'cursor_up from wrapping line: final char')
786 > check_eq(cursor_up('abcdefgh', 9, 5), 4, 'cursor_up from wrapping line: wrapped line end')
788 > -- within a single very long wrapping line
789 > -- |abcde| <-- wrap, no newline
790 > -- |fghij| <-- wrap, no newline
792 > check_eq(cursor_up('abcdefghijklm', 11, 5), 6, 'cursor_up within wrapping line: first char')
793 > check_eq(cursor_up('abcdefghijklm', 12, 5), 7, 'cursor_up within wrapping line: mid char')
794 > check_eq(cursor_up('abcdefghijklm', 13, 5), 8, 'cursor_up within wrapping line: final char')
795 > check_eq(cursor_up('abcdefghijklm', 14, 5), 9, 'cursor_up within wrapping line: wrapped line end')
797 > -- from below to (the bottom of) a wrapping line
798 > -- |abcde| <-- wrap, no newline
801 > check_eq(cursor_up('abcdefg\nhij', 9, 5), 6, 'cursor_up to wrapping line: first char')
802 > check_eq(cursor_up('abcdefg\nhij', 10, 5), 7, 'cursor_up to wrapping line: mid char')
803 > check_eq(cursor_up('abcdefg\nhij', 11, 5), 8, 'cursor_up to wrapping line: final char')
804 > check_eq(cursor_up('abcdefg\nhij', 12, 5), 8, 'cursor_up to wrapping line: to shorter line')
806 - __teliva_timestamp:
807 >Thu Feb 17 19:52:30 2022
809 >A tiny editor (no scrolling) for composing a series of toots or tweets. Always shows character counts for current state of prose.
811 >Typing '===' on its own lines, surrounded by empty lines, partitions prose and gives all segments independent character counts. Good for threads (tweetstorms).
812 - __teliva_timestamp:
813 >Fri Mar 11 09:45:27 2022
816 - __teliva_timestamp:
817 >Fri Mar 11 11:47:34 2022
819 >function update(window)
820 > local key = window:getch()
821 > local h, w = window:getmaxyx()
822 > if key == curses.KEY_LEFT then
826 > elseif key == curses.KEY_RIGHT then
827 > if cursor <= #prose then
830 > elseif key == curses.KEY_DOWN then
831 > cursor = cursor_down(prose, cursor, w)
832 > elseif key == curses.KEY_UP then
833 > cursor = cursor_up(prose, cursor, w)
834 > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete
837 > prose = prose:remove(cursor)
839 > elseif key == 6 then -- ctrl-f
840 > first_toot = first_toot+1
841 > elseif key == 2 then -- ctrl-b
842 > if first_toot > 1 then
843 > first_toot = first_toot-1
845 > elseif key == 11 then -- ctrl-k
848 > elseif key == 23 then -- ctrl-w
849 > local out = io.open('toot', 'w')
851 > out:write(prose, '\n')
854 > elseif key == 10 or (key >= 32 and key < 127) then
855 > prose = prose:insert(string.char(key), cursor-1)
859 - __teliva_timestamp:
860 >Fri Mar 11 11:48:43 2022
862 >-- To show app-specific hotkeys in the menu bar, add hotkey/command
863 >-- arrays of strings to the menu array.
865 > {'^w', 'write to "toot"'},
866 > {'^f|^b', 'scroll'},
869 - __teliva_timestamp:
870 >Sat Mar 12 08:48:44 2022
872 >function render(window)
875 > local toots = split(prose, '\n\n===\n\n')
877 > for i, toot in ipairs(toots) do
878 >--? dbg(window, "render: "..i.." pos "..pos.." cursor "..cursor)
880 > pos = render_delimiter(window, '\n\n===\n\n', pos, cursor)
881 >--? dbg(window, "delim: "..pos.." cursor "..cursor)
883 > if i <= first_toot then
886 > pos = render_text(window, toot, pos, cursor)
888 >--? dbg(window, "text: "..pos.." cursor "..cursor)
889 > window:attron(curses.A_BOLD)
890 > window:addstr(toot:len())
891 > window:attroff(curses.A_BOLD)
895 - __teliva_timestamp:
896 >Sat Mar 12 08:57:41 2022
898 >A tiny editor (no scrolling) for composing a series of toots or tweets.
899 >Always shows character counts for current state of prose.
901 >Typing '===' on its own lines, surrounded by empty lines, partitions prose and gives all segments independent character counts. Good for threads (tweetstorms).
902 - __teliva_timestamp:
903 >Sat Mar 12 08:59:52 2022
905 >hacky scrolling support
907 >Since I started out rendering a toot at a time and tracking the position
908 >as I rendered each toot, the easiest way to build this was to scroll a
909 >toot at a time, always render each toot and just decide when to stop
910 >clearing the screen. This way I don't mess with the position computation
911 >logic which is carefully synced between render and cursor_up/cursor_down.
913 >But there may be a more elegant approach if I was building the current state
916 >A tiny editor for composing a short series of toots or tweets. Always shows character counts for current state of prose.
918 >Typing '===' on its own lines, surrounded by empty lines, partitions prose and gives all segments independent character counts. Good for threads (tweetstorms).
920 >Scrolling support is rudimentary. Keys to scroll are independent of cursor movement, so cursor can move off the screen and confusingly 'get lost'.
921 - __teliva_timestamp:
922 >Wed Mar 30 21:33:17 2022
924 >function update(window)
925 > local key = window:getch()
926 > local h, w = window:getmaxyx()
927 > if key == curses.KEY_LEFT then
931 > elseif key == curses.KEY_RIGHT then
932 > if cursor <= #prose then
935 > elseif key == curses.KEY_DOWN then
936 > cursor = cursor_down(prose, cursor, w)
937 > elseif key == curses.KEY_UP then
938 > cursor = cursor_up(prose, cursor, w)
939 > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete
942 > prose = prose:remove(cursor)
944 > elseif key == 6 then -- ctrl-f
945 > first_toot = first_toot+1
946 > elseif key == 2 then -- ctrl-b
947 > if first_toot > 1 then
948 > first_toot = first_toot-1
950 > elseif key == 23 then -- ctrl-w
951 > local out = io.open('toot', 'w')
953 > out:write(prose, '\n')
956 > elseif key == 10 or (key >= 32 and key < 127) then
957 > prose = prose:insert(string.char(key), cursor-1)
961 - __teliva_timestamp:
962 >Wed Mar 30 21:33:44 2022
964 >Get rid of the ctrl-k shortcut. Makes it too easy to lose data. To clear the page just quit and restart.
966 >-- To show app-specific hotkeys in the menu bar, add hotkey/command
967 >-- arrays of strings to the menu array.
969 > {'^w', 'write to "toot"'},
970 > {'^f|^b', 'scroll'},
972 - __teliva_timestamp:
973 >Thu Mar 31 08:42:19 2022
975 >function update(window)
976 > local key = window:getch()
977 > local h, w = window:getmaxyx()
978 > if key == curses.KEY_LEFT then
982 > elseif key == curses.KEY_RIGHT then
983 > if cursor <= #prose then
986 > elseif key == curses.KEY_DOWN then
987 > cursor = cursor_down(prose, cursor, w)
988 > elseif key == curses.KEY_UP then
989 > cursor = cursor_up(prose, cursor, w)
990 > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete
993 > prose = prose:remove(cursor)
995 > elseif key == 6 then -- ctrl-f
996 > first_toot = first_toot+1
997 > elseif key == 2 then -- ctrl-b
998 > if first_toot > 1 then
999 > first_toot = first_toot-1
1001 > elseif key == 23 then -- ctrl-w
1002 > local out = io.open(next_toot(), 'w')
1003 > if out ~= nil then
1004 > out:write(prose, '\n')
1007 > elseif key == 10 or (key >= 32 and key < 127) then
1008 > prose = prose:insert(string.char(key), cursor-1)
1012 - __teliva_timestamp:
1013 >Thu Mar 31 08:44:19 2022
1015 >-- pick the first filename starting with 'toot' that doesn't exist yet
1016 >function next_toot()
1017 > if not file_exists('toot') then return 'toot' end
1020 > local curr = 'toot'..str(idx)
1021 > if not file_exists(curr) then
1027 - __teliva_timestamp:
1028 >Thu Mar 31 08:46:27 2022
1030 >function file_exists(filename)
1031 > local f = io.open(filename, 'r')