Advertisement
joebodo

Forums.edit.lua

Nov 25th, 2016
934
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 29.27 KB | None | 0 0
  1. shell.setCompletionFunction(shell.getRunningProgram(), function(shell, index, text)
  2.   if index == 1 then
  3.     return fs.complete(text, shell.dir(), true, false)
  4.   end
  5. end)
  6.  
  7. local tArgs = { ... }
  8. if #tArgs == 0 then
  9.   error( "Usage: edit <path>" )
  10. end
  11.  
  12. -- Error checking
  13. local sPath = shell.resolve(tArgs[1])
  14. local bReadOnly = fs.isReadOnly(sPath)
  15. if fs.exists(sPath) and fs.isDir(sPath) then
  16.   error( "Cannot edit a directory." )
  17. end
  18.  
  19. if multishell then
  20.   multishell.setTitle(multishell.getCurrent(), sPath)
  21. end
  22.  
  23. local x, y      = 1, 1
  24. local w, h      = term.getSize()
  25. local scrollX   = 0
  26. local scrollY   = 0
  27. local lastPos   = { x = 1, y = 1 }
  28. local tLines    = { }
  29. local bRunning  = true
  30. local sStatus   = ""
  31. local isError
  32. local fileInfo
  33.  
  34. local dirty     = { y = 1, ey = h }
  35. local mark      = { anchor, active, continue }
  36. local keyboard
  37. local searchPattern
  38. local undo      = { chain = { }, pointer = 0 }
  39. local complete  = { }
  40.  
  41. if not clipboard then
  42.   _G.clipboard = { internal, data }
  43.   clipboard.shim = true
  44.  
  45.   function clipboard.setData(data)
  46.     clipboard.data = data
  47.     if data then
  48.       clipboard.useInternal(true)
  49.     end
  50.   end
  51.  
  52.   function clipboard.getText()
  53.     if clipboard.data then
  54.       return tostring(clipboard.data)
  55.     end
  56.   end
  57.  
  58.   function clipboard.isInternal()
  59.     return clipboard.internal
  60.   end
  61.  
  62.   function clipboard.useInternal(mode)
  63.     if mode ~= clipboard.mode then
  64.       clipboard.internal = mode
  65.     end
  66.   end
  67. end
  68.  
  69. local color = {
  70.   textColor       = '0',
  71.   keywordColor    = '4',
  72.   commentColor    = 'd',
  73.   stringColor     = 'e',
  74.   bgColor         = colors.black,
  75.   highlightColor  = colors.orange,
  76.   cursorColor     = colors.lime,
  77.   errorBackground = colors.red,
  78. }
  79.  
  80. if not term.isColor() then
  81.   color = {
  82.     textColor       = '0',
  83.     keywordColor    = '8',
  84.     commentColor    = '8',
  85.     stringColor     = '8',
  86.     bgColor         = colors.black,
  87.     highlightColor  = colors.lightGray,
  88.     cursorColor     = colors.white,
  89.     errorBackground = colors.gray,
  90.   }
  91. end
  92.  
  93. local keyMapping = {
  94.   -- movement
  95.   up                        = 'up',
  96.   down                      = 'down',
  97.   left                      = 'left',
  98.   right                     = 'right',
  99.   pageUp                    = 'pageUp',
  100.   [ 'control-b'           ] = 'pageUp',
  101.   pageDown                  = 'pageDown',
  102. --  [ 'control-f'           ] = 'pageDown',
  103.   home                      = 'home',
  104.   [ 'end'                 ] = 'toend',
  105.   [ 'control-home'        ] = 'top',
  106.   [ 'control-end'         ] = 'bottom',
  107.   [ 'control-right'       ] = 'word',
  108.   [ 'control-left'        ] = 'backword',
  109.   [ 'scrollUp'            ] = 'scroll_up',
  110.   [ 'control-up'          ] = 'scroll_up',
  111.   [ 'scrollDown'          ] = 'scroll_down',
  112.   [ 'control-down'        ] = 'scroll_down',
  113.   [ 'mouse_click'         ] = 'goto',
  114.   [ 'control-l'           ] = 'goto_line',
  115.  
  116.   -- marking
  117.   [ 'shift-up'            ] = 'mark_up',
  118.   [ 'shift-down'          ] = 'mark_down',
  119.   [ 'shift-left'          ] = 'mark_left',
  120.   [ 'shift-right'         ] = 'mark_right',
  121.   [ 'mouse_drag'          ] = 'mark_to',
  122.   [ 'shift-mouse_click'   ] = 'mark_to',
  123.   [ 'control-a'           ] = 'mark_all',
  124.   [ 'control-shift-right' ] = 'mark_word',
  125.   [ 'control-shift-left'  ] = 'mark_backword',
  126.   [ 'shift-end'           ] = 'mark_end',
  127.   [ 'shift-home'          ] = 'mark_home',
  128.  
  129.   -- editing
  130.   delete                    = 'delete',
  131.   backspace                 = 'backspace',
  132.   enter                     = 'enter',
  133.   char                      = 'char',
  134.   paste                     = 'paste',
  135.   tab                       = 'tab',
  136.   [ 'control-z'           ] = 'undo',
  137.   [ 'control-space'       ] = 'autocomplete',
  138.  
  139.   -- copy/paste
  140.   [ 'control-x'           ] = 'cut',
  141.   [ 'control-c'           ] = 'copy',
  142.   [ 'control-v'           ] = 'paste',
  143.   [ 'control-t'           ] = 'toggle_clipboard',
  144.  
  145.   -- file
  146.   [ 'control-s'           ] = 'save',
  147.   [ 'control-q'           ] = 'exit',
  148.   [ 'control-enter'       ] = 'run',
  149.  
  150.   -- search
  151.   [ 'control-f'           ] = 'find_prompt',
  152.   [ 'control-slash'       ] = 'find_prompt',
  153.   [ 'control-n'           ] = 'find_next',
  154.  
  155.   -- misc
  156.   [ 'control-g'           ] = 'status',
  157.   [ 'control-r'           ] = 'refresh',
  158.   [ 'leftCtrl'            ] = 'menu',
  159. }
  160.  
  161. local messages = {
  162.   menu    = '^s: save, ^q: quit, ^enter: run',
  163.   wrapped = 'search hit BOTTOM, continuing at TOP',
  164. }
  165. if w < 32 then
  166.   messages = {
  167.     menu    = '^s = save, ^q = quit',
  168.     wrapped = 'search wrapped',
  169.   }
  170. end
  171.  
  172. local function getFileInfo(path)
  173.   local abspath = shell.resolve(path)
  174.  
  175.   local fi = {
  176.     abspath = abspath,
  177.     path = path,
  178.     isNew = not fs.exists(abspath),
  179.     dirExists = fs.exists(fs.getDir(abspath)),
  180.     modified = false,
  181.   }
  182.   if fi.isDir then
  183.     fi.isReadOnly = true
  184.   else
  185.     fi.isReadOnly = fs.isReadOnly(fi.abspath)
  186.   end
  187.  
  188.   return fi
  189. end
  190.  
  191. local function setStatus(pattern, ...)
  192.   sStatus = string.format(pattern, ...)
  193. end
  194.  
  195. local function setError(pattern, ...)
  196.   setStatus(pattern, ...)
  197.   isError = true
  198. end
  199.  
  200. local function load(path)
  201.   tLines = {}
  202.   if fs.exists(path) then
  203.     local file = io.open(path, "r")
  204.     local sLine = file:read()
  205.     while sLine do
  206.       table.insert(tLines, sLine)
  207.       sLine = file:read()
  208.     end
  209.     file:close()
  210.   end
  211.  
  212.   if #tLines == 0 then
  213.     table.insert(tLines, '')
  214.   end
  215.  
  216.   fileInfo = getFileInfo(tArgs[1])
  217.  
  218.   local name = fileInfo.path
  219.   if w < 32 then
  220.     name = fs.getName(fileInfo.path)
  221.   end
  222.   if fileInfo.isNew then
  223.     if not fileInfo.dirExists then
  224.       setStatus('"%s" [New DIRECTORY]', name)
  225.     else
  226.       setStatus('"%s" [New File]', name)
  227.     end
  228.   elseif fileInfo.isReadOnly then
  229.     setStatus('"%s" [readonly] %dL, %dC',
  230.           name, #tLines, fs.getSize(fileInfo.abspath))
  231.   else
  232.     setStatus('"%s" %dL, %dC',
  233.           name, #tLines, fs.getSize(fileInfo.abspath))
  234.   end
  235. end
  236.  
  237. local function save( _sPath )
  238.   -- Create intervening folder
  239.   local sDir = _sPath:sub(1, _sPath:len() - fs.getName(_sPath):len() )
  240.   if not fs.exists( sDir ) then
  241.     fs.makeDir( sDir )
  242.   end
  243.  
  244.   -- Save
  245.   local file = nil
  246.   local function innerSave()
  247.     file = fs.open( _sPath, "w" )
  248.     if file then
  249.       for n, sLine in ipairs( tLines ) do
  250.         file.write(sLine .. "\n")
  251.       end
  252.     else
  253.       error( "Failed to open ".._sPath )
  254.     end
  255.   end
  256.  
  257.   local ok, err = pcall( innerSave )
  258.   if file then
  259.     file.close()
  260.   end
  261.   return ok, err
  262. end
  263.  
  264. local function split(str, pattern)
  265.   pattern = pattern or "(.-)\n"
  266.   local t = {}
  267.   local function helper(line) table.insert(t, line) return "" end
  268.   helper((str:gsub(pattern, helper)))
  269.   return t
  270. end
  271.  
  272. local tKeywords = {
  273.   ["and"] = true,
  274.   ["break"] = true,
  275.   ["do"] = true,
  276.   ["else"] = true,
  277.   ["elseif"] = true,
  278.   ["end"] = true,
  279.   ["false"] = true,
  280.   ["for"] = true,
  281.   ["function"] = true,
  282.   ["if"] = true,
  283.   ["in"] = true,
  284.   ["local"] = true,
  285.   ["nil"] = true,
  286.   ["not"] = true,
  287.   ["or"] = true,
  288.   ["repeat"] = true,
  289.   ["return"] = true,
  290.   ["then"] = true,
  291.   ["true"] = true,
  292.   ["until"]= true,
  293.   ["while"] = true,
  294. }
  295.  
  296. local function writeHighlighted(sLine, ny)
  297.   local buffer = {
  298.     fg = '',
  299.     text = '',
  300.   }
  301.  
  302.   local function tryWrite(sLine, regex, fgcolor)
  303.     local match = sLine:match(regex)
  304.     if match then
  305.       local fg
  306.       if type(fgcolor) == "string" then
  307.         fg = fgcolor
  308.       else
  309.         fg = fgcolor(match)
  310.       end
  311.       buffer.text = buffer.text .. match
  312.       buffer.fg = buffer.fg .. string.rep(fg, #match)
  313.       return sLine:sub(#match + 1)
  314.     end
  315.     return nil
  316.   end
  317.  
  318.   while #sLine > 0 do  
  319.     sLine =
  320.       tryWrite(sLine, "^%-%-%[%[.-%]%]", color.commentColor ) or
  321.       tryWrite(sLine, "^%-%-.*",         color.commentColor ) or
  322.       tryWrite(sLine, "^\".-[^\\]\"",    color.stringColor  ) or
  323.       tryWrite(sLine, "^\'.-[^\\]\'",    color.stringColor  ) or
  324.       tryWrite(sLine, "^%[%[.-%]%]",     color.stringColor  ) or
  325.       tryWrite(sLine, "^[%w_]+", function(match)
  326.         if tKeywords[match] then
  327.           return color.keywordColor
  328.         end
  329.         return color.textColor
  330.       end) or
  331.       tryWrite(sLine, "^[^%w_]", color.textColor)
  332.   end
  333.  
  334.   buffer.fg = buffer.fg .. '7'
  335.   buffer.text = buffer.text .. '.'
  336.  
  337.   if mark.active and ny >= mark.y and ny <= mark.ey then
  338.     local sx = 1
  339.     if ny == mark.y then
  340.       sx = mark.x
  341.     end
  342.     local ex = #buffer.text
  343.     if ny == mark.ey then
  344.       ex = mark.ex
  345.     end
  346.     buffer.bg = string.rep('f', sx - 1) ..
  347.                 string.rep('7', ex - sx) ..
  348.                 string.rep('f', #buffer.text - ex + 1)
  349.  
  350.   else
  351.     buffer.bg = string.rep('f', #buffer.text)
  352.   end
  353.  
  354.   term.blit(buffer.text, buffer.fg, buffer.bg)
  355. end
  356.  
  357. local function redraw()
  358.   if dirty.y > 0 then
  359.     term.setBackgroundColor(color.bgColor)
  360.     for dy = 1, h do
  361.  
  362.       local sLine = tLines[dy + scrollY]
  363.       if sLine ~= nil then
  364.         if dy + scrollY >= dirty.y and dy + scrollY <= dirty.ey then
  365.           term.setCursorPos(1 - scrollX, dy)
  366.           term.clearLine()
  367.           writeHighlighted(sLine, dy + scrollY)
  368.         end
  369.       else
  370.         term.setCursorPos(1 - scrollX, dy)
  371.         term.clearLine()
  372.       end
  373.     end
  374.   end
  375.  
  376.   -- Draw status
  377.   if #sStatus > 0 then
  378.     if isError then
  379.       term.setTextColor(colors.white)
  380.       term.setBackgroundColor(color.errorBackground)
  381.     else
  382.       term.setTextColor(color.highlightColor)
  383.       term.setBackgroundColor(colors.gray)
  384.     end
  385.     term.setCursorPos(1, h)
  386.     term.clearLine()
  387.     term.write(string.format(' %s ', sStatus))
  388.   end
  389.  
  390.   if not (w < 32 and #sStatus > 0) then
  391.     local clipboardIndicator = 'S'
  392.     if clipboard.isInternal() then
  393.       clipboardIndicator = 'I'
  394.     end
  395.  
  396.     local modifiedIndicator = ' '
  397.     if undo.chain[1] then
  398.       modifiedIndicator = '*'
  399.     end
  400.  
  401.     local str = string.format(' %d:%d %s%s',
  402.       y, x, clipboardIndicator, modifiedIndicator)
  403.     term.setTextColor(color.highlightColor)
  404.     term.setBackgroundColor(colors.gray)
  405.     term.setCursorPos(w - #str + 1, h)
  406.     term.write(str)
  407.   end
  408.  
  409.   term.setTextColor(color.cursorColor)
  410.   term.setCursorPos(x - scrollX, y - scrollY)
  411.  
  412.   dirty.y, dirty.ey = 0, 0
  413.   if #sStatus > 0 then
  414.     sStatus = ''
  415.     dirty.y = scrollY + h
  416.     dirty.ey = dirty.y
  417.   end
  418.   isError = false
  419. end
  420.  
  421. local function nextWord(line, cx)
  422.   local result = { line:find("(%w+)", cx) }
  423.   if #result > 1 and result[2] > cx then
  424.     return result[2] + 1
  425.   elseif #result > 0 and result[1] == cx then
  426.     result = { line:find("(%w+)", result[2] + 1) }
  427.     if #result > 0 then
  428.       return result[1]
  429.     end
  430.   end
  431. end
  432.  
  433. local function hacky_read()
  434.   local _oldSetCursorPos = term.setCursorPos
  435.   local _oldGetCursorPos = term.getCursorPos
  436.  
  437.   term.setCursorPos = function(x, y)
  438.     return _oldSetCursorPos(x, h)
  439.   end
  440.   term.getCursorPos = function()
  441.     local x, y = _oldGetCursorPos()
  442.     return x, 1
  443.   end
  444.  
  445.   local s, m = pcall(function() return read() end)
  446.   term.setCursorPos = _oldSetCursorPos
  447.   term.getCursorPos = _oldGetCursorPos
  448.   if s then
  449.     return m
  450.   end
  451.   if m == 'Terminated' then
  452.     bRunning = false
  453.   end
  454.   return ''
  455. end
  456.  
  457. local actions
  458. local __actions = {
  459.  
  460.   input = function(prompt)
  461.     term.setTextColor(color.highlightColor)
  462.     term.setBackgroundColor(colors.gray)
  463.     term.setCursorPos(1, h)
  464.     term.clearLine()
  465.     term.write(prompt)
  466.     local str = hacky_read()
  467.     term.setCursorBlink(true)
  468.     keyboard.shift, keyboard.control = false, false
  469.     term.setCursorPos(x - scrollX, y - scrollY)
  470.     actions.dirty_line(scrollY + h)
  471.     return str
  472.   end,
  473.  
  474.   undo = function()
  475.     local last = table.remove(undo.chain)
  476.     if last then
  477.       undo.active = true
  478.       actions[last.action](unpack(last.args))
  479.       undo.active = false
  480.     else
  481.       setStatus('Already at oldest change')
  482.     end
  483.   end,
  484.  
  485.   addUndo = function(entry)
  486.     local last = undo.chain[#undo.chain]
  487.     if last and last.action == entry.action then
  488.       --[[
  489.       debug('---')
  490.       debug(last)
  491.       debug(last.args)
  492.       debug(entry)
  493.       debug(entry.args)
  494.       ]]--
  495.       if last.action == 'deleteText' then
  496.         if last.args[3] == entry.args[1] and
  497.            last.args[4] == entry.args[2] then
  498.           last.args = {
  499.             last.args[1], last.args[2], entry.args[3], entry.args[4],
  500.             last.args[5] .. entry.args[5]
  501.           }
  502.         else
  503.           table.insert(undo.chain, entry)
  504.         end
  505.       else
  506.         -- insertText (need to finish)
  507.         table.insert(undo.chain, entry)
  508.       end
  509.     else
  510.       table.insert(undo.chain, entry)
  511.     end
  512.   end,
  513.  
  514.   autocomplete = function()
  515.     if keyboard.lastAction ~= 'autocomplete' or not complete.results then
  516.       local sLine = tLines[y]:sub(1, x - 1)
  517.       local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$")
  518.       if nStartPos then
  519.         sLine = sLine:sub(nStartPos)
  520.       end
  521.       if #sLine > 0 then
  522.         complete.results = textutils.complete(sLine)
  523.       else
  524.         complete.results = { }
  525.       end
  526.       complete.index = 0
  527.       complete.x = x
  528.     end
  529.  
  530.     if #complete.results == 0 then
  531.       setError('No completions available')
  532.  
  533.     elseif #complete.results == 1 then
  534.       actions.insertText(x, y, complete.results[1])
  535.       complete.results = nil
  536.  
  537.     elseif #complete.results > 1 then
  538.       local prefix = complete.results[1]
  539.       for n = 1, #complete.results do
  540.         local result = complete.results[n]
  541.         while #prefix > 0 do
  542.           if result:find(prefix, 1, true) == 1 then
  543.             break
  544.           end
  545.           prefix = prefix:sub(1, #prefix - 1)
  546.         end
  547.       end
  548.       if #prefix > 0 then
  549.         actions.insertText(x, y, prefix)
  550.         complete.results = nil
  551.       else
  552.         if complete.index > 0 then
  553.           actions.deleteText(complete.x, y, complete.x + #complete.results[complete.index], y)
  554.         end
  555.         complete.index = complete.index + 1
  556.         if complete.index > #complete.results then
  557.           complete.index = 1
  558.         end
  559.         actions.insertText(complete.x, y, complete.results[complete.index])
  560.       end
  561.     end
  562.   end,
  563.  
  564.   refresh = function()
  565.     actions.dirty_all()
  566.     mark.continue = mark.active
  567.     setStatus('refreshed')
  568.   end,
  569.  
  570.   menu = function()
  571.     setStatus(messages.menu)
  572.     mark.continue = mark.active
  573.   end,
  574.  
  575.   goto_line = function()
  576.     local lineNo = tonumber(actions.input('Line: '))
  577.     if lineNo then
  578.       actions.goto(1, lineNo)
  579.     else
  580.       setStatus('Invalid line number')
  581.     end
  582.   end,
  583.  
  584.   find = function(pattern, sx)
  585.     local nLines = #tLines
  586.     for i = 1, nLines + 1 do
  587.       local ny = y + i - 1
  588.       if ny > nLines then
  589.         ny = ny - nLines
  590.       end
  591.       local nx = tLines[ny]:lower():find(pattern, sx)
  592.       if nx then
  593.         if ny < y or ny == y and nx <= x then
  594.           setStatus(messages.wrapped)
  595.         end
  596.         actions.goto(nx, ny)
  597.         actions.mark_to(nx + #pattern, ny)
  598.         actions.goto(nx, ny)
  599.         return
  600.       end
  601.       sx = 1
  602.     end
  603.     setError('Pattern not found')
  604.   end,
  605.  
  606.   find_next = function()
  607.     if searchPattern then
  608.       actions.unmark()
  609.       actions.find(searchPattern, x + 1)
  610.     end
  611.   end,
  612.  
  613.   find_prompt = function()
  614.     local text = actions.input('/')
  615.     if #text > 0 then
  616.       searchPattern = text:lower()
  617.       if searchPattern then
  618.         actions.unmark()
  619.         actions.find(searchPattern, x)
  620.       end
  621.     end
  622.   end,
  623.  
  624.   save = function()
  625.     if bReadOnly then
  626.       setError("Access denied")
  627.     else
  628.       local ok, err = save(sPath)
  629.       if ok then
  630.         setStatus('"%s" %dL, %dC written',
  631.            fileInfo.path, #tLines, fs.getSize(fileInfo.abspath))
  632.       else
  633.         setError("Error saving to %s", sPath)
  634.       end
  635.     end
  636.   end,
  637.  
  638.   exit = function()
  639.     bRunning = false
  640.   end,
  641.  
  642.   run = function()
  643.     local sTempPath = "/.temp"
  644.     local ok, err = save(sTempPath)
  645.     if ok then
  646.       local nTask = shell.openTab(sTempPath)
  647.       if nTask then
  648.         shell.switchTab(nTask)
  649.       else
  650.         setError("Error starting Task")
  651.       end
  652.       os.sleep(0)
  653.       fs.delete(sTempPath)
  654.     else
  655.       setError("Error saving to %s", sTempPath)
  656.     end
  657.   end,
  658.  
  659.   status = function()
  660.     local modified = ''
  661.     if undo.chain[1] then
  662.       modified = '[Modified] '
  663.     end
  664.     setStatus('"%s" %s%d lines --%d%%--',
  665.          fileInfo.path, modified, #tLines,
  666.          math.floor((y - 1) / (#tLines - 1) * 100))
  667.   end,
  668.  
  669.   dirty_line = function(dy)
  670.     if dirty.y == 0 then
  671.       dirty.y = dy
  672.       dirty.ey = dy
  673.     else
  674.       dirty.y = math.min(dirty.y, dy)
  675.       dirty.ey = math.max(dirty.ey, dy)
  676.     end
  677.   end,
  678.  
  679.   dirty_range = function(dy, dey)
  680.     actions.dirty_line(dy)
  681.     actions.dirty_line(dey or #tLines)
  682.   end,
  683.  
  684.   dirty = function()
  685.     actions.dirty_line(y)
  686.   end,
  687.  
  688.   dirty_all = function()
  689.     actions.dirty_line(1)
  690.     actions.dirty_line(#tLines)
  691.   end,
  692.  
  693.   mark_begin = function()
  694.     actions.dirty()
  695.     if not mark.active then
  696.       mark.active = true
  697.       mark.anchor = { x = x, y = y }
  698.     end
  699.   end,
  700.  
  701.   mark_finish = function()
  702.     if y == mark.anchor.y then
  703.       if x == mark.anchor.x then
  704.         mark.active = false
  705.       else
  706.         mark.x = math.min(mark.anchor.x, x)
  707.         mark.y = y
  708.         mark.ex = math.max(mark.anchor.x, x)
  709.         mark.ey = y
  710.       end
  711.     elseif y < mark.anchor.y then
  712.       mark.x = x
  713.       mark.y = y
  714.       mark.ex = mark.anchor.x
  715.       mark.ey = mark.anchor.y
  716.     else
  717.       mark.x = mark.anchor.x
  718.       mark.y = mark.anchor.y
  719.       mark.ex = x
  720.       mark.ey = y
  721.     end
  722.     actions.dirty()
  723.     mark.continue = mark.active
  724.   end,
  725.  
  726.   unmark = function()
  727.     if mark.active then
  728.       actions.dirty_range(mark.y, mark.ey)
  729.       mark.active = false
  730.     end
  731.   end,
  732.  
  733.   mark_to = function(nx, ny)
  734.     actions.mark_begin()
  735.     actions.goto(nx, ny)
  736.     actions.mark_finish()
  737.   end,
  738.  
  739.   mark_up = function()
  740.     actions.mark_begin()
  741.     actions.up()
  742.     actions.mark_finish()
  743.   end,
  744.  
  745.   mark_right = function()
  746.     actions.mark_begin()
  747.     actions.right()
  748.     actions.mark_finish()
  749.   end,
  750.  
  751.   mark_down = function()
  752.     actions.mark_begin()
  753.     actions.down()
  754.     actions.mark_finish()
  755.   end,
  756.  
  757.   mark_left = function()
  758.     actions.mark_begin()
  759.     actions.left()
  760.     actions.mark_finish()
  761.   end,
  762.  
  763.   mark_word = function()
  764.     actions.mark_begin()
  765.     actions.word()
  766.     actions.mark_finish()
  767.   end,
  768.  
  769.   mark_backword = function()
  770.     actions.mark_begin()
  771.     actions.backword()
  772.     actions.mark_finish()
  773.   end,
  774.  
  775.   mark_home = function()
  776.     actions.mark_begin()
  777.     actions.home()
  778.     actions.mark_finish()
  779.   end,
  780.  
  781.   mark_end = function()
  782.     actions.mark_begin()
  783.     actions.toend()
  784.     actions.mark_finish()
  785.   end,
  786.  
  787.   mark_all = function()
  788.     mark.anchor = { x = 1, y = 1 }
  789.     mark.active = true
  790.     mark.continue = true
  791.     mark.x = 1
  792.     mark.y = 1
  793.     mark.ey = #tLines
  794.     mark.ex = #tLines[mark.ey] + 1
  795.     actions.dirty_all()
  796.   end,
  797.  
  798.   setCursor = function(newX, newY)
  799.     local oldX, oldY = lastPos.x, lastPos.y
  800.  
  801.     lastPos.x = x
  802.     lastPos.y = y
  803.  
  804.     local screenX = x - scrollX
  805.     local screenY = y - scrollY
  806.  
  807.     if screenX < 1 then
  808.       scrollX = x - 1
  809.       screenX = 1
  810.       actions.dirty_all()
  811.     elseif screenX > w then
  812.       scrollX = x - w
  813.       screenX = w
  814.       actions.dirty_all()
  815.     end
  816.  
  817.     if screenY < 1 then
  818.       scrollY = y - 1
  819.       screenY = 1
  820.       actions.dirty_all()
  821.     elseif screenY > h - 1 then
  822.       scrollY = y - (h - 1)
  823.       screenY = h - 1
  824.       actions.dirty_all()
  825.     end
  826.   end,
  827.  
  828.   top = function()
  829.     actions.goto(1, 1)
  830.   end,
  831.  
  832.   bottom = function()
  833.     y = #tLines
  834.     x = #tLines[y] + 1
  835.   end,
  836.  
  837.   up = function()
  838.     if y > 1 then
  839.       x = math.min(x, #tLines[y - 1] + 1)
  840.       y = y - 1
  841.     end
  842.   end,
  843.  
  844.   down = function()
  845.     if y < #tLines then
  846.       x = math.min(x, #tLines[y + 1] + 1)
  847.       y = y + 1
  848.     end
  849.   end,
  850.  
  851.   tab = function()
  852.     if mark.active then
  853.       actions.delete()
  854.     end
  855.     actions.insertText(x, y, '  ')
  856.   end,
  857.  
  858.   pageUp = function()
  859.     actions.goto(x, y - (h - 1))
  860.   end,
  861.  
  862.   pageDown = function()
  863.     actions.goto(x, y + (h - 1))
  864.   end,
  865.  
  866.   home = function()
  867.     x = 1
  868.   end,
  869.  
  870.   toend = function()
  871.     x = #tLines[y] + 1
  872.   end,
  873.  
  874.   left = function()
  875.     if x > 1 then
  876.       x = x - 1
  877.     elseif y > 1 then
  878.       x = #tLines[y - 1] + 1
  879.       y = y - 1
  880.     else
  881.       return false
  882.     end
  883.     return true
  884.   end,
  885.  
  886.   right = function()
  887.     if x < #tLines[y] + 1 then
  888.       x = x + 1
  889.     elseif y < #tLines then
  890.       x = 1
  891.       y = y + 1
  892.     end
  893.   end,
  894.  
  895.   word = function()
  896.     local nx = nextWord(tLines[y], x)
  897.     if nx then
  898.       x = nx
  899.     elseif x < #tLines[y] + 1 then
  900.       x = #tLines[y] + 1
  901.     elseif y < #tLines then
  902.       x = 1
  903.       y = y + 1
  904.     end
  905.   end,
  906.  
  907.   backword = function()
  908.     if x == 1 then
  909.       actions.left()
  910.     else
  911.       local sLine = tLines[y]
  912.       local lx = 1
  913.       while true do
  914.         local nx = nextWord(sLine, lx)
  915.         if not nx or nx >= x then
  916.           break
  917.         end
  918.         lx = nx
  919.       end
  920.       if not lx then
  921.         x = 1
  922.       else
  923.         x = lx
  924.       end
  925.     end
  926.   end,
  927.  
  928.   insertText = function(sx, sy, text)
  929.     x = sx
  930.     y = sy
  931.     local sLine = tLines[y]
  932.  
  933.     if not text:find('\n') then
  934.       tLines[y] = sLine:sub(1, x - 1) .. text .. sLine:sub(x)
  935.       actions.dirty_line(y)
  936.       x = x + #text
  937.     else
  938.       local lines = split(text)
  939.       local remainder = sLine:sub(x)
  940.       tLines[y] = sLine:sub(1, x - 1) .. lines[1]
  941.       actions.dirty_range(y, #tLines + #lines)
  942.       x = x + #lines[1]
  943.       for k = 2, #lines do
  944.         y = y + 1
  945.         table.insert(tLines, y, lines[k])
  946.         x = #lines[k] + 1
  947.       end
  948.       tLines[y] = tLines[y]:sub(1, x) .. remainder
  949.     end
  950.  
  951.     if not undo.active then
  952.       actions.addUndo(
  953.         { action = 'deleteText', args = { sx, sy, x, y, text } })
  954.     end
  955.   end,
  956.  
  957.   deleteText = function(sx, sy, ex, ey)
  958.     x = sx
  959.     y = sy
  960.  
  961.     if not undo.active then
  962.       local text = actions.copyText(sx, sy, ex, ey)
  963.       actions.addUndo(
  964.         { action = 'insertText', args = { sx, sy, text } })
  965.     end
  966.  
  967.     local front = tLines[sy]:sub(1, sx - 1)
  968.     local back = tLines[ey]:sub(ex, #tLines[ey])
  969.     for k = 2, ey - sy + 1 do
  970.       table.remove(tLines, y + 1)
  971.     end
  972.     tLines[y] = front .. back
  973.     if sy ~= ey then
  974.       actions.dirty_range(y)
  975.     else
  976.       actions.dirty()
  977.     end
  978.   end,
  979.  
  980.   copyText = function(csx, csy, cex, cey)
  981.     local count = 0
  982.     local lines = { }
  983.  
  984.     for y = csy, cey do
  985.       local line = tLines[y]
  986.       if line then
  987.         local x = 1
  988.         local ex = #line
  989.         if y == csy then
  990.           x = csx
  991.         end
  992.         if y == cey then
  993.           ex = cex - 1
  994.         end
  995.         local str = line:sub(x, ex)
  996.         count = count + #str
  997.         table.insert(lines, str)
  998.       end
  999.     end
  1000.     return table.concat(lines, '\n'), count
  1001.   end,
  1002.  
  1003.   delete = function()
  1004.     if mark.active then
  1005.       actions.deleteText(mark.x, mark.y, mark.ex, mark.ey)
  1006.     else
  1007.       local nLimit = #tLines[y] + 1
  1008.       if x < nLimit then
  1009.         actions.deleteText(x, y, x + 1, y)
  1010.       elseif y < #tLines then
  1011.         actions.deleteText(x, y, 1, y + 1)
  1012.       end
  1013.     end
  1014.   end,
  1015.  
  1016.   backspace = function()
  1017.     if mark.active then
  1018.       actions.delete()
  1019.     elseif actions.left() then
  1020.       actions.delete()
  1021.     end
  1022.   end,
  1023.  
  1024.   enter = function()
  1025.     local sLine = tLines[y]
  1026.     local _,spaces = sLine:find("^[ ]+")
  1027.     if not spaces then
  1028.       spaces = 0
  1029.     end
  1030.     spaces = math.min(spaces, x - 1)
  1031.     if mark.active then
  1032.       actions.delete()
  1033.     end
  1034.     actions.insertText(x, y, '\n' .. string.rep(' ', spaces))
  1035.   end,
  1036.  
  1037.   char = function(ch)
  1038.     if mark.active then
  1039.       actions.delete()
  1040.     end
  1041.     actions.insertText(x, y, ch)
  1042.   end,
  1043.  
  1044.   toggle_clipboard = function()
  1045.     if clipboard.shim then
  1046.       clipboard.setInternal(not clipboard.internal)
  1047.     end
  1048.     if clipboard.isInternal() then
  1049.       setStatus('Using internal clipboard')
  1050.     else
  1051.       setStatus('Using system clipboard')
  1052.     end
  1053.   end,
  1054.  
  1055.   copy_marked = function()
  1056.     local text, size =
  1057.         actions.copyText(mark.x, mark.y, mark.ex, mark.ey)
  1058.     clipboard.setData(text)
  1059.     setStatus('%d chars copied', size)
  1060.     clipboard.useInternal(true)
  1061.   end,
  1062.  
  1063.   cut = function()
  1064.     if mark.active then
  1065.       actions.copy_marked()
  1066.       actions.delete()
  1067.     end
  1068.   end,
  1069.  
  1070.   copy = function()
  1071.     if mark.active then
  1072.       actions.copy_marked()
  1073.       mark.continue = true
  1074.     end
  1075.   end,
  1076.  
  1077.   paste = function(text)
  1078.     if mark.active then
  1079.       actions.delete()
  1080.     end
  1081.     if clipboard.isInternal() then
  1082.       text = clipboard.getText()
  1083.     end
  1084.     if text then
  1085.       actions.insertText(x, y, text)
  1086.       setStatus('%d chars added', #text)
  1087.     else
  1088.       setStatus('Clipboard empty')
  1089.     end
  1090.   end,
  1091.  
  1092.   goto = function(cx, cy)
  1093.     y = math.min(math.max(cy, 1), #tLines)
  1094.     x = math.min(math.max(cx, 1), #tLines[y] + 1)
  1095.   end,
  1096.  
  1097.   scroll_up = function()
  1098.     if scrollY > 0 then
  1099.       scrollY = scrollY - 1
  1100.       actions.dirty_all()
  1101.     end
  1102.     mark.continue = mark.active
  1103.   end,
  1104.  
  1105.   scroll_down = function()
  1106.     local nMaxScroll = #tLines - (h-1)
  1107.     if scrollY < nMaxScroll then
  1108.       scrollY = scrollY + 1
  1109.       actions.dirty_all()
  1110.     end
  1111.     mark.continue = mark.active
  1112.   end,
  1113. }
  1114.  
  1115. actions = __actions
  1116.  
  1117. -- Actual program functionality begins
  1118. load(sPath)
  1119.  
  1120. term.setCursorBlink(true)
  1121. redraw()
  1122.  
  1123. if not keyboard then
  1124.   keyboard = { control, shift, combo }
  1125.  
  1126.   function keyboard:translate(event, code)
  1127.     if event == 'key' then
  1128.       local ch = keys.getName(code)
  1129.       if ch then
  1130.  
  1131.         if code == keys.leftCtrl or code == keys.rightCtrl then
  1132.           self.control = true
  1133.           self.combo = false
  1134.           return
  1135.         end
  1136.  
  1137.         if code == keys.leftShift or code == keys.rightShift  then
  1138.           self.shift = true
  1139.           self.combo = false
  1140.           return
  1141.         end
  1142.  
  1143.         if self.shift then
  1144.           if #ch > 1 then
  1145.             ch = 'shift-' .. ch
  1146.           elseif self.control then
  1147.             -- will create control-X
  1148.             -- better than shift-control-x
  1149.             ch = ch:upper()
  1150.           end
  1151.           self.combo = true
  1152.         end
  1153.  
  1154.         if self.control then
  1155.           ch = 'control-' .. ch
  1156.           self.combo = true
  1157.           -- even return numbers such as
  1158.           -- control-seven
  1159.           return ch
  1160.         end
  1161.  
  1162.         -- filter out characters that will be processed in
  1163.         -- the subsequent char event
  1164.         if ch and #ch > 1 and (code < 2 or code > 11) then
  1165.           return ch
  1166.         end
  1167.       end
  1168.  
  1169.     elseif event == 'key_up' then
  1170.  
  1171.       if code == keys.leftCtrl or code == keys.rightCtrl then
  1172.         self.control = false
  1173.       elseif code == keys.leftShift or code == keys.rightShift then
  1174.         self.shift = false
  1175.       else
  1176.         return
  1177.       end
  1178.  
  1179.       -- only send through the shift / control event if it wasn't
  1180.       -- used in combination with another event
  1181.       if not self.combo then
  1182.         return keys.getName(code)
  1183.       end
  1184.  
  1185.     elseif event == 'char' then
  1186.       if not self.control then
  1187.         self.combo = true
  1188.         return event
  1189.       end
  1190.  
  1191.     elseif event == 'mouse_click' then
  1192.  
  1193.       local buttons = { 'mouse_click', 'mouse_rightclick', 'mouse_doubleclick' }
  1194.  
  1195.       self.combo = true
  1196.       if self.shift then
  1197.         return 'shift-' .. buttons[code]
  1198.       end
  1199.       return buttons[code]
  1200.  
  1201.     elseif event == "mouse_scroll" then
  1202.       local directions = {
  1203.         [ -1 ] = 'scrollUp',
  1204.         [  1 ] = 'scrollDown'
  1205.       }
  1206.       return directions[code]
  1207.  
  1208.     elseif event == 'paste' then
  1209.       self.combo = true
  1210.       return event
  1211.  
  1212.     elseif event == 'mouse_drag' then
  1213.       return event
  1214.     end
  1215.   end
  1216. end
  1217.  
  1218. while bRunning do
  1219.   local sEvent, param, param2, param3 = os.pullEventRaw()
  1220.   local action
  1221.  
  1222.   if sEvent == 'terminate' then
  1223.     action = 'exit'
  1224.   elseif sEvent == "mouse_click" or sEvent == 'mouse_drag' then
  1225.     if param3 < h or sEvent == 'mouse_drag' then
  1226.       local ch = keyboard:translate(sEvent, param)
  1227.       if ch then
  1228.         action = keyMapping[ch]
  1229.         param = param2 + scrollX
  1230.         param2 = param3 + scrollY
  1231.       end
  1232.     end
  1233.   else
  1234.     local ch = keyboard:translate(sEvent, param)
  1235.     if ch then
  1236.       action = keyMapping[ch]
  1237.     end
  1238.   end
  1239.  
  1240.   if action then
  1241.     if not actions[action] then
  1242.       error('Invaid action: ' .. action)
  1243.     end
  1244.  
  1245.     local wasMarking = mark.continue
  1246.     mark.continue = false
  1247.  
  1248.     actions[action](param, param2)
  1249.     if action ~= 'menu' then
  1250.       keyboard.lastAction = action
  1251.     end
  1252.  
  1253.     if x ~= lastPos.x or y ~= lastPos.y then
  1254.       actions.setCursor()
  1255.     end
  1256.     if not mark.continue and wasMarking then
  1257.       actions.unmark()
  1258.     end
  1259.  
  1260.     redraw()
  1261.  
  1262.   elseif sEvent == "term_resize" then
  1263.     w,h = term.getSize()
  1264.     actions.setCursor(x, y)
  1265.     actions.dirty_all()
  1266.     redraw()
  1267.   end
  1268. end
  1269.  
  1270. -- Cleanup
  1271. term.setBackgroundColor(colors.black)
  1272. term.setTextColor(colors.white)
  1273. term.clear()
  1274. term.setCursorBlink(false)
  1275. term.setCursorPos(1, 1)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement