about summary refs log blame commit diff stats
path: root/toot-toot.tlv
blob: 26eb59183ccb761b6e634d0e2c5fb4406bd2ff37 (plain) (tree)































































                                                                                



                              

                                                                              























                                                                 
























































                                                 
                                                         
















                                                            
                         
                                                                   



                                 



















                                              



                                         



                                                         


























                                   
                             

               

               
                     


























                                                                                                  

                     















                                                     

                     












                                        

                     




                                     
          















                                                                                         

                     



























                                                                   
          
        





























                                                                                                                         
          








                                                                                 

                     
                             


                            





                                                
                                                




                                                                     
            










                                                     
          
                       
        

                             





















































                                                                                                                
# .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 within refresh when using this
    >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
    >  curses.refresh()
    >end
- __teliva_timestamp: original
  menu:
    >menu = {}
    >menu['^u'] = 'clear'
    >menu['^w'] = "write prose to file 'toot' (edit 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
- main:
    >function main()
    >  init_colors()
    >
    >  while true do
    >    render(window)
    >    update(window)
    >  end
    >end
  __teliva_timestamp: original
- __teliva_timestamp:
    >Tue Dec 14 17:29:11 2021
  prose:
    >prose = ''
- cursor:
    >cursor = 1
  __teliva_timestamp:
    >Tue Dec 14 17:29:27 2021
- render_delimiter:
    >function render_delimiter(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 and i ~= 1 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: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:
    >Fri Dec 17 15:46:51 2021
- cursor_down:
    >function cursor_down(s, idx)
    >  local colidx = col_within_line(s, idx)
    >  local newidx = skip_past_newline(s, idx)
    >--?   dbg(curses.stdscr(), tostring(idx))
    >--?   dbg(curses.stdscr(), tostring(colidx))
    >--?   dbg(curses.stdscr(), tostring(newidx))
    >--?   curses.getch()
    >  while true do
    >    if s[newidx] == '\n' then break end
    >    local newcolidx = col_within_line(s, newidx)
    >    if newcolidx == colidx then break end
    >    newidx = newidx+1
    >  end
    >  return newidx
    >end
  __teliva_timestamp:
    >Fri Dec 17 16:18:34 2021
- 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:
    >Fri Dec 17 19:47:36 2021
- 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:
    >Fri Dec 17 22:40:43 2021
- __teliva_timestamp:
    >Fri Dec 17 22:54:12 2021
  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
- 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: second line first char')
    >end
  __teliva_timestamp:
    >Fri Dec 17 23:02:05 2021
- render:
    >function render(window)
    >  window:clear()
    >  debugy = 5
    >--?   render_text(window, prose, 1, cursor)
    >--?   curses.refresh()
    >--? end
    >
    >--? function unused()
    >  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)
    >--?     if pos == cursor then
    >--?       window:attron(curses.A_REVERSE)
    >--?       window:addch(' ')
    >--?       window:attroff(curses.A_REVERSE)
    >--?     end
    >    print('')
    >    window:attron(curses.A_BOLD)
    >    window:addstr(string.len(toot))
    >    window:attroff(curses.A_BOLD)
    >--?     print('')
    >  end
    >  curses.refresh()
    >end
  __teliva_timestamp:
    >Fri Dec 17 23:04:57 2021
- __teliva_timestamp:
    >Fri Dec 17 23:08:32 2021
  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