# .tlv file generated by https://github.com/akkartik/teliva
# You may edit it if you are careful; however, you may see cryptic errors if you
# violate Teliva's assumptions.
#
# .tlv files are representations of Teliva programs. Teliva programs consist of
# sequences of definitions. Each definition is a table of key/value pairs. Keys
# and values are both strings.
#
# Lines in .tlv files always follow exactly one of the following forms:
# - comment lines at the top of the file starting with '#' at column 0
# - beginnings of definitions starting with '- ' at column 0, followed by a
# key/value pair
# - key/value pairs consisting of ' ' at column 0, containing either a
# spaceless value on the same line, or a multi-line value
# - multiline values indented by more than 2 spaces, starting with a '>'
#
# If these constraints are violated, Teliva may unceremoniously crash. Please
# report bugs at http://akkartik.name/contact
- __teliva_timestamp: original
str_helpers:
>-- some string helpers from http://lua-users.org/wiki/StringIndexing
>
>-- index characters using []
>getmetatable('').__index = function(str,i)
> if type(i) == 'number' then
> return string.sub(str,i,i)
> else
> return string[i]
> end
>end
>
>-- ranges using (), selected bytes using {}
>getmetatable('').__call = function(str,i,j)
> if type(i)~='table' then
> return string.sub(str,i,j)
> else
> local t={}
> for k,v in ipairs(i) do
> t[k]=string.sub(str,v,v)
> end
> return table.concat(t)
> end
>end
>
>-- iterate over an ordered sequence
>function q(x)
> if type(x) == 'string' then
> return x:gmatch('.')
> else
> return ipairs(x)
> end
>end
>
>-- insert within string
>function string.insert(str1, str2, pos)
> return str1:sub(1,pos)..str2..str1:sub(pos+1)
>end
>
>function string.remove(s, pos)
> return s:sub(1,pos-1)..s:sub(pos+1)
>end
>
>-- TODO: backport utf-8 support from Lua 5.3
- __teliva_timestamp: original
debugy:
>debugy = 5
- __teliva_timestamp: original
dbg:
>-- helper for debug by print; overlay debug information towards the right
>-- reset debugy every time you refresh screen
>function dbg(window, s)
> local oldy = 0
> local oldx = 0
> oldy, oldx = window:getyx()
> window:mvaddstr(debugy, 60, s)
> debugy = debugy+1
> window:mvaddstr(oldy, oldx, '')
>end
- __teliva_timestamp: original
check_eq:
>function check_eq(x, expected, msg)
> if x == expected then
> curses.addch('.')
> else
> print('F - '..msg)
> print(' expected '..tostring(expected)..' but got '..x)
> teliva_num_test_failures = teliva_num_test_failures + 1
> -- overlay first test failure on editors
> if teliva_first_failure == nil then
> teliva_first_failure = msg
> end
> end
>end
- __teliva_timestamp: original
map:
>-- only for arrays
>function map(l, f)
> result = {}
> for _, x in ipairs(l) do
> table.insert(result, f(x))
> end
> return result
>end
- __teliva_timestamp: original
reduce:
>-- only for arrays
>function reduce(l, f, init)
> result = init
> for _, x in ipairs(l) do
> result = f(result, x)
> end
> return result
>end
- __teliva_timestamp: original
filter:
>-- only for arrays
>function filter(l, f)
> result = {}
> for _, x in ipairs(l) do
> if f(x) then
> table.insert(result, x)
> end
> end
> return result
>end
- __teliva_timestamp: original
find_index:
>function find_index(arr, x)
> for n, y in ipairs(arr) do
> if x == y then
> return n
> end
> end
>end
- __teliva_timestamp: original
trim:
>function trim(s)
> return s:gsub('^%s*', ''):gsub('%s*$', '')
>end
- __teliva_timestamp: original
split:
>function split(s, d)
> result = {}
> for match in (s..d):gmatch("(.-)"..d) do
> table.insert(result, match);
> end
> return result
>end
- __teliva_timestamp: original
window:
>window = curses.stdscr()
>curses.curs_set(0) -- we'll simulate our own cursor
- __teliva_timestamp: original
render:
>function render(window)
> window:clear()
> -- draw stuff to screen here
> window:attron(curses.A_BOLD)
> window:mvaddstr(1, 5, "example app")
> window:attrset(curses.A_NORMAL)
> for i=0,15 do
> window:attrset(curses.color_pair(i))
> window:mvaddstr(3+i, 5, "========================")
> end
>end
- __teliva_timestamp: original
menu:
>menu = {
> {'^u', 'clear'},
> {'^w', 'write prose to file "toot" (edit hotkey does NOT save)'},
>}
- __teliva_timestamp: original
update:
>function update(window)
> local key = curses.getch()
> 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)
> elseif key == curses.KEY_UP then
> cursor = cursor_up(prose, cursor)
> elseif key == curses.KEY_BACKSPACE then
> if cursor > 1 then
> cursor = cursor-1
> prose = prose:remove(cursor)
> end
> elseif key == 21 then -- ctrl-u
> prose = ''
> cursor = 1
> elseif key == 23 then -- ctrl-w
> local out = io.open('toot', 'w')
> out:write(prose, '\n')
> out:close()
> elseif key == 10 or (key >= 32 and key < 127) then
> prose = prose:insert(string.char(key), cursor-1)
> cursor = cursor+1
> end
>end
- __teliva_timestamp: original
init_colors:
>function init_colors()
> for i=0,7 do
> curses.init_pair(i, i, -1)
> end
> curses.init_pair(8, 7, 0)
> curses.init_pair(9, 7, 1)
> curses.init_pair(10, 7, 2)
> curses.init_pair(11, 7, 3)
> curses.init_pair(12, 7, 4)
> curses.init_pair(13, 7, 5)
> curses.init_pair(14, 7, 6)
> curses.init_pair(15, -1, 15)
>end
- __teliva_timestamp: original
main:
>function main()
> init_colors()
>
> while true do
> render(window)
> update(window)
> end
>end
- __teliva_timestamp: original
prose:
>prose = ''
- __teliva_timestamp: original
cursor:
>cursor = 1
- __teliva_timestamp: original
render_delimiter:
>function render_delimiter(window, s, pos, cursor)
> local newpos = pos
> for i=1,string.len(s) do
> if newpos == cursor and i ~= 1 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)
> window:addch(s[i])
> else
> -- most characters at cursor = render in reverse video
> window:attron(curses.A_REVERSE)
> window:addch(s[i])
> window:attroff(curses.A_REVERSE)
> end
> else
> window:addch(s[i])
> end
> newpos = newpos+1
> end
> return newpos
>end
- __teliva_timestamp: original
cursor_down:
>function cursor_down(s, old_idx)
> local colidx = 0
> local old_colidx = -1
> for i=1,string.len(s) do
> if i == old_idx then
> old_colidx = colidx
> elseif colidx == old_colidx then -- next line
> return i
> end
> -- loop update
> if s[i] == '\n' then
> if old_colidx ~= -1 and old_colidx > colidx then
> return i
> end
> colidx = 0
> else
> colidx = colidx+1
> end
> end
> if old_colidx == colidx then
> return string.len(s)+1
> else
> return old_idx
> end
>end
>
>function test_cursor_down()
> check_eq(cursor_down('abc\ndef', 1), 5, 'cursor_down: non-bottom line first char')
> check_eq(cursor_down('abc\ndef', 2), 6, 'cursor_down: non-bottom line mid char')
> check_eq(cursor_down('abc\ndef', 3), 7, 'cursor_down: non-bottom line final char')
> check_eq(cursor_down('abc\ndef', 4), 8, 'cursor_down: non-bottom line end')
> check_eq(cursor_down('abc\ndef', 5), 5, 'cursor_down: bottom line first char')
> check_eq(cursor_down('abc\ndef', 6), 6, 'cursor_down: bottom line mid char')
> check_eq(cursor_down('abc\ndef', 7), 7, 'cursor_down: bottom line final char')
> check_eq(cursor_down('abc\n\ndef', 2), 5, 'cursor_down: to shorter line')
>end
- __teliva_timestamp: original
skip_past_newline:
>function skip_past_newline(s, idx)
> local result = idx
> while true do
> if result >= string.len(s) then
> return idx
> end
> if s[result] == '\n' then
> return result+1
> end
> result = result+1
> end
>end
- __teliva_timestamp: original
col_within_line:
>function col_within_line(s, idx)
> if idx <= 1 then
> return idx
> end
> idx = idx-1
> local result = 1
> while idx >= 1 do
> if s[idx] == '\n' then break end
> idx = idx-1
> result=result+1
> end
> return result
>end
>
>function test_col_within_line()
> check_eq(col_within_line('', 4), 4, 'col_within_line("")')
> check_eq(col_within_line('abc\ndef', 1), 1, 'col_within_line(..., 1)')
> check_eq(col_within_line('abc\ndef', 3), 3, 'col_within_line(..., -1)')
> check_eq(col_within_line('abc\ndef', 4), 4, 'col_within_line(..., newline)')
> check_eq(col_within_line('abc\ndef', 5), 1, 'col_within_line(..., after newline)')
>end
- __teliva_timestamp: original
skip_to_start_of_previous_line:
>function skip_to_start_of_previous_line(s, idx)
> local result = idx
> -- skip to newline
> if idx == 1 then return 1 end
> result = result-1 -- just in case we start out on a newline
> while true do
> if result <= 1 then
> return idx
> end
> if s[result] == '\n' then
> result = result-1
> break
> end
> result = result-1
> end
> -- dbg(window, 'skip: '..tostring(result))
> while true do
> if result <= 1 then
> return result
> end
> if s[result] == '\n' then
> return result+1
> end
> result = result-1
> end
>end
>
>function test_skip_to_start_of_previous_line()
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 1), 1, 'start of previous line: first line, first char')
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 2), 2, 'start of previous line: first line, mid char')
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 3), 3, 'start of previous line: first line, final char')
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 4), 4, 'start of previous line: first line, newline')
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 5), 1, 'start of previous line: second line, first char')
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 6), 1, 'start of previous line: second line, mid char')
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 7), 1, 'start of previous line: second line, final char')
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 8), 1, 'start of previous line: second line, newline')
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 9), 5, 'start of previous line: final line, first char')
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 10), 5, 'start of previous line: final line, mid char')
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 11), 5, 'start of previous line: final line, final char')
> check_eq(skip_to_start_of_previous_line('abc\ndef\nghi', 12), 5, 'start of previous line: end of file')
>
> check_eq(skip_to_start_of_previous_line('abc\n\nghi', 7), 5, 'start of previous line: to empty line')
> check_eq(skip_to_start_of_previous_line('abc\nd\nghi', 8), 5, 'start of previous line: to shorter line')
>end
- __teliva_timestamp: original
cursor_up:
>function cursor_up(s, idx)
> if idx <= 1 then return idx end
> -- check column within current line, then go to start of previous line, then count off columns there
> local colidx = col_within_line(s, idx)
> local newidx = skip_to_start_of_previous_line(s, idx)
> if newidx == idx then return idx end
> if s[newidx] == '\n' then return newidx end
> for i=2,colidx do -- we're already starting at col 1
> if newidx >= string.len(s) then break end
> if s[newidx] == '\n' then break end
> newidx = newidx+1
> end
> return newidx
>end
>
>function test_cursor_up()
> check_eq(cursor_up('abc\ndef', 1), 1, 'cursor_up: top line first char')
> check_eq(cursor_up('abc\ndef', 2), 2, 'cursor_up: top line mid char')
> check_eq(cursor_up('abc\ndef', 3), 3, 'cursor_up: top line final char')
> check_eq(cursor_up('abc\ndef', 4), 4, 'cursor_up: top line end')
> check_eq(cursor_up('abc\ndef', 5), 1, 'cursor_up: non-top line first char')
> check_eq(cursor_up('abc\ndef', 6), 2, 'cursor_up: non-top line mid char')
> check_eq(cursor_up('abc\ndef', 7), 3, 'cursor_up: non-top line final char')
> check_eq(cursor_up('abc\ndef\n', 8), 4, 'cursor_up: non-top line end')
> check_eq(cursor_up('ab\ndef\n', 7), 3, 'cursor_up: to shorter line')
> -- idx that's too high for s not working; let's see if that matters
>end
- __teliva_timestamp: original
render:
>function render(window)
> window:clear()
> debugy = 5
> local toots = split(prose, '\n\n===\n\n')
> pos = 1
> debugy = 5
> for i, toot in ipairs(toots) do
> if i > 1 then
> pos = render_delimiter(window, '\n\n===\n\n', pos, cursor)
> end
> pos = render_text(window, toot, pos, cursor)
> print('')
> window:attron(curses.A_BOLD)
> window:addstr(string.len(toot))
> window:attroff(curses.A_BOLD)
> end
> curses.refresh()
>end
- __teliva_timestamp: original
render_text:
>-- https://gankra.github.io/blah/text-hates-you
>-- https://lord.io/text-editing-hates-you-too
>
>-- manual tests:
>-- cursor on some character
>-- cursor on (within) '\n\n===\n\n' delimiter (delimiter is hardcoded; things may break if you change it)
>-- cursor at end of each line
>-- render digits
>
>-- positions serve two purposes:
>-- character to index into prose
>-- cursor-printing
>
>-- sequence of stories
>-- focus on rendering a single piece of text, first get that rock-solid
>-- split prose into toots, manage transitions between toots in response to cursor movements
>-- cursor movement: left/right vs up/down
>
>-- what is the ideal representation?
>-- prose + cursor has issues in multi-toot context. when to display cursor?
>function render_text(window, s, pos, cursor)
> local newpos = pos
>--? dbg(window, '--')
> for i=1,string.len(s) do
>--? dbg(window, tostring(newpos)..' '..tostring(string.byte(s[i])))
> if newpos == cursor then
>--? dbg(window, 'cursor: '..tostring(cursor))
> 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)
> window:addstr(s[i])
> 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
> window:addstr(s[i])
> end
> newpos = newpos+1
> end
> if newpos == cursor then
> window:attron(curses.A_REVERSE)
> window:addch(' ')
> window:attroff(curses.A_REVERSE)
> end
> return newpos
>end