Advertisement
GopherAtl

gedit v0.4a (computercraft lua)

Sep 25th, 2012
234
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 33.75 KB | None | 0 0
  1. --[[ gedit
  2. advanced editor with undo, page views with word wrap, keyboard shortcuts, and
  3. advanced printing functions (coming soon)
  4.  
  5. based on edit by dan200+cloudy, enhancements by GopherAtl
  6.  
  7. Do whatever you want, unless you want to take credit, in which case screw you, jerk.
  8.  
  9. --]]
  10. -- Get file to edit
  11. local tArgs = { ... }
  12. if #tArgs == 0 then
  13.     print( "Usage: edit <path>" )
  14.     return
  15. end
  16.  
  17. -- Error checking
  18. local sPath = shell.resolve( tArgs[1] )
  19. local bReadOnly = fs.isReadOnly( sPath )
  20. if fs.exists( sPath ) and fs.isDir( sPath ) then
  21.     print( "Cannot edit a directory." )
  22.     return
  23. end
  24.  
  25. local x,y = 1,1
  26. local w,h = term.getSize()
  27. local scrollX, scrollY = 0,0
  28.  
  29. local tLinesRaw = {} --raw lines - end in implied \ns
  30. local tLinesDrawn = {} --modified version for current draw view, with addl. metadata
  31.  
  32. local bRunning = true
  33. local bChanged = false
  34. local tViewModes = {"code","document","page"}
  35. local nViewMode = 0
  36.  
  37. --undo related
  38. local tUndo={}
  39. local tRedo={}
  40. local hChangeTimer=0
  41. local sLastChangeType=""
  42.  
  43. --conveinence guys
  44. local function fTrue() return true end
  45. local function fFalse() return false end
  46. local function fNil() end
  47. local function hasChanged() return bChanged end
  48.  
  49. -- Menus and other fierce creatures
  50. local nMenuItem = 1
  51. local onConfirm=fNil
  52. local sMenuTitle=""
  53.  
  54. local tMenuStack = {}
  55.  
  56. --confirmation menu
  57. local tConfirm = {
  58.   {hotkey="n", sLabel="No", fConfirm=fFalse, onSelect=fNil},
  59.   {hotkey="y", sLabel="Yes", fConfirm=fFalse, onSelect=fNil},
  60. }
  61.  
  62. --forms
  63. local tActiveForm=nil
  64. local nSelectedField=1
  65. local nFieldCursorX=1
  66. local nFieldCursorY=1
  67.  
  68.  
  69. --printer settings
  70. local tFormPrintSettings = {
  71.     { sLabel="Tab Size", type="number", value=2,
  72.       x=2, y=3, nLabelWidth=10, nFieldWidth=3 },
  73.     { sLabel="Indent", type="number", value=0,
  74.       x=2, y=4, nLabelWidth=10, nFieldWidth=3 },
  75.     { sLabel="Copies", type="number", value=1,
  76.       x=2, y=5, nLabelWidth=10, nFieldWidth=3 },
  77.     { sLabel="Color", type="boolean", value=false,
  78.       x=2, y=6, nLabelWidth=10, nFieldWidth=3 },
  79.     width=31,height=17,
  80.     x=(w-31)/2, y=(h-17)/2,
  81.    
  82.   }
  83.  
  84. local tPrintSettings={tabSize=2,indent=0,numCopies=1,color=true}
  85.  
  86. local sStatus = "Press Ctrl to access menu"
  87.  
  88.  
  89. local function onChange(sType,sChar,x,y)
  90.   bChanged=true
  91.   if sType==sLastChangeType then
  92.     --append to current change
  93.     if sType=="backspace" then --backspace deletes from right to left
  94.       tUndo[#tUndo].sText=sChar..tUndo[#tUndo].sText
  95.     else
  96.       tUndo[#tUndo].sText=tUndo[#tUndo].sText..sChar
  97.     end
  98.     tUndo[#tUndo].nCount=tUndo[#tUndo].nCount+1
  99.   else
  100.     --start new change
  101.     tUndo[#tUndo+1]={sType=sType,sText=sChar,nCount=1,x=x,y=y}
  102.   end
  103.   --any changes obliterate any redo hist..., er, future.
  104.   tRedo={}
  105.   sLastChangeType=sType
  106.   hChangeTimer=os.startTimer(1)
  107. end
  108.  
  109. local function closeChange()
  110.   hChangeTimer=0
  111.   sLastChangeType=""
  112. end
  113.  
  114.  
  115.  
  116. local function load(_sPath)
  117.     tLinesRaw = {}
  118.     if fs.exists( _sPath ) then
  119.         local file = io.open( _sPath, "r" )
  120.         local sLine = file:read()
  121.         while sLine do
  122.       table.insert( tLinesRaw, sLine )
  123.             sLine = file:read()
  124.         end
  125.         file:close()
  126.     end
  127.    
  128.   if #tLinesRaw == 0 then
  129.     table.insert( tLinesRaw, "" )
  130.     end
  131. end
  132.  
  133. local function save( _sPath )
  134.     -- Create intervening folder
  135.     local sDir = sPath:sub(1, sPath:len() - fs.getName(sPath):len() )
  136.     if not fs.exists( sDir ) then
  137.         fs.makeDir( sDir )
  138.     end
  139.  
  140.     -- Save
  141.     local file = nil
  142.     local function innerSave()
  143.         file = fs.open( _sPath, "w" )
  144.         if file then
  145.             for n, sLine in ipairs( tLinesRaw ) do
  146.                 file.write( sLine .. "\n" )
  147.             end
  148.         else
  149.             error( "Failed to open ".._sPath )
  150.         end
  151.     end
  152.    
  153.     local ok = pcall( innerSave )
  154.     if file then
  155.         file.close()
  156.     end
  157.     return ok
  158. end
  159.  
  160.  
  161. --======= Drawing functions =======--
  162.  
  163. function getWrapCol(str,w)
  164.   --start at w and work back
  165.   for i=w,1,-1 do
  166.     if string.sub(str,i,i)==' ' or string.sub(str,i,i)=='\t' then
  167.       return i
  168.     end
  169.   end
  170.   --whole thing had no breaks, so interrupt word :(
  171.   return w
  172. end
  173.  
  174.  
  175. function getStrippedLine(rawLine,viewMode)
  176.   local outStr=""
  177.   local i=1
  178.   local wrapW=viewMode==3 and 25 or w
  179.   while i<=#rawLine do
  180.     local ch=string.sub(rawLine,i,i)
  181.     if viewMode==3 and ch=="%" then
  182.       i=i+1
  183.       ch=string.sub(rawLine,i,i)
  184.       if ch=="%" or ch=="" then
  185.         outStr=outStr..ch      
  186.       elseif ch=="<" then
  187.         outStr=""
  188.       end
  189.     elseif ch=="\t" then
  190.       local spaces=tFormPrintSettings[1].value-#outStr%tFormPrintSettings[1].value
  191.       outStr=outStr..string.rep(" ",spaces)
  192.     elseif ch=="\n" then
  193.       if i>1 then
  194.         error("somehow a \n got left in a line, should've been split before it got to getStrippedLine!")
  195.       end
  196.       outStr=string.rep(" ",tFormPrintSettings[2].value)
  197.     else
  198.       outStr=outStr..ch
  199.     end
  200.    
  201.     i=i+1        
  202.   end
  203.   local outLines={}
  204.   local offset=0
  205.   local offScr=0
  206.   local i=1
  207.   if viewMode>1 and #outStr>wrapW then
  208.     local nSub=0 --subline number
  209.     while #outStr>wrapW do
  210.       wrapPos=getWrapCol(outStr,wrapW)          
  211.       outLines[i]={s=string.sub(outStr,1,wrapPos).."",offsetRaw=offset, offsetScr=offScr}
  212.       offset=offset+wrapPos
  213.       offScr=offScr+#outLines[i].s
  214.       i=i+1
  215.       outStr=string.sub(outStr,wrapPos+1)..""
  216.     end
  217.   end
  218.   outLines[i]={s=outStr,offsetRaw=offset, offsetScr=offScr}
  219.   return outLines
  220. end
  221.  
  222.  
  223. function scrPosToRaw(x,y,view)
  224.   if view==nil then view=nVieWMode end
  225.   local rawY=tLinesDrawn[y].raw
  226.   local sLine = tLinesRaw[rawY]
  227.   local rawX=scrColToIndex(sLine,tLinesDrawn[y].offsetScr+x,view)
  228.   return rawX,rawY
  229. end
  230.  
  231. function scrColToIndex(rawLine,col,viewMode)
  232.   if viewMode==nil then viewMode=nViewMode end
  233.   local index, curCol=1,1
  234.   while curCol<col and index<=#rawLine do
  235.     local ch=string.sub(rawLine,index,index)
  236.     if ch=="\n" then
  237.       curCol=curCol+tPrintSettings.indent
  238.     elseif ch=="\t" then
  239.       curCol=curCol-(curCol-1)%tPrintSettings.tabSize + tPrintSettings.tabSize
  240.       index=index+1
  241.     elseif viewMode==3 and ch=="%" then
  242.       if #rawLine>index and string.sub(rawLine,index+1,index+1)=="%" then
  243.         curCol=curCol+1
  244.       end
  245.       index=index+2
  246.     else
  247.       index=index+1
  248.       curCol=curCol+1
  249.     end
  250.   end
  251.  
  252.   if viewMode==3 then
  253.     while index<=#rawLine and string.sub(rawLine,index,index)=="%" do
  254.       if index<#rawLine and string.sub(rawLine,index+1,index+1)=="%" then
  255.         break      
  256.       end
  257.       index=index+2
  258.     end
  259.   end
  260.   return index
  261. end
  262.  
  263. function rawToScrPos(rawX,rawY,viewMode)
  264.   if viewMode==nil then viewMode=nViewMode end
  265.   local sLine=tLinesRaw[rawY]
  266.   local x,y=0,1
  267.   --get the initial y for the line rawY
  268.   while rawY>tLinesDrawn[y].raw do
  269.     y=y+1
  270.   end
  271.   --zero in on actual line
  272.   local offset=0
  273.   while y<#tLinesDrawn and tLinesDrawn[y+1].raw==rawY and tLinesDrawn[y+1].offsetRaw<rawX do
  274.     offset=tLinesDrawn[y+1].offsetRaw
  275.     y=y+1
  276.   end
  277.   --loop through raw to find this actual offset
  278.   while offset<rawX do
  279.     local ch=string.sub(sLine,offset,offset)
  280.     if ch=="%" and viewMode==3 then
  281.       if #sLine>offset and string.sub(sLine,offset+1,offset+1)=="%" then
  282.         x=x+1
  283.       end
  284.       offset=offset+2
  285.     elseif ch=="\n" then
  286.       x=x+tPrintSettings.indent
  287.       offset=offset+1
  288.     elseif ch=="\t" then
  289.       x=x-(x-1)%tPrintSettings.tabSize+tPrintSettings.tabSize
  290.       offset=offset+1
  291.     else
  292.       x=x+1
  293.       offset=offset+1
  294.     end
  295.   end
  296.   return x,y
  297. end
  298.  
  299. local function redrawText()
  300.     for y=1,h-1 do
  301.         term.setCursorPos( 1 - scrollX, y )
  302.         term.clearLine()
  303.  
  304.         local sLine = tLinesDrawn[ y + scrollY ]
  305.         if sLine ~= nil then
  306.             term.write( sLine.s )
  307.         end
  308.     end
  309.     term.setCursorPos( x - scrollX, y - scrollY )  
  310. end
  311.  
  312. local function rebuildText()
  313.   tLinesDrawn={}
  314.  
  315.   local iDrawn=1
  316.   for i,sLine in ipairs(tLinesRaw) do
  317.     --if it fits, just copy it
  318.     local stripped=getStrippedLine(sLine,nViewMode)
  319.  
  320.     for j=1,#stripped do
  321.       tLinesDrawn[iDrawn]=stripped[j]
  322.       tLinesDrawn[iDrawn].raw=i
  323.       iDrawn=iDrawn+1
  324.     end        
  325.   end
  326.   redrawText()
  327. end
  328.  
  329.  
  330. local function updateFormCursorPos()
  331.   local tField=tActiveForm[nSelectedField]
  332.   if tField.type=="number" or tField.type=="boolean" then
  333.     nFieldCursorX=tField.nFieldWidth
  334.     nFieldCursorY=1  
  335.   else
  336.     nFieldCursorX=#tField.value
  337.   end
  338. end
  339.  
  340. local function redrawForm(all)
  341.    --draw the frame
  342.    local x,y=tActiveForm.x,tActiveForm.y
  343.    local width,height=tActiveForm.width,tActiveForm.height
  344.    if all then
  345.      --error("redrawing all,"..x..","..y..")
  346.      term.setCursorPos(x,y)
  347.      term.write("+"..string.rep("-",width-2).."+")
  348.      for i=1,height-2 do
  349.        term.setCursorPos(x,y+i)
  350.        term.write("|"..string.rep(" ",width-2).."|")
  351.      end
  352.      term.setCursorPos(x,y+2)
  353.      term.write("+"..string.rep("-",width-2).."+")
  354.      term.setCursorPos(x,y+height-1)
  355.      term.write("+"..string.rep("-",width-2).."+")
  356.      term.setCursorPos(x+6,y+1)
  357.      term.write("Print preferences")
  358.    end  
  359.  
  360.    --fields and values
  361.    for i=1,#tActiveForm do
  362.      local field=tActiveForm[i]
  363.      term.setCursorPos(x+field.x,y+field.y)
  364.      term.write(field.sLabel)
  365.      term.setCursorPos(x+field.x+field.nLabelWidth,y+field.y)
  366.      --generate value string
  367.      local fieldStr=""
  368.      if field.type=="number" then
  369.        fieldStr=tostring(field.value)
  370.        fieldStr=string.rep(" ",field.nFieldWidth-#fieldStr)..fieldStr
  371.      elseif field.type=="boolean" then
  372.        fieldStr=field.value and "Yes" or "No"
  373.        fieldStr=fieldStr..string.rep(" ",field.nFieldWidth-#fieldStr)
  374.      else
  375.        fieldStr=field.value..string.rep(" ",field.nFieldWidth-#field.value)
  376.      end
  377.      
  378.      if nSelectedField==i then
  379.        fieldStr="["..fieldStr.."]"
  380.      else
  381.        fieldStr=" "..fieldStr.." "
  382.      end
  383.      term.write(fieldStr)
  384.    end
  385.    
  386.    --set the cursor position into the active field
  387.    local tField=tActiveForm[nSelectedField]
  388.    term.setCursorPos(
  389.         x+tField.x+tField.nLabelWidth+1+nFieldCursorX,
  390.         y+tField.y+nFieldCursorY-1)
  391.  
  392. end
  393.  
  394. local function redrawLine(_nY)
  395.     local sLine = tLinesDrawn[_nY].s
  396.     term.setCursorPos( 1 - scrollX, _nY - scrollY )
  397.     term.clearLine()
  398.     term.write( sLine )
  399.     term.setCursorPos( x - scrollX, _nY - scrollY )
  400. end
  401.  
  402. local function redrawRawLine(rawY)
  403.   local sLine=tLinesRaw[rawY]
  404.   local startLine
  405.   for i=1,#tLinesDrawn do
  406.     if tLinesDrawn[i].raw==rawY then
  407.       startLine=i
  408.       break
  409.     end
  410.   end
  411.   local endLine=startLine
  412.   while endLine<#tLinesDrawn and tLinesDrawn[endLine+1].raw==rawY do
  413.     endLine=endLine+1
  414.   end
  415.  
  416.   local newLines=getStrippedLine(sLine,nViewMode)    
  417.   for i=1,#newLines do
  418.     if tLinesDrawn[startLine+i-1]==nil or tLinesDrawn[startLine+i-1].raw~=rawY then
  419.       table.insert(tLinesDrawn,startLine+i-1,newLines[i])
  420.     else
  421.       tLinesDrawn[startLine+i-1]=newLines[i]
  422.     end
  423.     tLinesDrawn[startLine+i-1].raw=rawY
  424.   end
  425.   if endLine-startLine+1 == #newLines then
  426.     for j=startLine,endLine do
  427.       redrawLine(j)
  428.     end
  429.   else
  430.     redrawText()
  431.   end
  432. end
  433.  
  434. local function redrawMenu()
  435.   local prevX,prevY=term.getCursorPos()
  436.   term.setCursorPos( 1, h )
  437.     term.clearLine()
  438.     local sLeft, sRight
  439.     if #tMenuStack>0 then    
  440.     local tActiveMenu=tMenuStack[#tMenuStack]
  441.     local sMenu = sMenuTitle
  442.         for n,tItem in ipairs( tActiveMenu ) do
  443.             if n == nMenuItem then
  444.         sMenu = sMenu.."["..tItem.sLabel.."]"
  445.             else
  446.         sMenu = sMenu.." "..tItem.sLabel.." "
  447.             end
  448.         end
  449.         sLeft = sMenu
  450.   else
  451.         sLeft = sStatus
  452.     end
  453.     sRight = "Ln "..y
  454.    
  455.     -- Left goes last so that it can overwrite the line numbers.
  456.     if sRight then
  457.         term.setCursorPos( w-sRight:len()+1, h )
  458.         term.write(sRight)
  459.     end
  460.     if sLeft then
  461.         term.setCursorPos( 1, h )
  462.         term.write(sLeft)
  463.     end
  464.    
  465.     -- Cursor highlights selection
  466.     term.setCursorPos( prevX,prevY)
  467. end
  468.  
  469.  
  470. local function menuStub()
  471.   sStatus = "feature not implemented"
  472. end
  473.  
  474. local function confirmAndDo(item)
  475.   if not item.fConfirm() then
  476.     item.onSelect()
  477.     return true
  478.   else
  479.     tMenuStack[#tMenuStack+1]=tConfirm
  480.     nMenuItem=1
  481.     tConfirm[2].onSelect=item.onSelect
  482.     sMenuTitle=item.sConfirm
  483.     return false
  484.   end
  485. end
  486.  
  487. local function doMenuItem( _n )
  488.   local tMenu=tMenuStack[#tMenuStack]
  489.   local bClosed=true  
  490.   if type(tMenu[_n].onSelect)=="table" then
  491.     --open submenu
  492.     tMenuStack[#tMenuStack+1]=tMenu[_n].onSelect
  493.     bClosed=false
  494.     nMenuItem=1
  495.   else  
  496.     bClosed=confirmAndDo(tMenu[_n])    
  497.   end
  498.     if #tMenuStack>0 and bClosed then
  499.         tMenuStack = {}
  500.         term.setCursorBlink( true )
  501.     end
  502.     redrawMenu()
  503. end
  504.  
  505. local function setCursor( x, y )
  506.     local screenX = x - scrollX
  507.     local screenY = y - scrollY
  508.    
  509.     local bRedraw = false
  510.     if screenX < 1 then
  511.         scrollX = x - 1
  512.         screenX = 1
  513.         bRedraw = true
  514.     elseif screenX > w then
  515.         scrollX = x - w
  516.         screenX = w
  517.         bRedraw = true
  518.     end
  519.    
  520.     if screenY < 1 then
  521.         scrollY = y - 1
  522.         screenY = 1
  523.         bRedraw = true
  524.     elseif screenY > h-1 then
  525.         scrollY = y - (h-1)
  526.         screenY = h-1
  527.         bRedraw = true
  528.     end
  529.    
  530.     if bRedraw then
  531.         redrawText()
  532.     end
  533.     term.setCursorPos( screenX, screenY )
  534.    
  535.     -- Statusbar now pertains to menu, it would probably be safe to redraw the menu on every key event.
  536.     redrawMenu()
  537. end
  538.  
  539. local function switchViewMode(nNewMode)
  540.   --convert current cursor pos to raw
  541.   local rawY=tLinesDrawn[y].raw
  542.   local sLine = tLinesRaw[rawY]
  543.   local rawX=scrColToIndex(sLine,tLinesDrawn[y].offsetScr+x,nViewMode)
  544.  
  545.   if nNewMode==nViewMode then
  546.     return
  547.   end
  548.   nViewMode=nNewMode
  549.   rebuildText()  
  550.   --convert raw position back to screen
  551.   x,y=rawToScrPos(rawX,rawY,nViewMode)
  552.   setCursor(x,y)
  553. end
  554.  
  555.  
  556.  
  557. -- modification functions - called on keystroke, also by undo/redo
  558. local function backspace()
  559.   local rawY=tLinesDrawn[y].raw
  560.   local sLine = tLinesRaw[rawY]
  561.   local rawX=scrColToIndex(sLine,tLinesDrawn[y].offsetScr+x,nViewMode)
  562.  
  563.   local ch=""
  564.   if rawX > 1 then
  565.     -- Remove character    
  566.     local sLine = tLinesRaw[rawY]    
  567.     local prevRawX=scrColToIndex(sLine,tLinesDrawn[y].offsetScr+x-1,nViewMode)
  568.     ch=string.sub(sLine,prevRawX,rawX-1)
  569.     tLinesRaw[rawY] = string.sub(sLine,1,prevRawX-1) .. string.sub(sLine,rawX)
  570.     redrawRawLine(rawY)
  571.  
  572.     rawX = prevRawX
  573.     x,y=rawToScrPos(rawX,rawY,nViewMode)
  574.     setCursor( x, y )
  575.   elseif y > 1 then
  576.     -- Remove newline
  577.     local sPrevLen = string.len( tLinesRaw[rawY-1] )
  578.     tLinesRaw[rawY-1] = tLinesRaw[rawY-1] .. tLinesRaw[rawY]
  579.     table.remove( tLinesRaw, rawY )
  580.     rebuildText()
  581.     redrawText()
  582.     ch="\n"
  583.     rawX = sPrevLen + 1
  584.     rawY = rawY - 1
  585.     x,y=rawToScrPos(rawX,rawY,nViewMode)
  586.     setCursor( x, y )
  587.   end
  588.   return ch
  589. end
  590.  
  591. local function delete()
  592.   local rawY=tLinesDrawn[y].raw
  593.   local sLine = tLinesRaw[rawY]
  594.   local rawX=scrColToIndex(sLine,tLinesDrawn[y].offsetScr+x,nViewMode)
  595.   local ch=""
  596.   if  rawX < string.len( tLinesRaw[rawY] ) + 1 then
  597.     local sLine = tLinesRaw[rawY]
  598.     ch=string.sub(sLine,rawX,rawY)
  599.     tLinesRaw[rawY] = string.sub(sLine,1,x-1) .. string.sub(sLine,x+1)
  600.     redrawRawLine(rawY)
  601.   elseif y<#tLinesRaw then
  602.     tLinesRaw[rawY] = tLinesRaw[rawY] .. tLinesRaw[rawY+1]
  603.     ch="\n"
  604.     table.remove( tLinesRaw, rawY+1 )
  605.     rebuildText()
  606.   end
  607.   return ch
  608. end
  609.  
  610. local function insert(text, bRight)
  611.   local rawY=tLinesDrawn[y].raw
  612.   local sLine = tLinesRaw[rawY]
  613.   local rawX=scrColToIndex(sLine,tLinesDrawn[y].offsetScr+x,nViewMode)
  614.   tLinesRaw[rawY] = string.sub(sLine,1,rawX-1) .. text .. string.sub(sLine,rawX)
  615.   sLine=tLinesRaw[rawY]
  616.   local i=0
  617.  
  618.   --rebuild and draw raw line
  619.   redrawRawLine(rawY)
  620.   --update cursor    
  621.   if not bRight then  
  622.     rawX=rawX+ string.len( text )
  623.     x,y=rawToScrPos(rawX,rawY,nViewMode)    
  624.   end
  625.   setCursor( x, y )
  626. end
  627.  
  628.  
  629. local function newline()
  630.   -- Newline
  631.   local rawY=tLinesDrawn[y].raw
  632.   local sLine = tLinesRaw[rawY]
  633.   local rawX=scrColToIndex(sLine,tLinesDrawn[y].offsetScr+x,nViewMode)
  634.   local _,spaces=string.find(sLine,"^[ ]+")
  635.   if not spaces then
  636.     spaces=0
  637.   end
  638.   tLinesRaw[rawY] = string.sub(sLine,1,rawX-1)
  639.   table.insert( tLinesRaw, rawY+1, string.rep(' ',spaces)..string.sub(sLine,rawX) )
  640.   rebuildText()
  641.  
  642.   rawX = 1
  643.   rawY = rawY + 1
  644.   x,y=rawToScrPos(rawX,rawY,nViewMode)
  645.   setCursor( x, y )
  646. end
  647.  
  648. --File menu
  649. function fileSave()
  650.   if bReadOnly then
  651.     sStatus = "Access denied"
  652.   else
  653.     local ok, err = save( sPath )
  654.     if ok then
  655.       sStatus="Saved to "..sPath
  656.       bChanged=false
  657.     else
  658.       sStatus="Error saving to "..sPath
  659.     end
  660.   end
  661.   redrawMenu()
  662. end
  663.  
  664. fileSaveAs=menuStub
  665.  
  666. function fileRevert()
  667.   if bChanged then
  668.     load( sPath )
  669.     redrawText()
  670.     sStatus="Reverted to last saved version"    
  671.     bChanged=false
  672.   else
  673.     sStatus="No changes to revert"
  674.   end
  675. end
  676.  
  677. fileOpen=menuStub
  678.  
  679. --print menu
  680. function printPrint()
  681.   local sPrinterSide = nil
  682.   for n,sSide in ipairs(rs.getSides()) do
  683.     if peripheral.isPresent(sSide) and peripheral.getType(sSide) == "printer" then
  684.       sPrinterSide = sSide
  685.       break
  686.     end
  687.   end
  688.  
  689.   if not sPrinterSide then
  690.     sStatus = "No printer attached"
  691.     return
  692.   end
  693.  
  694.   local nPage = 0
  695.   local sName = fs.getName( sPath )
  696.   local printer = peripheral.wrap(sPrinterSide)
  697.   if printer.getInkLevel() < 1 then
  698.     sStatus = "Printer out of ink"
  699.     return
  700.   elseif printer.getPaperLevel() < 1 then
  701.     sStatus = "Printer out of paper"
  702.     return
  703.   end
  704.  
  705.   local terminal = {
  706.     getCursorPos = printer.getCursorPos,
  707.     setCursorPos = printer.setCursorPos,
  708.     getSize = printer.getPageSize,
  709.     write = printer.write,
  710.   }
  711.   terminal.scroll = function()
  712.     if nPage == 1 then
  713.       printer.setPageTitle( sName.." (page "..nPage..")" )      
  714.     end
  715.  
  716.     while not printer.newPage() do
  717.       if printer.getInkLevel() < 1 then
  718.         sStatus = "Printer out of ink, please refill"
  719.       elseif printer.getPaperLevel() < 1 then
  720.         sStatus = "Printer out of paper, please refill"
  721.       else
  722.         sStatus = "Printer output tray full, please empty"
  723.       end
  724.  
  725.       term.restore()
  726.       redrawMenu()
  727.       term.redirect( terminal )
  728.  
  729.       local timer = os.startTimer(0.5)
  730.       sleep(0.5)
  731.     end
  732.  
  733.     nPage = nPage + 1
  734.     if nPage == 1 then
  735.       printer.setPageTitle( sName )
  736.     else
  737.       printer.setPageTitle( sName.." (page "..nPage..")" )
  738.     end
  739.   end
  740.  
  741.   bMenu = false
  742.   term.redirect( terminal )
  743.   local ok, error = pcall( function()
  744.     term.scroll()
  745.     for n, sLine in ipairs( tLinesRaw ) do
  746.       print( sLine )
  747.     end
  748.   end )
  749.   term.restore()
  750.   if not ok then
  751.     print( error )
  752.   end
  753.  
  754.   while not printer.endPage() do
  755.     sStatus = "Printer output tray full, please empty"
  756.     redrawMenu()
  757.     sleep( 0.5 )
  758.   end
  759.   bMenu = true
  760.  
  761.   if nPage > 1 then
  762.     sStatus = "Printed "..nPage.." Pages"
  763.   else
  764.     sStatus = "Printed 1 Page"
  765.   end
  766.   redrawMenu()
  767. end
  768.  
  769. printPreview=menuStub
  770.  
  771. local function printSettings()
  772.   tActiveForm=tFormPrintSettings
  773.   nSelectedField=1
  774.   updateFormCursorPos()
  775.   redrawForm(true)
  776. end
  777.  
  778.  
  779. --other main
  780. function _exit()
  781.   bRunning = false
  782. end
  783.  
  784. function _undo()
  785.   if #tUndo==0 then
  786.     sStatus="Nothing to undo!"
  787.   else
  788.     bChanged=false
  789.     --grab it and move to the redo stack
  790.     local tUndoing=tUndo[#tUndo]
  791.     tUndo[#tUndo]=nil
  792.     tRedo[#tRedo+1]=tUndoing
  793.     --set cursor where it was
  794.     x,y=rawToScrPos(tUndoing.x,tUndoing.y)
  795.     --now undo it
  796.     if tUndoing.sType=="char" then
  797.       --move cursor to the end of the inserted text
  798.       x=x+#tUndoing.sText
  799.       --undo with a backspace!
  800.       for i=1,#tUndoing.sText do
  801.         backspace()
  802.       end      
  803.     elseif tUndoing.sType=="backspace" then
  804.       x=x-#tUndoing.sText
  805.       --insert the text that was removed
  806.       insert(tUndoing.sText)      
  807.     elseif tUndoing.sType=="delete" then
  808.       --insert the snipped text to the right
  809.       insert(tUndoing.sText,true)
  810.     elseif tUndoing.sType=="backspaceNewline" then
  811.       newline()
  812.     elseif tUndoing.sType=="deleteNewline" then
  813.       newline()      
  814.     elseif tUndoing.sType=="newline" then
  815.       x=1
  816.       y=y+1
  817.       backspace()
  818.     end
  819.    
  820.     sStatus=""
  821.   end
  822. end
  823.  
  824. function _redo()
  825.   if #tRedo==0 then
  826.     sStatus="Nothing to redo!"
  827.   else
  828.     bChanged=false
  829.     --grab it and move to the redo stack
  830.     local tRedoing=tRedo[#tRedo]
  831.     tRedo[#tRedo]=nil
  832.     tUndo[#tUndo+1]=tRedoing
  833.     --set cursor where it was
  834.     x,y=rawToScrPos(tRedoing.x,tRedoing.y)
  835.     --now undo it
  836.     if tRedoing.sType=="char" then
  837.       insert(tRedoing.sText)      
  838.     elseif tRedoing.sType=="backspace" then
  839.       --undo with a backspace!
  840.       for i=1,tRedoing.nCount do
  841.         backspace()
  842.       end
  843.     elseif tRedoing.sType=="delete" then
  844.       --undo with a backspace!
  845.       for i=1,tRedoing.nCount do
  846.         delete()
  847.       end
  848.     elseif tRedoing.sType=="backspaceNewline" then
  849.       y=y+1
  850.       x=1
  851.       backspace()
  852.     elseif tRedoing.sType=="deleteNewline" then
  853.       delete()
  854.     elseif tRedoing.sType=="newline" then
  855.       newline()
  856.     end
  857.    
  858.     sStatus=""
  859.   end
  860. end
  861.  
  862. local sConfirmDiscard="Discard unsaved changes? "
  863.  
  864. local tMenuItems = {
  865.   {hotkey="f", sLabel="File", fConfirm=fFalse,
  866.       onSelect={
  867.         {hotkey="s",sLabel="(S)ave", fConfirm=fFalse,onSelect=fileSave},
  868.         {hotkey="a",sLabel="Save (A)s", fConfirm=fFalse,onSelect=fileSaveAs},
  869.         {hotkey="r",sLabel="(R)evert",fConfirm=hasChanged,sConfirm=sConfirmDiscard,onSelect=fileRevert},
  870.         {hotkey="o",sLabel="(O)pen", fConfirm=hasChanged,sConfirm=sConfirmDiscard,onSelect=fileOpen},
  871.         {hotkey="x", sLabel="E(x)it",fConfirm=hasChanged,sConfirm=sConfirmDiscard,onSelect=_exit},
  872.       },
  873.     },
  874.   {hotkey="e", sLabel="Edit", fConfirm=false,
  875.       onSelect={
  876.         {hotkey="z", sLabel="Undo(Z)",fConfirm=fFalse,onSelect=_undo},
  877.         {hotkey="y", sLabel="Redo(Y)",fConfirm=fFalse,onSelect=_redo},
  878.       },
  879.     },
  880.   {hotkey="v", sLabel="View", fConfirm=false,
  881.       onSelect={
  882.         {hotkey="c", sLabel="Code view",fConfirm=fFalse,onSelect=function() switchViewMode(1) end},
  883.         {hotkey="d", sLabel="Document view",fConfirm=fFalse,onSelect=function() switchViewMode(2) end},
  884.         {hotkey="p", sLabel="Page view",fConfirm=fFalse,onSelect=function() switchViewMode(3) end},
  885.       },
  886.     },
  887.   {hotkey="p", sLabel="Print", fConfirm=fFalse,
  888.       onSelect={
  889.         {hotkey="p",sLabel="(P)rint",fConfirm=fFalse,onSelect=printPrint},
  890.         {hotkey="r",sLabel="P(r)eview...",fConfirm=fFalse,onSelect=printPreview},
  891.         {hotkey="s",sLabel="(S)ettings...",fConfirm=fFalse,onSelect=printSettings},
  892.       }
  893.     },
  894. }
  895.  
  896. --table of ctrl-key events handled in the doc view - also handled in menu while not in a form?
  897. local tDocCtrlKeys={
  898.     one={fConfirm=fFalse,onSelect=function() switchViewMode(1) end},
  899.     two={fConfirm=fFalse,onSelect=function() switchViewMode(2) end},
  900.     three={fConfirm=fFalse,onSelect=function() switchViewMode(3) end},
  901.  
  902.     r={fConfirm=hasChanged,sConfirm=sConfirmDiscard,onSelect=fileRevert},
  903.     x={fConfirm=hasChanged,sConfirm=sConfirmDiscard,onSelect=_exit},
  904.     s={fConfirm=fFalse,onSelect=fileSave},
  905.     a={fConfirm=fFalse,onSelect=fileSaveAs},
  906.     p={fConfirm=fFales,onSelect=printPrint},
  907.    
  908.     z={fConfirm=fFalse,onSelect=_undo},
  909.     y={fConfirm=fFalse,onSelect=_redo},
  910.    
  911. }
  912.  
  913. function docInputHandler(sEvent,param)
  914.   if sEvent == "key" then
  915.     if param == 200 then --up
  916.       closeChange()
  917.       -- Up
  918.       if y > 1 then
  919.         -- Move cursor up
  920.         y = y - 1
  921.         x = math.min( x, string.len( tLinesDrawn[y].s ) + 1 )
  922.         setCursor( x, y )
  923.       end
  924.     elseif param == 208 then --down
  925.       closeChange()
  926.       -- Down
  927.       -- Move cursor down
  928.       if y < #tLinesDrawn then
  929.         y = y + 1
  930.         x = math.min( x, string.len( tLinesDrawn[y].s ) + 1 )
  931.         setCursor( x, y )
  932.       end
  933.     elseif param == 15 then --tab
  934.       -- Tab
  935.       --[[local sLine = tLinesRaw[y]
  936.  
  937.       -- Indent line
  938.       -- IN CASE OF INSERT TAB IN PLACE:
  939.       -- tLinesRaw[y] = string.sub(sLine,1,x-1) .. "  " .. string.sub(sLine,x)
  940.       local nSpaces=2-x%2
  941.       local sSpaces=string.rep(" ",nSpaces)
  942.       local px,py=x,y
  943.       tLinesRaw[y]=sSpaces..tLinesRaw[y]
  944.       x = x+nSpaces
  945.       setCursor( x, y )
  946.       redrawLine(y)
  947.       --]]
  948.       local px,py=scrPosToRaw(x,y)
  949.       insert("\t")
  950.       onChange("char","\t",px,py)
  951.     elseif param == 201 then --pageUp
  952.       closeChange()
  953.       -- Page Up
  954.       -- Move up a page
  955.       local sx,sy=term.getSize()
  956.       y=y-sy-1
  957.       if y<1 then y=1 end
  958.       x = math.min( x, string.len( tLinesDrawn[y].s ) + 1 )
  959.       setCursor( x, y )
  960.     elseif param == 209 then --pageDown
  961.       closeChange()
  962.       -- Page Down
  963.       -- Move down a page
  964.       local sx,sy=term.getSize()
  965.       if y<#tLinesDrawn-sy-1 then
  966.         y = y+sy-1
  967.       else
  968.         y = #tLinesDrawn
  969.       end
  970.       x = math.min( x, string.len( tLinesDrawn[y].s ) + 1 )
  971.       setCursor( x, y )
  972.     elseif param == 199 then --home
  973.       closeChange()
  974.       -- Home
  975.       -- [TODO] respect indentation here
  976.       -- Move cursor to the beginning
  977.       x=1
  978.       setCursor(x,y)
  979.     elseif param == 207 then --end
  980.       closeChange()
  981.       -- End
  982.       -- Move cursor to the end
  983.       x = string.len( tLinesDrawn[y].s ) + 1
  984.       setCursor(x,y)
  985.     elseif param == 203 then --left
  986.       closeChange()
  987.       -- Left
  988.       if x > 1 then
  989.         -- Move cursor left
  990.         x = x - 1
  991.       elseif x==1 and y>1 then
  992.         x = string.len( tLinesDrawn[y-1].s ) + 1
  993.         y = y - 1
  994.       end
  995.       setCursor( x, y )
  996.     elseif param == 205 then --right
  997.       closeChange()
  998.       -- Right
  999.       if x < string.len( tLinesDrawn[y].s ) + 1 then
  1000.         -- Move cursor right
  1001.         x = x + 1
  1002.       elseif x==string.len( tLinesDrawn[y].s ) + 1 and y<#tLinesDrawn then
  1003.         x = 1
  1004.         y = y + 1
  1005.       end
  1006.       setCursor( x, y )
  1007.     elseif param == 211 then --delete
  1008.       -- Delete
  1009.       local px,py=scrPosToRaw(x,y)
  1010.       local ch=delete()
  1011.       if ch=="\n" then
  1012.         closeChange()
  1013.         onChange("deleteNewline",ch,px,py)
  1014.         closeChange()
  1015.       else
  1016.         onChange("delete",ch,px,py)
  1017.       end
  1018.     elseif param == 14 then --backspace
  1019.       -- Backspace
  1020.       local px,py=scrPosToRaw(x,y)
  1021.       local ch=backspace()
  1022.       if ch=="\n" then
  1023.         closeChange()
  1024.         --NOTE this special case uses x,y, not px,py!
  1025.         onChange("backspaceNewline","",x,y)
  1026.         closeChange()
  1027.       else
  1028.         onChange("backspace",ch,px,py)
  1029.       end
  1030.     elseif param == 28 then --enter
  1031.       -- Enter
  1032.       local px,py=scrPosToRaw(x,y)
  1033.       newline()
  1034.       onChange("newline","",px,py)
  1035.       closeChange() --never group enters together
  1036.     elseif param == 29 or param == 157 then  --left or right ctrl
  1037.       closeChange()
  1038.       -- open top level menus
  1039.       bMenu = true
  1040.       sMenuTitle=""
  1041.       tMenuStack[1]=tMenuItems
  1042.       term.setCursorBlink( false )
  1043.       nMenuItem = 1
  1044.       redrawMenu()
  1045.     end
  1046.   elseif sEvent == "char" then
  1047.     -- Input text
  1048.     local px,py=scrPosToRaw(x,y)
  1049.     if param=="%" and nViewMode==3 then
  1050.       insert("%%")
  1051.     else
  1052.       insert(param)
  1053.     end
  1054.     onChange("char",param,px,py)
  1055.   elseif sEvent == "timer" and param==hChangeTimer then
  1056.     closeChange()
  1057.   elseif sEvent == "ctrl-char" then
  1058.     if tDocCtrlKeys[param] then
  1059.       confirmAndDo(tDocCtrlKeys[param])
  1060.       redrawMenu()
  1061.     end
  1062.   end
  1063. end
  1064.  
  1065.  
  1066. function formInputHandler(sEvent,param)
  1067.   if sEvent=="key" then
  1068.     if param==keys.leftCtrl or param==keys.rightCtrl then
  1069.       tActiveForm=nil
  1070.       redrawText()
  1071.     elseif param==208 then --down
  1072.       nSelectedField=nSelectedField+1
  1073.       if nSelectedField>#tActiveForm then
  1074.         nSelectedField=1
  1075.       end
  1076.       updateFormCursorPos()
  1077.       redrawForm()
  1078.     elseif param==200 then --up
  1079.       nSelectedField=nSelectedField-1
  1080.       if nSelectedField==0 then
  1081.         nSelectedField=#tActiveForm        
  1082.         updateFormCursorPos()
  1083.       end
  1084.       redrawForm()
  1085.     elseif param==14 then --backspace
  1086.       local tField=tActiveForm[nSelectedField]
  1087.       if tField.type=="number" then
  1088.         if tField.value>0 then
  1089.           tField.value=math.floor(tField.value/10)
  1090.           redrawForm()
  1091.         end                    
  1092.       elseif tField.type=="string" then
  1093.         if #tField.value>0 then
  1094.           tField.value=string.sub(tField.value,1,#tField.value-1)
  1095.           nFieldCursorX=nFieldCursorX-1
  1096.           redrawForm()
  1097.         end
  1098.       end
  1099.     end      
  1100.   elseif sEvent=="char" then
  1101.     --edit the field's value...fun stuff here
  1102.     local tField=tActiveForm[nSelectedField]
  1103.     if tField.type=="number" then
  1104.       --numbers only
  1105.       if param>="0" and param<="9" then
  1106.         if #tostring(tField.value)<tField.nFieldWidth then
  1107.           tField.value=tField.value*10+tonumber(param)
  1108.           redrawForm()
  1109.         end
  1110.       end
  1111.    
  1112.     elseif tField.type=="boolean" then
  1113.       if param=="n" or param=="N" then
  1114.         tField.value=false
  1115.         redrawForm()
  1116.       elseif param=="y" or param=="Y" then
  1117.         tField.value=true
  1118.         redrawForm()
  1119.       elseif param==" " then
  1120.         tField.value=not tField.value
  1121.         redrawForm()
  1122.       end
  1123.     end
  1124.   elseif tField.type=="string" then
  1125.     if #tField.value<tField.nFieldWidth then
  1126.       tField.value=tField.value..param
  1127.       nFieldCursorX=nFieldCursorX+1
  1128.       redrawForm()
  1129.     end
  1130.   end
  1131. end
  1132.  
  1133. function menuInputHandler(sEvent,param)
  1134.   if sEvent == "key" then    
  1135.     if param == 203 then --left
  1136.       -- Move menu left
  1137.       nMenuItem = nMenuItem - 1
  1138.       if nMenuItem < 1 then
  1139.         nMenuItem = #tMenuStack[#tMenuStack]
  1140.       end
  1141.       redrawMenu()
  1142.     elseif param == 205 then --right
  1143.       -- Move menu right
  1144.       nMenuItem = nMenuItem + 1
  1145.       if nMenuItem > #tMenuStack[#tMenuStack] then
  1146.         nMenuItem = 1
  1147.       end
  1148.       redrawMenu()
  1149.     elseif param == 28 then --enter
  1150.       -- Menu selection
  1151.       doMenuItem( nMenuItem )
  1152.     elseif param == 29 or param == 157 then  --left or right control
  1153.       -- Menu toggle
  1154.       if #tMenuStack>1 then
  1155.         tMenuStack[#tMenuStack]=nil
  1156.         nMenuItem=1
  1157.       else
  1158.         tMenuStack={}
  1159.         term.setCursorBlink( true )
  1160.       end
  1161.       redrawMenu()
  1162.     end
  1163.   elseif sEvent == "char" then
  1164.     -- Select menu items
  1165.     local tMenu=tMenuStack[#tMenuStack]
  1166.     for n,sMenuItem in ipairs( tMenu ) do
  1167.       if sMenuItem.hotkey == string.lower(param) then
  1168.         doMenuItem( n )
  1169.         break
  1170.       end
  1171.     end
  1172.   elseif sEvent == "ctrl-char" then
  1173.     -- Menu toggle
  1174.     tMenuStack={}
  1175.     term.setCursorBlink( true )
  1176.     if tActiveForm==nil then
  1177.       if tDocCtrlKeys[param] then
  1178.         confirmAndDo(tDocCtrlKeys[param])
  1179.       end
  1180.     end
  1181.     redrawMenu()
  1182.   end
  1183. end
  1184.  
  1185.  
  1186. --[[
  1187. local line="[%%%1]"
  1188. print("line : \n"..line)
  1189. print("---")
  1190. local lines=getStrippedLine(line,1)
  1191. print("stripped line mode 1: \n123456789012345678901234567890123456789\n"..lines[1].s)
  1192.  
  1193.  
  1194. tLinesRaw={line}
  1195. local stripped=getStrippedLine(line,1)
  1196. tLinesDrawn={{s=stripped[1].s,raw=1,offsetRaw=0,offsetCol=0}}
  1197.  
  1198. local first=nil
  1199. for i=1,#stripped[1].s do
  1200.   local sCh=string.sub(line,i,i)
  1201.   local ri=scrColToIndex(line,i,1)
  1202.   local rCh=string.sub(line,ri,ri)
  1203.   --back to index
  1204.   local px,py=  rawToScrPos(ri,1,3)
  1205.  
  1206.   print("col "..i.."=> index "..ri.." => "..px)
  1207. end
  1208.  
  1209. if first then print("first error at "..first) end
  1210.  
  1211. --]]
  1212. ---[[
  1213.  
  1214. -- Actual program functionality begins
  1215. load(sPath)
  1216.  
  1217. nViewMode=1
  1218. rebuildText()
  1219.  
  1220. term.clear()
  1221. term.setCursorPos(x,y)
  1222. term.setCursorBlink( true )
  1223.  
  1224. redrawText()
  1225. redrawMenu()
  1226.  
  1227. --key->char lookup table for keys that map to chars
  1228. local keyToChar={[2]="1",[3]="2",[4]="3",[5]="4",[6]="5",[7]="6",[8]="7",[9]="8",[10]="9",[11]="0",[12]="minus",[13]="equals",[16]="q",[17]="w",[18]="e",[19]="r",[20]="t",[21]="y",[22]="u",[23]="i",[24]="o",[25]="p",[26]="leftBracket",[27]="rightBracket",[30]="a",[31]="s",[32]="d",[33]="f",[34]="g",[35]="h",[36]="j",[37]="k",[38]="l",[39]="semiColon",[40]="apostrophe",[41]="grave",[43]="backslash",[44]="z",[45]="x",[46]="c",[47]="v",[48]="b",[49]="n",[50]="m",[51]="comma",[52]="period",[53]="slash",}
  1229.  
  1230. -- Handle input
  1231. local eventBuffer={}
  1232. while bRunning do
  1233.   local function nextEvent(eventBuffer)
  1234.     if #eventBuffer>0 then
  1235.       local e,p=unpack(eventBuffer[1])
  1236.       table.remove(eventBuffer,1)
  1237.       return e,p
  1238.     else
  1239.       return os.pullEvent()
  1240.     end
  1241.   end
  1242.  
  1243.   local sEvent, param = nextEvent(eventBuffer)
  1244.   --identify ctrl-char events at top level
  1245.   if sEvent=="key" and keyToChar[param]~=nil then
  1246.     local tempBuff={}
  1247.     local timer=os.startTimer(0)
  1248.     while true do
  1249.       e2,p2=nextEvent(eventBuffer)
  1250.       --if it's a char event, and it matches the original key...
  1251.       if e2=="char" and keyToChar[param]==string.lower(p2) then
  1252.         tempBuff[#tempBuff+1]={e2,p2}
  1253.         break
  1254.       elseif e2=="timer" and p2==timer then
  1255.         --no matching char event, this is a ctrl-char key!
  1256.         local ch=keyToChar[param]
  1257.         if ch=="0" then
  1258.           ch="zero"
  1259.         elseif ch>="1" and ch<="9" then
  1260.           ch=({"one","two","three","four","five","six","seven","eight","nine"})[tonumber(ch)]
  1261.         end        
  1262.         os.queueEvent("ctrl-char",ch)
  1263.         break
  1264.       else
  1265.         --anything else we get buffer, so we can use them in order
  1266.         tempBuff[#tempBuff+1]={e2,p2}
  1267.       end
  1268.     end
  1269.     --prepend tempBuffer to eventBuffer, again preserving original order
  1270.     for i=1,#eventBuffer do
  1271.       tempBuff[#tempBuff+i]=eventBuffer[i]
  1272.     end
  1273.     eventBuffer=tempBuff
  1274.   end
  1275.  
  1276.   if #tMenuStack>0 then
  1277.     menuInputHandler(sEvent,param)
  1278.   elseif tActiveForm then
  1279.     formInputHandler(sEvent,param)
  1280.   else
  1281.     docInputHandler(sEvent,param)
  1282.   end
  1283. end
  1284.  
  1285. -- Cleanup
  1286. term.clear()
  1287. term.setCursorBlink( false )
  1288. term.setCursorPos( 1, 1 )
  1289. --]]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement