tomtrein

text viewer

Oct 16th, 2016
145
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- Get file to edit
  2. local tArgs = { ... }
  3. if #tArgs == 0 then
  4.     print( "Usage: edit <path>" )
  5.     return
  6. end
  7.  
  8. -- Error checking
  9. local sPath = shell.resolve( tArgs[1] )
  10. local bReadOnly = fs.isReadOnly( sPath )
  11. if fs.exists( sPath ) and fs.isDir( sPath ) then
  12.     print( "Cannot edit a directory." )
  13.     return
  14. end
  15.  
  16. local x,y = 1,1
  17. local w,h = term.getSize()
  18. local scrollX, scrollY = 0,0
  19.  
  20. local tLines = {}
  21. local bRunning = true
  22.  
  23. -- Colours
  24. local highlightColour, keywordColour, commentColour, textColour, bgColour
  25. if term.isColour() then
  26.     bgColour = colours.black
  27.     textColour = colours.white
  28.     highlightColour = colours.yellow
  29.     keywordColour = colours.yellow
  30.     commentColour = colours.green
  31.     stringColour = colours.red
  32. else
  33.     bgColour = colours.black
  34.     textColour = colours.white
  35.     highlightColour = colours.white
  36.     keywordColour = colours.white
  37.     commentColour = colours.white
  38.     stringColour = colours.white
  39. end
  40.  
  41. -- Menus
  42. local bMenu = false
  43. local nMenuItem = 1
  44. local tMenuItems = {}
  45. if not bReadOnly then
  46.     table.insert( tMenuItems, "Delete" )
  47. end
  48. table.insert( tMenuItems, "Exit" )
  49.  
  50. local sStatus = "Press Ctrl to access menu"
  51. if string.len( sStatus ) > w - 5 then
  52.     sStatus = "Press Ctrl for menu"
  53. end
  54.  
  55. local function load( _sPath )
  56.     tLines = {}
  57.     if fs.exists( _sPath ) then
  58.         local file = io.open( _sPath, "r" )
  59.         local sLine = file:read()
  60.         while sLine do
  61.             table.insert( tLines, sLine )
  62.             sLine = file:read()
  63.         end
  64.         file:close()
  65.     end
  66.    
  67.     if #tLines == 0 then
  68.         table.insert( tLines, "" )
  69.     end
  70. end
  71.  
  72. local function save( _sPath )
  73.     -- Create intervening folder
  74.     local sDir = _sPath:sub(1, _sPath:len() - fs.getName(_sPath):len() )
  75.     if not fs.exists( sDir ) then
  76.         fs.makeDir( sDir )
  77.     end
  78.  
  79.     -- Save
  80.     local file = nil
  81.     local function innerSave()
  82.         file = fs.open( _sPath, "w" )
  83.         if file then
  84.             for n, sLine in ipairs( tLines ) do
  85.                 file.write( sLine .. "\n" )
  86.             end
  87.         else
  88.             error( "Failed to open ".._sPath )
  89.         end
  90.     end
  91.    
  92.     local ok, err = pcall( innerSave )
  93.     if file then
  94.         file.close()
  95.     end
  96.     return ok, err
  97. end
  98.  
  99. local tKeywords = {
  100. }
  101.  
  102. local function tryWrite( sLine, regex, colour )
  103.     local match = string.match( sLine, regex )
  104.     if match then
  105.         if type(colour) == "number" then
  106.             term.setTextColour( colour )
  107.         else
  108.             term.setTextColour( colour(match) )
  109.         end
  110.         term.write( match )
  111.         term.setTextColour( textColour )
  112.         return string.sub( sLine, string.len(match) + 1 )
  113.     end
  114.     return nil
  115. end
  116.  
  117. local function writeHighlighted( sLine )
  118.     while string.len(sLine) > 0 do 
  119.         sLine =
  120.             tryWrite( sLine, "^%-%-%[%[.-%]%]", commentColour ) or
  121.             tryWrite( sLine, "^%-%-.*", commentColour ) or
  122.             tryWrite( sLine, "^\".-[^\\]\"", stringColour ) or
  123.             tryWrite( sLine, "^\'.-[^\\]\'", stringColour ) or
  124.             tryWrite( sLine, "^%[%[.-%]%]", stringColour ) or
  125.             tryWrite( sLine, "^[%w_]+", function( match )
  126.                 if tKeywords[ match ] then
  127.                     return keywordColour
  128.                 end
  129.                 return textColour
  130.             end ) or
  131.             tryWrite( sLine, "^[^%w_]", textColour )
  132.     end
  133. end
  134.  
  135. local tCompletions
  136. local nCompletion
  137.  
  138. local tCompleteEnv = _ENV
  139. local function complete( sLine )
  140.     local nStartPos = string.find( sLine, "[a-zA-Z0-9_%.]+$" )
  141.     if nStartPos then
  142.         sLine = string.sub( sLine, nStartPos )
  143.     end
  144.     if #sLine > 0 then
  145.         return textutils.complete( sLine, tCompleteEnv )
  146.     end
  147.     return nil
  148. end
  149.  
  150. local function recomplete()
  151.     local sLine = tLines[y]
  152.     if not bMenu and not bReadOnly and x == string.len(sLine) + 1 then
  153.         tCompletions = complete( sLine )
  154.         if tCompletions and #tCompletions > 0 then
  155.             nCompletion = 1
  156.         else
  157.             nCompletion = nil
  158.         end
  159.     else
  160.         tCompletions = nil
  161.         nCompletion = nil
  162.     end
  163. end
  164.  
  165. local function writeCompletion( sLine )
  166.     if nCompletion then
  167.         local sCompletion = tCompletions[ nCompletion ]
  168.         term.setTextColor( colours.white )
  169.         term.setBackgroundColor( colours.grey )
  170.         term.write( sCompletion )
  171.         term.setTextColor( textColour )
  172.         term.setBackgroundColor( bgColour )
  173.     end
  174. end
  175.  
  176. local function redrawText()
  177.     local cursorX, cursorY = x, y
  178.     for y=1,h-1 do
  179.         term.setCursorPos( 1 - scrollX, y )
  180.         term.clearLine()
  181.  
  182.         local sLine = tLines[ y + scrollY ]
  183.         if sLine ~= nil then
  184.             writeHighlighted( sLine )
  185.             if cursorY == y and cursorX == #sLine + 1 then
  186.                 writeCompletion()
  187.             end
  188.         end
  189.     end
  190.     term.setCursorPos( x - scrollX, y - scrollY )
  191. end
  192.  
  193. local function redrawLine(_nY)
  194.     local sLine = tLines[_nY]
  195.     if sLine then
  196.         term.setCursorPos( 1 - scrollX, _nY - scrollY )
  197.         term.clearLine()
  198.         writeHighlighted( sLine )
  199.         if _nY == y and x == #sLine + 1 then
  200.             writeCompletion()
  201.         end
  202.         term.setCursorPos( x - scrollX, _nY - scrollY )
  203.     end
  204. end
  205.  
  206. local function redrawMenu()
  207.     -- Clear line
  208.     term.setCursorPos( 1, h )
  209.     term.clearLine()
  210.  
  211.     -- Draw line numbers
  212.     term.setCursorPos( w - string.len( "Ln "..y ) + 1, h )
  213.     term.setTextColour( highlightColour )
  214.     term.write( "Ln " )
  215.     term.setTextColour( textColour )
  216.     term.write( y )
  217.  
  218.     term.setCursorPos( 1, h )
  219.     if bMenu then
  220.         -- Draw menu
  221.         term.setTextColour( textColour )
  222.         for nItem,sItem in pairs( tMenuItems ) do
  223.             if nItem == nMenuItem then
  224.                 term.setTextColour( highlightColour )
  225.                 term.write( "[" )
  226.                 term.setTextColour( textColour )
  227.                 term.write( sItem )
  228.                 term.setTextColour( highlightColour )
  229.                 term.write( "]" )
  230.                 term.setTextColour( textColour )
  231.             else
  232.                 term.write( " "..sItem.." " )
  233.             end
  234.         end
  235.     else
  236.         -- Draw status
  237.         term.setTextColour( highlightColour )
  238.         term.write( sStatus )
  239.         term.setTextColour( textColour )
  240.     end
  241.  
  242.     -- Reset cursor
  243.     term.setCursorPos( x - scrollX, y - scrollY )
  244. end
  245.  
  246. local tMenuFuncs = {
  247.     Delete = function()
  248.         shell.run("delete", sPath)
  249.         bRunning = false
  250.     end,
  251.     Print = function()
  252.         local printer = peripheral.find( "printer" )
  253.         if not printer then
  254.             sStatus = "No printer attached"
  255.             return
  256.         end
  257.  
  258.         local nPage = 0
  259.         local sName = fs.getName( sPath )
  260.         if printer.getInkLevel() < 1 then
  261.             sStatus = "Printer out of ink"
  262.             return
  263.         elseif printer.getPaperLevel() < 1 then
  264.             sStatus = "Printer out of paper"
  265.             return
  266.         end
  267.  
  268.         local screenTerminal = term.current()
  269.         local printerTerminal = {
  270.             getCursorPos = printer.getCursorPos,
  271.             setCursorPos = printer.setCursorPos,
  272.             getSize = printer.getPageSize,
  273.             write = printer.write,
  274.         }
  275.         printerTerminal.scroll = function()
  276.             if nPage == 1 then
  277.                 printer.setPageTitle( sName.." (page "..nPage..")" )           
  278.             end
  279.            
  280.             while not printer.newPage() do
  281.                 if printer.getInkLevel() < 1 then
  282.                     sStatus = "Printer out of ink, please refill"
  283.                 elseif printer.getPaperLevel() < 1 then
  284.                     sStatus = "Printer out of paper, please refill"
  285.                 else
  286.                     sStatus = "Printer output tray full, please empty"
  287.                 end
  288.    
  289.                 term.redirect( screenTerminal )
  290.                 redrawMenu()
  291.                 term.redirect( printerTerminal )
  292.                
  293.                 local timer = os.startTimer(0.5)
  294.                 sleep(0.5)
  295.             end
  296.  
  297.             nPage = nPage + 1
  298.             if nPage == 1 then
  299.                 printer.setPageTitle( sName )
  300.             else
  301.                 printer.setPageTitle( sName.." (page "..nPage..")" )
  302.             end
  303.         end
  304.        
  305.         bMenu = false
  306.         term.redirect( printerTerminal )
  307.         local ok, error = pcall( function()
  308.             term.scroll()
  309.             for n, sLine in ipairs( tLines ) do
  310.                 print( sLine )
  311.             end
  312.         end )
  313.         term.redirect( screenTerminal )
  314.         if not ok then
  315.             print( error )
  316.         end
  317.        
  318.         while not printer.endPage() do
  319.             sStatus = "Printer output tray full, please empty"
  320.             redrawMenu()
  321.             sleep( 0.5 )
  322.         end
  323.         bMenu = true
  324.            
  325.         if nPage > 1 then
  326.             sStatus = "Printed "..nPage.." Pages"
  327.         else
  328.             sStatus = "Printed 1 Page"
  329.         end
  330.         redrawMenu()
  331.     end,
  332.     Exit = function()
  333.         bRunning = false
  334.     end,
  335.     Run = function()
  336.         local sTempPath = "/.temp"
  337.         local ok, err = save( sTempPath )
  338.         if ok then
  339.             local nTask = shell.openTab( sTempPath )
  340.             if nTask then
  341.                 shell.switchTab( nTask )
  342.             else
  343.                 sStatus="Error starting Task"
  344.             end
  345.             fs.delete( sTempPath )
  346.         else
  347.             sStatus="Error saving to "..sTempPath
  348.         end
  349.         redrawMenu()
  350.     end
  351. }
  352.  
  353. local function doMenuItem( _n )
  354.     tMenuFuncs[tMenuItems[_n]]()
  355.     if bMenu then
  356.         bMenu = false
  357.         term.setCursorBlink( true )
  358.     end
  359.     redrawMenu()
  360. end
  361.  
  362. local function setCursor( newX, newY )
  363.     local oldX, oldY = x, y
  364.     x, y = newX, newY
  365.     local screenX = x - scrollX
  366.     local screenY = y - scrollY
  367.    
  368.     local bRedraw = false
  369.     if screenX < 1 then
  370.         scrollX = x - 1
  371.         screenX = 1
  372.         bRedraw = true
  373.     elseif screenX > w then
  374.         scrollX = x - w
  375.         screenX = w
  376.         bRedraw = true
  377.     end
  378.    
  379.     if screenY < 1 then
  380.         scrollY = y - 1
  381.         screenY = 1
  382.         bRedraw = true
  383.     elseif screenY > h-1 then
  384.         scrollY = y - (h-1)
  385.         screenY = h-1
  386.         bRedraw = true
  387.     end
  388.  
  389.     recomplete()
  390.     if bRedraw then
  391.         redrawText()
  392.     elseif y ~= oldY then
  393.         redrawLine( oldY )
  394.         redrawLine( y )
  395.     else
  396.         redrawLine( y )
  397.     end
  398.     term.setCursorPos( screenX, screenY )
  399.  
  400.     redrawMenu()
  401. end
  402.  
  403. -- Actual program functionality begins
  404. load(sPath)
  405.  
  406. term.setBackgroundColour( bgColour )
  407. term.clear()
  408. term.setCursorPos(x,y)
  409. term.setCursorBlink( true )
  410.  
  411. recomplete()
  412. redrawText()
  413. redrawMenu()
  414.  
  415. local function acceptCompletion()
  416.     if nCompletion then
  417.         -- Find the common prefix of all the other suggestions which start with the same letter as the current one
  418.         local sCompletion = tCompletions[ nCompletion ]
  419.         local sFirstLetter = string.sub( sCompletion, 1, 1 )
  420.         local sCommonPrefix = sCompletion
  421.         for n=1,#tCompletions do
  422.             local sResult = tCompletions[n]
  423.             if n ~= nCompletion and string.find( sResult, sFirstLetter, 1, true ) == 1 then
  424.                 while #sCommonPrefix > 1 do
  425.                     if string.find( sResult, sCommonPrefix, 1, true ) == 1 then
  426.                         break
  427.                     else
  428.                         sCommonPrefix = string.sub( sCommonPrefix, 1, #sCommonPrefix - 1 )
  429.                     end
  430.                 end
  431.             end
  432.         end
  433.  
  434.         -- Append this string
  435.         tLines[y] = tLines[y] .. sCommonPrefix
  436.         setCursor( x + string.len( sCommonPrefix ), y )
  437.     end
  438. end
  439.  
  440. -- Handle input
  441. while bRunning do
  442.     local sEvent, param, param2, param3 = os.pullEvent()
  443.     if sEvent == "key" then
  444.         local oldX, oldY = x, y
  445.         if param == keys.up then
  446.             -- Up
  447.             if not bMenu then
  448.                 if nCompletion then
  449.                     -- Cycle completions
  450.                     nCompletion = nCompletion - 1
  451.                     if nCompletion < 1 then
  452.                         nCompletion = #tCompletions
  453.                     end
  454.                     redrawLine(y)
  455.  
  456.                 elseif y > 1 then
  457.                     -- Move cursor up
  458.                     setCursor(
  459.                         math.min( x, string.len( tLines[y - 1] ) + 1 ),
  460.                         y - 1
  461.                     )
  462.                 end
  463.             end
  464.  
  465.         elseif param == keys.down then
  466.             -- Down
  467.             if not bMenu then
  468.                 -- Move cursor down
  469.                 if nCompletion then
  470.                     -- Cycle completions
  471.                     nCompletion = nCompletion + 1
  472.                     if nCompletion > #tCompletions then
  473.                         nCompletion = 1
  474.                     end
  475.                     redrawLine(y)
  476.  
  477.                 elseif y < #tLines then
  478.                     -- Move cursor down
  479.                     setCursor(
  480.                         math.min( x, string.len( tLines[y + 1] ) + 1 ),
  481.                         y + 1
  482.                     )
  483.                 end
  484.             end
  485.  
  486.         elseif param == keys.tab then
  487.             -- Tab
  488.             if not bMenu and not bReadOnly then
  489.                 if nCompletion and x == string.len(tLines[y]) + 1 then
  490.                     -- Accept autocomplete
  491.                     acceptCompletion()
  492.                 else
  493.                     -- Indent line
  494.                     local sLine = tLines[y]
  495.                     tLines[y] = string.sub(sLine,1,x-1) .. "  " .. string.sub(sLine,x)
  496.                     setCursor( x + 2, y )
  497.                 end
  498.             end
  499.  
  500.         elseif param == keys.pageUp then
  501.             -- Page Up
  502.             if not bMenu then
  503.                 -- Move up a page
  504.                 local newY
  505.                 if y - (h - 1) >= 1 then
  506.                     newY = y - (h - 1)
  507.                 else
  508.                     newY = 1
  509.                 end
  510.                 setCursor(
  511.                     math.min( x, string.len( tLines[newY] ) + 1 ),
  512.                     newY
  513.                 )
  514.             end
  515.  
  516.         elseif param == keys.pageDown then
  517.             -- Page Down
  518.             if not bMenu then
  519.                 -- Move down a page
  520.                 local newY
  521.                 if y + (h - 1) <= #tLines then
  522.                     newY = y + (h - 1)
  523.                 else
  524.                     newY = #tLines
  525.                 end
  526.                 local newX = math.min( x, string.len( tLines[newY] ) + 1 )
  527.                 setCursor( newX, newY )
  528.             end
  529.  
  530.         elseif param == keys.home then
  531.             -- Home
  532.             if not bMenu then
  533.                 -- Move cursor to the beginning
  534.                 if x > 1 then
  535.                     setCursor(1,y)
  536.                 end
  537.             end
  538.  
  539.         elseif param == keys["end"] then
  540.             -- End
  541.             if not bMenu then
  542.                 -- Move cursor to the end
  543.                 local nLimit = string.len( tLines[y] ) + 1
  544.                 if x < nLimit then
  545.                     setCursor( nLimit, y )
  546.                 end
  547.             end
  548.  
  549.         elseif param == keys.left then
  550.             -- Left
  551.             if not bMenu then
  552.                 if x > 1 then
  553.                     -- Move cursor left
  554.                     setCursor( x - 1, y )
  555.                 elseif x==1 and y>1 then
  556.                     setCursor( string.len( tLines[y-1] ) + 1, y - 1 )
  557.                 end
  558.             else
  559.                 -- Move menu left
  560.                 nMenuItem = nMenuItem - 1
  561.                 if nMenuItem < 1 then
  562.                     nMenuItem = #tMenuItems
  563.                 end
  564.                 redrawMenu()
  565.             end
  566.  
  567.         elseif param == keys.right then
  568.             -- Right
  569.             if not bMenu then
  570.                 local nLimit = string.len( tLines[y] ) + 1
  571.                 if x < nLimit then
  572.                     -- Move cursor right
  573.                     setCursor( x + 1, y )
  574.                 elseif nCompletion and x == string.len(tLines[y]) + 1 then
  575.                     -- Accept autocomplete
  576.                     acceptCompletion()
  577.                 elseif x==nLimit and y<#tLines then
  578.                     -- Go to next line
  579.                     setCursor( 1, y + 1 )
  580.                 end
  581.             else
  582.                 -- Move menu right
  583.                 nMenuItem = nMenuItem + 1
  584.                 if nMenuItem > #tMenuItems then
  585.                     nMenuItem = 1
  586.                 end
  587.                 redrawMenu()
  588.             end
  589.  
  590.         elseif param == keys.delete then
  591.             -- Delete
  592.             if not bMenu and not bReadOnly then
  593.                 local nLimit = string.len( tLines[y] ) + 1
  594.                 if x < nLimit then
  595.                     local sLine = tLines[y]
  596.                     tLines[y] = string.sub(sLine,1,x-1) .. string.sub(sLine,x+1)
  597.                     recomplete()
  598.                     redrawLine(y)
  599.                 elseif y<#tLines then
  600.                     tLines[y] = tLines[y] .. tLines[y+1]
  601.                     table.remove( tLines, y+1 )
  602.                     recomplete()
  603.                     redrawText()
  604.                 end
  605.             end
  606.  
  607.         elseif param == keys.backspace then
  608.             -- Backspace
  609.             if not bMenu and not bReadOnly then
  610.                 if x > 1 then
  611.                     -- Remove character
  612.                     local sLine = tLines[y]
  613.                     tLines[y] = string.sub(sLine,1,x-2) .. string.sub(sLine,x)
  614.                     setCursor( x - 1, y )
  615.                 elseif y > 1 then
  616.                     -- Remove newline
  617.                     local sPrevLen = string.len( tLines[y-1] )
  618.                     tLines[y-1] = tLines[y-1] .. tLines[y]
  619.                     table.remove( tLines, y )
  620.                     setCursor( sPrevLen + 1, y - 1 )
  621.                     redrawText()
  622.                 end
  623.             end
  624.  
  625.         elseif param == keys.enter then
  626.             -- Enter
  627.             if not bMenu and not bReadOnly then
  628.                 -- Newline
  629.                 local sLine = tLines[y]
  630.                 local _,spaces=string.find(sLine,"^[ ]+")
  631.                 if not spaces then
  632.                     spaces=0
  633.                 end
  634.                 tLines[y] = string.sub(sLine,1,x-1)
  635.                 table.insert( tLines, y+1, string.rep(' ',spaces)..string.sub(sLine,x) )
  636.                 setCursor( spaces + 1, y + 1 )
  637.                 redrawText()
  638.  
  639.             elseif bMenu then
  640.                 -- Menu selection
  641.                 doMenuItem( nMenuItem )
  642.  
  643.             end
  644.  
  645.         elseif param == keys.leftCtrl or param == keys.rightCtrl or param == keys.rightAlt then
  646.             -- Menu toggle
  647.             bMenu = not bMenu
  648.             if bMenu then
  649.                 term.setCursorBlink( false )
  650.             else
  651.                 term.setCursorBlink( true )
  652.             end
  653.             redrawMenu()
  654.  
  655.         end
  656.        
  657.     elseif sEvent == "char" then
  658.         if not bMenu and not bReadOnly then
  659.             -- Input text
  660.             local sLine = tLines[y]
  661.             tLines[y] = string.sub(sLine,1,x-1) .. param .. string.sub(sLine,x)
  662.             setCursor( x + 1, y )
  663.  
  664.         elseif bMenu then
  665.             -- Select menu items
  666.             for n,sMenuItem in ipairs( tMenuItems ) do
  667.                 if string.lower(string.sub(sMenuItem,1,1)) == string.lower(param) then
  668.                     doMenuItem( n )
  669.                     break
  670.                 end
  671.             end
  672.         end
  673.  
  674.     elseif sEvent == "paste" then
  675.         if not bMenu and not bReadOnly then
  676.             -- Input text
  677.             local sLine = tLines[y]
  678.             tLines[y] = string.sub(sLine,1,x-1) .. param .. string.sub(sLine,x)
  679.             setCursor( x + string.len( param ), y )
  680.         end
  681.        
  682.     elseif sEvent == "mouse_click" then
  683.         if not bMenu then
  684.             if param == 1 then
  685.                 -- Left click
  686.                 local cx,cy = param2, param3
  687.                 if cy < h then
  688.                     local newY = math.min( math.max( scrollY + cy, 1 ), #tLines )
  689.                     local newX = math.min( math.max( scrollX + cx, 1 ), string.len( tLines[newY] ) + 1 )
  690.                     setCursor( newX, newY )
  691.                 end
  692.             end
  693.         end
  694.        
  695.     elseif sEvent == "mouse_scroll" then
  696.         if not bMenu then
  697.             if param == -1 then
  698.                 -- Scroll up
  699.                 if scrollY > 0 then
  700.                     -- Move cursor up
  701.                     scrollY = scrollY - 1
  702.                     redrawText()
  703.                 end
  704.            
  705.             elseif param == 1 then
  706.                 -- Scroll down
  707.                 local nMaxScroll = #tLines - (h-1)
  708.                 if scrollY < nMaxScroll then
  709.                     -- Move cursor down
  710.                     scrollY = scrollY + 1
  711.                     redrawText()
  712.                 end
  713.                
  714.             end
  715.         end
  716.  
  717.     elseif sEvent == "term_resize" then
  718.         w,h = term.getSize()
  719.         setCursor( x, y )
  720.         redrawMenu()
  721.         redrawText()
  722.  
  723.     end
  724. end
  725.  
  726. -- Cleanup
  727. term.clear()
  728. term.setCursorBlink( false )
  729. term.setCursorPos( 1, 1 )
Advertisement
Add Comment
Please, Sign In to add comment