Advertisement
Sewbacca

Just Scrolling Shell

Dec 28th, 2017
204
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.16 KB | None | 0 0
  1. --[[
  2.  
  3.     MIT License
  4.  
  5.     Copyright (c) 2017 Sewbacca
  6.    
  7.     Permission is hereby granted, free of charge, to any person obtaining a copy
  8.     of this software and associated documentation files (the "Software"), to deal
  9.     in the Software without restriction, including without limitation the rights
  10.     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11.     copies of the Software, and to permit persons to whom the Software is
  12.     furnished to do so, subject to the following conditions:
  13.    
  14.     The above copyright notice and this permission notice shall be included in all
  15.     copies or substantial portions of the Software.
  16.    
  17.     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18.     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19.     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20.     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21.     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22.     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23.     SOFTWARE.
  24.  
  25. --]]
  26. -- Code taken from rom/programs/shell and modified by sewbacca
  27.  
  28. local multishell = multishell
  29. local parentShell = shell
  30. local parentTerm = term.current()
  31.  
  32. if multishell then
  33.     multishell.setTitle( multishell.getCurrent(), "shell" )
  34. end
  35.  
  36. local bExit = false
  37. local sDir = (parentShell and parentShell.dir()) or ""
  38. local sPath = (parentShell and parentShell.path()) or ".:/rom/programs"
  39. local tAliases = (parentShell and parentShell.aliases()) or {}
  40. local tCompletionInfo = (parentShell and parentShell.getCompletionInfo()) or {}
  41. local tProgramStack = {}
  42. local bPause = false
  43. local nBufferdLines = 250
  44.  
  45. local shell = {}
  46. local tEnv = {
  47.     [ "shell" ] = shell,
  48.     [ "multishell" ] = multishell,
  49. }
  50.  
  51. -- Colours
  52. local promptColour, textColour, bgColour
  53. if term.isColour() then
  54.     promptColour = colours.yellow
  55.     textColour = colours.white
  56.     bgColour = colours.black
  57. else
  58.     promptColour = colours.white
  59.     textColour = colours.white
  60.     bgColour = colours.black
  61. end
  62.  
  63. local function run( _sCommand, ... )
  64.     local sPath = shell.resolveProgram( _sCommand )
  65.     if sPath ~= nil then
  66.         tProgramStack[#tProgramStack + 1] = sPath
  67.         if multishell then
  68.             multishell.setTitle( multishell.getCurrent(), fs.getName( sPath ) )
  69.         end
  70.         local result = os.run( tEnv, sPath, ... )
  71.         tProgramStack[#tProgramStack] = nil
  72.         if multishell then
  73.             if #tProgramStack > 0 then
  74.                 multishell.setTitle( multishell.getCurrent(), fs.getName( tProgramStack[#tProgramStack] ) )
  75.             else
  76.                 multishell.setTitle( multishell.getCurrent(), "shell" )
  77.             end
  78.         end
  79.         return result
  80.     else
  81.         printError( "No such program" )
  82.         return false
  83.     end
  84. end
  85.  
  86. local function tokenise( ... )
  87.     local sLine = table.concat( { ... }, " " )
  88.     local tWords = {}
  89.     local bQuoted = false
  90.     for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do
  91.         if bQuoted then
  92.             table.insert( tWords, match )
  93.         else
  94.             for m in string.gmatch( match, "[^ \t]+" ) do
  95.                 table.insert( tWords, m )
  96.             end
  97.         end
  98.         bQuoted = not bQuoted
  99.     end
  100.     return tWords
  101. end
  102.  
  103. -- Install shell API
  104. function shell.run( ... )
  105.     local tWords = tokenise( ... )
  106.     local sCommand = tWords[1]
  107.     if sCommand then
  108.         return run( sCommand, table.unpack( tWords, 2 ) )
  109.     end
  110.     return false
  111. end
  112.  
  113. function shell.exit()
  114.     bExit = true
  115. end
  116.  
  117. function shell.dir()
  118.     return sDir
  119. end
  120.  
  121. function shell.setDir( _sDir )
  122.     sDir = _sDir
  123. end
  124.  
  125. function shell.path()
  126.     return sPath
  127. end
  128.  
  129. function shell.setPath( _sPath )
  130.     sPath = _sPath
  131. end
  132.  
  133. function shell.resolve( _sPath )
  134.     local sStartChar = string.sub( _sPath, 1, 1 )
  135.     if sStartChar == "/" or sStartChar == "\\" then
  136.         return fs.combine( "", _sPath )
  137.     else
  138.         return fs.combine( sDir, _sPath )
  139.     end
  140. end
  141.  
  142. local function pathWithExtension( _sPath, _sExt )
  143.     local nLen = #sPath
  144.     local sEndChar = string.sub( _sPath, nLen, nLen )
  145.     -- Remove any trailing slashes so we can add an extension to the path safely
  146.     if sEndChar == "/" or sEndChar == "\\" then
  147.         _sPath = string.sub( _sPath, 1, nLen - 1 )
  148.     end
  149.     return _sPath .. "." .. _sExt
  150. end
  151.  
  152.  
  153. function shell.resolveProgram( _sCommand )
  154.     if type( _sCommand ) ~= "string" then
  155.         error( "bad argument #1 (expected string, got " .. type( _sCommand ) .. ")", 2 )
  156.     end
  157.     -- Substitute aliases firsts
  158.     if tAliases[ _sCommand ] ~= nil then
  159.         _sCommand = tAliases[ _sCommand ]
  160.     end
  161.  
  162.  
  163.     -- If the path is a global path, use it directly
  164.     local sStartChar = string.sub( _sCommand, 1, 1 )
  165.     if sStartChar == "/" or sStartChar == "\\" then
  166.         local sPath = fs.combine( "", _sCommand )
  167.         if fs.exists( sPath ) and not fs.isDir( sPath ) then
  168.             return sPath
  169.         else
  170.             local sPathLua = pathWithExtension( sPath, "lua" )
  171.             if fs.exists( sPathLua ) and not fs.isDir( sPathLua ) then
  172.                 return sPathLua
  173.             end
  174.         end
  175.         return nil
  176.     end
  177.    
  178.      -- Otherwise, look on the path variable
  179.     for sPath in string.gmatch(sPath, "[^:]+") do
  180.         sPath = fs.combine( shell.resolve( sPath ), _sCommand )
  181.         if fs.exists( sPath ) and not fs.isDir( sPath ) then
  182.             return sPath
  183.         else
  184.             local sPathLua = pathWithExtension( sPath, "lua" )
  185.             if fs.exists( sPathLua ) and not fs.isDir( sPathLua ) then
  186.                 return sPathLua
  187.             end
  188.         end
  189.     end
  190.    
  191.     -- Not found
  192.     return nil
  193. end
  194.  
  195.  
  196. function shell.programs( _bIncludeHidden )
  197.     local tItems = {}
  198.    
  199.     -- Add programs from the path
  200.     for sPath in string.gmatch(sPath, "[^:]+") do
  201.         sPath = shell.resolve( sPath )
  202.         if fs.isDir( sPath ) then
  203.             local tList = fs.list( sPath )
  204.             for n=1,#tList do
  205.                 local sFile = tList[n]
  206.                 if not fs.isDir( fs.combine( sPath, sFile ) ) and
  207.                    (_bIncludeHidden or string.sub( sFile, 1, 1 ) ~= ".") then
  208.                     if #sFile > 4 and sFile:sub(-4) == ".lua" then
  209.                         sFile = sFile:sub(1,-5)
  210.                     end
  211.                     tItems[ sFile ] = true
  212.                 end
  213.             end
  214.         end
  215.     end    
  216.  
  217.  
  218.     -- Sort and return
  219.     local tItemList = {}
  220.     for sItem, b in pairs( tItems ) do
  221.         table.insert( tItemList, sItem )
  222.     end
  223.     table.sort( tItemList )
  224.     return tItemList
  225. end
  226.  
  227.  
  228. local function completeProgram( sLine )
  229.     if #sLine > 0 and string.sub( sLine, 1, 1 ) == "/" then
  230.         -- Add programs from the root
  231.         return fs.complete( sLine, "", true, false )
  232.  
  233.  
  234.     else
  235.         local tResults = {}
  236.         local tSeen = {}
  237.  
  238.  
  239.         -- Add aliases
  240.         for sAlias, sCommand in pairs( tAliases ) do
  241.             if #sAlias > #sLine and string.sub( sAlias, 1, #sLine ) == sLine then
  242.                 local sResult = string.sub( sAlias, #sLine + 1 )
  243.                 if not tSeen[ sResult ] then
  244.                     table.insert( tResults, sResult )
  245.                     tSeen[ sResult ] = true
  246.                 end
  247.             end
  248.         end
  249.  
  250.  
  251.         -- Add programs from the path
  252.         local tPrograms = shell.programs()
  253.         for n=1,#tPrograms do
  254.             local sProgram = tPrograms[n]
  255.             if #sProgram > #sLine and string.sub( sProgram, 1, #sLine ) == sLine then
  256.                 local sResult = string.sub( sProgram, #sLine + 1 )
  257.                 if not tSeen[ sResult ] then
  258.                     table.insert( tResults, sResult )
  259.                     tSeen[ sResult ] = true
  260.                 end
  261.             end
  262.         end
  263.  
  264.  
  265.         -- Sort and return
  266.         table.sort( tResults )
  267.         return tResults
  268.     end
  269. end
  270.  
  271. local function completeProgramArgument( sProgram, nArgument, sPart, tPreviousParts )
  272.     local tInfo = tCompletionInfo[ sProgram ]
  273.     if tInfo then
  274.         return tInfo.fnComplete( shell, nArgument, sPart, tPreviousParts )
  275.     end
  276.     return nil
  277. end
  278.  
  279. function shell.complete( sLine )
  280.     if #sLine > 0 then
  281.         local tWords = tokenise( sLine )
  282.         local nIndex = #tWords
  283.         if string.sub( sLine, #sLine, #sLine ) == " " then
  284.             nIndex = nIndex + 1
  285.         end
  286.         if nIndex == 1 then
  287.             local sBit = tWords[1] or ""
  288.             local sPath = shell.resolveProgram( sBit )
  289.             if tCompletionInfo[ sPath ] then
  290.                 return { " " }
  291.             else
  292.                 local tResults = completeProgram( sBit )
  293.                 for n=1,#tResults do
  294.                     local sResult = tResults[n]
  295.                     local sPath = shell.resolveProgram( sBit .. sResult )
  296.                     if tCompletionInfo[ sPath ] then
  297.                         tResults[n] = sResult .. " "
  298.                     end
  299.                 end
  300.                 return tResults
  301.             end
  302.  
  303.         elseif nIndex > 1 then
  304.             local sPath = shell.resolveProgram( tWords[1] )
  305.             local sPart = tWords[nIndex] or ""
  306.             local tPreviousParts = tWords
  307.             tPreviousParts[nIndex] = nil
  308.             return completeProgramArgument( sPath , nIndex - 1, sPart, tPreviousParts )
  309.  
  310.         end
  311.     end
  312.     return nil
  313. end
  314.  
  315. function shell.completeProgram( sProgram )
  316.     return completeProgram( sProgram )
  317. end
  318.  
  319. function shell.setCompletionFunction( sProgram, fnComplete )
  320.     tCompletionInfo[ sProgram ] = {
  321.         fnComplete = fnComplete
  322.     }
  323. end
  324.  
  325. function shell.getCompletionInfo()
  326.     return tCompletionInfo
  327. end
  328.  
  329. function shell.getRunningProgram()
  330.     if #tProgramStack > 0 then
  331.         return tProgramStack[#tProgramStack]
  332.     end
  333.     return nil
  334. end
  335.  
  336. function shell.setAlias( _sCommand, _sProgram )
  337.     tAliases[ _sCommand ] = _sProgram
  338. end
  339.  
  340. function shell.clearAlias( _sCommand )
  341.     tAliases[ _sCommand ] = nil
  342. end
  343.  
  344. function shell.aliases()
  345.     -- Copy aliases
  346.     local tCopy = {}
  347.     for sAlias, sCommand in pairs( tAliases ) do
  348.         tCopy[sAlias] = sCommand
  349.     end
  350.     return tCopy
  351. end
  352.  
  353. if multishell then
  354.     function shell.openTab( ... )
  355.         local tWords = tokenise( ... )
  356.         local sCommand = tWords[1]
  357.         if sCommand then
  358.             local sPath = shell.resolveProgram( sCommand )
  359.             if sPath == "rom/programs/shell" then
  360.                 return multishell.launch( tEnv, sPath, table.unpack( tWords, 2 ) )
  361.             elseif sPath ~= nil then
  362.                 return multishell.launch( tEnv, "rom/programs/shell", sCommand, table.unpack( tWords, 2 ) )
  363.             else
  364.                 printError( "No such program" )
  365.             end
  366.         end
  367.     end
  368.  
  369.     function shell.switchTab( nID )
  370.         multishell.setFocus( nID )
  371.     end
  372. end
  373.  
  374. function shell.allowScroll()
  375.     bPause = false
  376. end
  377.  
  378. function shell.disallowScroll()
  379.     bPause = true
  380. end
  381.  
  382. local tArgs = { ... }
  383. if #tArgs > 0 then
  384.     -- "shell x y z"
  385.     -- Run the program specified on the commandline
  386.     shell.run( ... )
  387.  
  388. else
  389.     -- "shell"
  390.  
  391.     -- Run the startup program
  392.     if parentShell == nil then
  393.         shell.run( "/rom/startup" )
  394.     end
  395.  
  396.     -- Setup scrolling
  397.  
  398.     local nWidth, nHeight = term.getSize()
  399.     local visWin = window.create(parentTerm, 1, -nBufferdLines + 1, nWidth, nHeight + nBufferdLines * 2)
  400.  
  401.     visWin.clear = function ()
  402.         visWin.scroll(nHeight)
  403.     end
  404.  
  405.     local oldSetCursorPos = visWin.setCursorPos
  406.     local oldGetCursorPos = visWin.getCursorPos
  407.  
  408.     function visWin.setCursorPos(nX, nY)
  409.         oldSetCursorPos(nX, nY + nBufferdLines)
  410.     end
  411.  
  412.     function visWin.getCursorPos(nX, nY)
  413.         local nX, nY = oldGetCursorPos()
  414.         return nX, nY - nBufferdLines
  415.     end
  416.  
  417.     function visWin.getSize()
  418.         return nWidth, nHeight
  419.     end
  420.  
  421.     term.redirect(visWin)
  422.  
  423.     visWin.setCursorPos(1,1)
  424.     -- Print the header
  425.     term.setBackgroundColor( bgColour )
  426.     term.setTextColour( promptColour )
  427.     print( os.version() )
  428.     term.setTextColour( textColour )
  429.  
  430.     parallel.waitForAny(
  431.     function()
  432.         -- Read commands and execute them
  433.        
  434.         local tCommandHistory = {}
  435.         while not bExit do
  436.             term.redirect( visWin )
  437.             term.setBackgroundColor( bgColour )
  438.             term.setTextColour( promptColour )
  439.  
  440.             write( shell.dir() .. "> " )
  441.             term.setTextColour( textColour )
  442.  
  443.             local sLine = read( nil, tCommandHistory, shell.complete )
  444.             table.insert( tCommandHistory, sLine )
  445.  
  446.             bPause = true
  447.             shell.run( sLine )
  448.             bPause = false
  449.         end
  450.     end, function ()
  451.         local nPos = nBufferdLines
  452.         while true do
  453.             local e, n = os.pullEventRaw()
  454.             if not bPause then
  455.                 local nX, nY = visWin.getCursorPos()
  456.                 if e == "key" then
  457.                     if n == keys.pageDown then
  458.                         e = "mouse_scroll"
  459.                         n = nHeight
  460.                     elseif n == keys.pageUp then
  461.                         e = "mouse_scroll"
  462.                         n = -nHeight
  463.                     end
  464.                 end
  465.  
  466.                 if e == "char" or e == "paste" then
  467.                     e = "mouse_scroll"
  468.                     n = nBufferdLines - nPos
  469.                 end
  470.  
  471.                 if e == "mouse_scroll" then
  472.                     if nPos + n >= 1 and nPos + n <= nBufferdLines then
  473.                         visWin.scroll(n)
  474.                         visWin.setCursorPos(nX, nY - n)
  475.                         nPos = nPos + n
  476.                     else
  477.                         n = n > 0 and nBufferdLines - nPos or 1 - nPos
  478.                         visWin.scroll(n)
  479.                         visWin.setCursorPos(nX, nY - n)
  480.                         nPos = nPos + n
  481.                     end
  482.                 elseif e == "mouse_click" then
  483.                     if n == 3 then
  484.                         visWin.scroll(nBufferdLines - nPos)
  485.                         visWin.setCursorPos(nX, nY - (nBufferdLines - nPos))
  486.                         nPos = nBufferdLines
  487.                     end
  488.                 end
  489.             end
  490.         end
  491.     end)
  492. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement