Wojbie

Shell Extended 3.0 by Wojbie

Mar 31st, 2014
566
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 34.97 KB | None | 0 0
  1. local multishell = multishell
  2. local parentShell = shell
  3. local parentTerm = term.current()
  4.  
  5. if multishell then
  6.     multishell.setTitle( multishell.getCurrent(), "ex-sh" )
  7. end
  8.  
  9. local bExit = false
  10. local sDir = (parentShell and parentShell.dir()) or ""
  11. local sPath = (parentShell and parentShell.path()) or ".:/rom/programs"
  12. local tAliases = (parentShell and parentShell.aliases()) or {}
  13. local tProgramStack = {}
  14.  
  15. local shell = {}
  16. local tEnv = {
  17.     [ "shell" ] = shell,
  18.     [ "multishell" ] = multishell,
  19. }
  20.  
  21. -- Colours
  22. local promptColour, textColour, bgColour
  23. if term.isColour() then
  24.     promptColour = colours.yellow
  25.     textColour = colours.white
  26.     bgColour = colours.black
  27. else
  28.     promptColour = colours.white
  29.     textColour = colours.white
  30.     bgColour = colours.black
  31.     ghostColor = colours.black
  32. end
  33.  
  34. local function run( _sCommand, ... )
  35.     local sPath = shell.resolveProgram( _sCommand )
  36.     if sPath ~= nil then
  37.         tProgramStack[#tProgramStack + 1] = sPath
  38.         if multishell then
  39.             multishell.setTitle( multishell.getCurrent(), fs.getName( sPath ) )
  40.         end
  41.         local result = os.run( tEnv, sPath, ... )
  42.         tProgramStack[#tProgramStack] = nil
  43.         if multishell then
  44.             if #tProgramStack > 0 then
  45.                 multishell.setTitle( multishell.getCurrent(), fs.getName( tProgramStack[#tProgramStack] ) )
  46.             else
  47.                 multishell.setTitle( multishell.getCurrent(), "ex-sh" )
  48.             end
  49.         end
  50.         return result
  51.     else
  52.         printError( "No such program" )
  53.         return false
  54.     end
  55. end
  56.  
  57. local function tokenise( ... )
  58.     local sLine = table.concat( { ... }, " " )
  59.     local tWords = {}
  60.     local bQuoted = false
  61.     for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do
  62.         if bQuoted then
  63.             table.insert( tWords, match )
  64.         else
  65.             for m in string.gmatch( match, "[^ \t]+" ) do
  66.                 table.insert( tWords, m )
  67.             end
  68.         end
  69.         bQuoted = not bQuoted
  70.     end
  71.     return tWords
  72. end
  73.  
  74. -- Install shell API
  75. function shell.run( ... )
  76.     local tWords = tokenise( ... )
  77.     local sCommand = tWords[1]
  78.     if sCommand then
  79.         return run( sCommand, unpack( tWords, 2 ) )
  80.     end
  81.     return false
  82. end
  83.  
  84. function shell.exit()
  85.     bExit = true
  86. end
  87.  
  88. function shell.dir()
  89.     return sDir
  90. end
  91.  
  92. function shell.setDir( _sDir )
  93.     sDir = _sDir
  94. end
  95.  
  96. function shell.path()
  97.     return sPath
  98. end
  99.  
  100. function shell.setPath( _sPath )
  101.     sPath = _sPath
  102. end
  103.  
  104. function shell.resolve( _sPath )
  105.     local sStartChar = string.sub( _sPath, 1, 1 )
  106.     if sStartChar == "/" or sStartChar == "\\" then
  107.         return fs.combine( "", _sPath )
  108.     else
  109.         return fs.combine( sDir, _sPath )
  110.     end
  111. end
  112.  
  113. function shell.resolveProgram( _sCommand )
  114.     -- Substitute aliases firsts
  115.     if tAliases[ _sCommand ] ~= nil then
  116.         _sCommand = tAliases[ _sCommand ]
  117.     end
  118.  
  119.     -- If the path is a global path, use it directly
  120.     local sStartChar = string.sub( _sCommand, 1, 1 )
  121.     if sStartChar == "/" or sStartChar == "\\" then
  122.         local sPath = fs.combine( "", _sCommand )
  123.         if fs.exists( sPath ) and not fs.isDir( sPath ) then
  124.             return sPath
  125.         end
  126.         return nil
  127.     end
  128.    
  129.     -- Otherwise, look on the path variable
  130.     for sPath in string.gmatch(sPath, "[^:]+") do
  131.         sPath = fs.combine( shell.resolve( sPath ), _sCommand )
  132.         if fs.exists( sPath ) and not fs.isDir( sPath ) then
  133.             return sPath
  134.         end
  135.     end
  136.    
  137.     -- Not found
  138.     return nil
  139. end
  140.  
  141. function shell.programs( _bIncludeHidden )
  142.     local tItems = {}
  143.    
  144.     -- Add programs from the path
  145.     for sPath in string.gmatch(sPath, "[^:]+") do
  146.         sPath = shell.resolve( sPath )
  147.         if fs.isDir( sPath ) then
  148.             local tList = fs.list( sPath )
  149.             for n,sFile in pairs( tList ) do
  150.                 if not fs.isDir( fs.combine( sPath, sFile ) ) and
  151.                    (_bIncludeHidden or string.sub( sFile, 1, 1 ) ~= ".") then
  152.                     tItems[ sFile ] = true
  153.                 end
  154.             end
  155.         end
  156.     end
  157.  
  158.     -- Sort and return
  159.     local tItemList = {}
  160.     for sItem, b in pairs( tItems ) do
  161.         table.insert( tItemList, sItem )
  162.     end
  163.     table.sort( tItemList )
  164.     return tItemList
  165. end
  166.  
  167. function shell.getRunningProgram()
  168.     if #tProgramStack > 0 then
  169.         return tProgramStack[#tProgramStack]
  170.     end
  171.     return nil
  172. end
  173.  
  174. function shell.setAlias( _sCommand, _sProgram )
  175.     tAliases[ _sCommand ] = _sProgram
  176. end
  177.  
  178. function shell.clearAlias( _sCommand )
  179.     tAliases[ _sCommand ] = nil
  180. end
  181.  
  182. function shell.aliases()
  183.     -- Add aliases
  184.     local tCopy = {}
  185.     for sAlias, sCommand in pairs( tAliases ) do
  186.         tCopy[sAlias] = sCommand
  187.     end
  188.     return tCopy
  189. end
  190.  
  191. if multishell then
  192.     function shell.openTab( ... )
  193.         local tWords = tokenise( ... )
  194.         local sCommand = tWords[1]
  195.         if sCommand then
  196.             local sPath = shell.resolveProgram( sCommand )
  197.             if sPath == "rom/programs/shell" then
  198.                 return multishell.launch( tEnv, sPath, unpack( tWords, 2 ) )
  199.             elseif sPath ~= nil then
  200.                 return multishell.launch( tEnv, "rom/programs/shell", sPath, unpack( tWords, 2 ) )
  201.             else
  202.                 printError( "No such program" )
  203.             end
  204.         end
  205.     end
  206.  
  207.     function shell.switchTab( nID )
  208.         multishell.setFocus( nID )
  209.     end
  210. end
  211.  
  212.  
  213. --[[ Ext-shell additions --]]
  214.  
  215. --File Useage --
  216. local function save(A,B) local file = fs.open(tostring(A),"w") file.write(B) file.close() end
  217. local function saveT(A,B) save(A,textutils.serialize(B)) end
  218. local function saveTL(A,B) save(A,string.gsub(textutils.serialize(B),"\n%s*","")) end
  219. local function get(A) local file = fs.open(tostring(A),"r") if not file then return false end local data = file.readAll() file.close() if data then return data end end
  220. local function getT(A) local data = get(A) if data then data = textutils.unserialize(data) end if data then return data end end
  221.  
  222. local saveDir= (parentShell and parentShell.getRunningProgram and parentShell.getRunningProgram()) or "/"
  223. saveDir=string.sub(saveDir,1,#saveDir-#fs.getName(saveDir))
  224. if fs.isReadOnly(saveDir) then saveDir="/" end
  225.  
  226. --Additional Colors setup
  227. local argumentColour, programColour, systemColour, dirColour, fileColour ,ghostColor
  228. if term.isColour() then
  229.     argumentColour = colours.cyan
  230.     programColour = colours.lightBlue
  231.     systemColour = colours.orange
  232.     dirColour = colours.green
  233.     fileColour = colours.lightGrey
  234.     ghostColor = colours.grey
  235. else
  236.     argumentColour = colours.white
  237.     programColour = colours.white
  238.     systemColour = colours.white
  239.     dirColour = colours.white
  240.     fileColour = colours.white
  241.     ghostColor = colours.black
  242. end
  243.  
  244. --Argument Tables help functions
  245. local function sumtab(A,B)
  246. C={}
  247. for i,k in ipairs(A) do
  248.     table.insert( C, k )
  249. end
  250. for i,k in ipairs(B) do
  251.     table.insert( C, k )
  252. end
  253. return C
  254. end
  255.  
  256. local function setall(A,B)
  257.     local function Rev(A) local Revt,i,j={} for i,j in ipairs(A) do Revt[j]=i end return Revt end
  258.     local C=Rev(A)
  259.     return setmetatable({},{["__index"]=function(tab, key) if C[key] then return B end end})
  260. end
  261.  
  262. local function setany(B)
  263. return setmetatable({},{["__index"]=function(tab, key) return B end})
  264. end
  265.  
  266. local function peripherallook(A)
  267.     local per = peripheral.getNames()
  268.     for i=#per,1,-1 do
  269.         if peripheral.getType(per[i])~=A then table.remove(per,i) end
  270.     end
  271.     return per
  272. end
  273.  
  274. local function hostnameslookup(sProtocol)
  275.     -- Build list of host IDs
  276.     local tResults = {}
  277.     local close=false
  278.    
  279.     if not rednet.isOpen() then
  280.         for i,k in pairs(rs.getSides()) do
  281.             if peripheral.getType( k ) == "modem" then
  282.                 rednet.open(k)
  283.                 close=k
  284.                 break
  285.             end
  286.         end
  287.         if not close then return tResults end
  288.     end
  289.  
  290.     -- Broadcast a lookup packet
  291.     rednet.broadcast( {
  292.         sType = "lookup",
  293.         sProtocol = sProtocol,
  294.         sHostname = sHostname,
  295.     }, "dns" )
  296.  
  297.     -- Start a timer
  298.     local timer = os.startTimer( 0.5 )
  299.  
  300.     -- Wait for events
  301.     while true do
  302.         local event, p1, p2, p3 = os.pullEvent()
  303.         if event == "rednet_message" then
  304.             -- Got a rednet message, check if it's the response to our request
  305.             local nSenderID, tMessage, sMessageProtocol = p1, p2, p3
  306.             if sMessageProtocol == "dns" and tMessage.sType == "lookup response" then
  307.                 if tMessage.sProtocol == sProtocol then
  308.                         table.insert( tResults, tMessage.sHostname )
  309.                 end
  310.             end
  311.         else
  312.             -- Got a timer event, check it's the end of our timeout
  313.             if p1 == timer then
  314.                 break
  315.             end
  316.         end
  317.     end
  318.  
  319.     if close then
  320.         rednet.close(close)
  321.     end
  322.    
  323.     return tResults
  324. end
  325.  
  326. local ArgLists = {
  327.         ['side']={'left', 'right', 'top', 'bottom', 'front', 'back'},
  328.         ['slots']={},
  329.         ['equip']={'left', 'right'},
  330.         ['direction']={'left', 'right', 'forward', 'back', 'down', 'up'},
  331.         ['turn']={'left', 'right'},
  332.         ['power']={},
  333.         ['colors']={'white','orange','magenta','lightBlue','yellow','lime','pink','gray','lightGray','cyan','purple','blue','brown','green','red','black'},
  334.         ['topic']=help.topics(),
  335. }
  336. table.insert(ArgLists['topic'],1,'index')
  337. for i=1,16 do
  338.     ArgLists['slots'][i]=tostring(i)
  339. end
  340. for i=0,15 do
  341.     ArgLists['power'][i+1]=tostring(i)
  342. end
  343. table.insert(ArgLists['power'],1,'false')
  344. table.insert(ArgLists['power'],1,'true')
  345.  
  346. --Standard programs argument list.
  347.  
  348.     --If first is in arguments then go next until you reach current word
  349.     --List programs on first word (or in case of restart) use shell.resolveProgram on first word to get path
  350.     --If there is 'restart' look for word in arguments and repeat starting process
  351.  
  352.     --When you reach word you need
  353.     --If there is an ['file']=true then is simply to start filesystem pathfinding
  354.     --If there is an 'specialArgs' and you are in tab mode then run it and set result as 'args' content
  355.     --If you are outside of whole table do filesystem tab-complete without programs
  356.  
  357. local tProgramsArguments={
  358.     --['rom/program/path']={['suggest']="mode",['args']={'get','set','clear'},['next']={},['specialArgs']=function() return peripherallook("drive") end},
  359.    
  360.     --Complicated argument lists
  361.     ['rom/programs/label']={['suggest']='mode',['args']={'get','set','clear'},['next']={   
  362.         ['get']={['suggest']='[side]',['optional']=true,['args']={},['specialArgs']=function() return peripherallook("drive") end},
  363.         ['set']={['suggest']='[side] text',['optional']=true,['args']={},['specialArgs']=function() return peripherallook("drive") end,['next']=setall(ArgLists['side'],{'text'})},
  364.         ['clear']={['suggest']='[side]',['optional']=true,['args']={},['specialArgs']=function() return peripherallook("drive") end},
  365.         },
  366.     },
  367.     ['rom/programs/fun/dj']={['suggest']='mode',['args']={'play','stop'},['next']={
  368.         ['play']={['suggest']='[drive]',['optional']=true,['args']={},['specialArgs']=function() return peripherallook("drive") end},
  369.         },
  370.     },
  371.     ['rom/programs/gps'] = {['suggest']='mode',['args']={'locate', 'host'},['next']={
  372.         ['host']={['suggest']='[coordinates]'},
  373.         },
  374.     },
  375.     ['rom/programs/redstone'] = {['suggest']='mode',['args']={'probe', 'set' , 'pulse'},['next']={
  376.         ['pulse']={['suggest']='side',['args']=ArgLists['side'],['next']=setall(ArgLists['side'],{['suggest']='count',['args']={},['next']=setany({['suggest']='perdoit',['args']={}})})},
  377.         ['set']={['suggest']='side',['args']=ArgLists['side'],['next']=setall(ArgLists['side'],{['suggest']='[color] value',['optional']=true,['args']=sumtab(ArgLists['power'],ArgLists['colors']),['next']={setall(ArgLists['colors'],{['suggest']='value',['args']=ArgLists['power']})}})},
  378.         },
  379.     },
  380.     ['rom/programs/rednet/chat'] = {['suggest']='mode',['args']={'join', 'host'},['next']={
  381.         ['join']={['suggest']='hostname',['args']={},['specialArgs']=function() return hostnameslookup("chat") end,['next']=setany({['suggest']='username'})},
  382.         ['host']={['suggest']='hostname',['next']={}},
  383.         },
  384.     }, 
  385.     ['rom/programs/http/pastebin'] = {['suggest']='mode',['args']={'put', 'get' , 'run'},['next']={
  386.         ['put']={['suggest']='filename',['file']=true},
  387.         ['run']={['suggest']='code',['args']={}},
  388.         ['get']={['suggest']='code',['args']={},['next']=setany({['suggest']='filename',['file']=true})},
  389.         },
  390.     },
  391.     ['rom/programs/monitor'] = {['suggest']='side',['args']={},['specialArgs']=function() return peripherallook("monitor") end,['next']=setany({['suggest']='program',['restart']=true})},
  392.    
  393.     --Filesystem operations
  394.     ['rom/programs/copy'] = {['suggest']='source',['file']=true,['next']=setany({['suggest']='destination',['file']=true,['next']={}})},
  395.     ['rom/programs/rename'] = {['suggest']='source',['file']=true,['next']=setany({['suggest']='destination',['file']=true,['next']={}})},
  396.     ['rom/programs/move'] = {['suggest']='source',['file']=true,['next']=setany({['suggest']='destination',['file']=true,['next']={}})},
  397.    
  398.     --2 argument list
  399.     ['rom/programs/turtle/equip'] = {['suggest']='slo',['args']=ArgLists['slots'],['next']=setall(ArgLists['slots'],{['suggest']='side',['args']=ArgLists['equip']})},
  400.     ['rom/programs/turtle/go'] = {['suggest']='direction',['args']=ArgLists['direction'],['next']=setall(ArgLists['direction'],{['suggest']='[distance]',['args']={}})},
  401.     ['rom/programs/turtle/turn'] = {['suggest']='side',['args']=ArgLists['turn'],['next']=setall(ArgLists['turn'],{['suggest']='[turns]',['args']={}})},
  402.    
  403.     --one argument that can be suggested and is needed
  404.     ['rom/programs/turtle/unequip'] = {['suggest']='side',['args']=ArgLists['equip']},
  405.     ['rom/programs/eject'] = {['suggest']='side',['args']=ArgLists['side']},
  406.    
  407.     --one argument that can be suggested but optional
  408.     ['rom/programs/help'] = {['suggest']='[topic]',['args']=ArgLists['topic']},
  409.     ['rom/programs/programs'] = {['suggest']='[all]',['args']={"all"}},
  410.    
  411.     --simple arguments that will not be suggested (or are a filepaths)
  412.     ['rom/programs/turtle/craft'] = {['suggest']='amount',['args']={}},
  413.     ['rom/programs/cd'] = {['suggest']='path',['file']=true},
  414.     ['rom/programs/delete'] = {['suggest']='path',['file']=true},
  415.     ['rom/programs/mkdir'] = {['suggest']='path',['file']=true},
  416.     ['rom/programs/list'] = {['suggest']='[path]',['file']=true},
  417.     ['rom/programs/edit'] = {['suggest']='path',['file']=true},
  418.     ['rom/programs/type'] = {['suggest']='path',['file']=true},
  419.     ['rom/programs/advanced/paint'] = {['suggest']='path',['file']=true},
  420.     ['rom/programs/advanced/bg'] = {['suggest']='program',['restart']=true},
  421.     ['rom/programs/advanced/fg'] = {['suggest']='program',['restart']=true},
  422.     ['rom/programs/shell'] = {['suggest']='[program]',['restart']=true},
  423.     ['rom/programs/turtle/refuel'] = {['suggest']='amount',['args']={}},
  424.     ['rom/programs/turtle/tunnel'] = {['suggest']='length',['args']={}},
  425.     ['rom/programs/turtle/excavate'] = {['suggest']='diameter',['args']={}},
  426. }
  427.  
  428. --System Commands Tables (arugments and functions)
  429.  
  430.     --If first is in System Commands arguments then go next until you reach current word using same algorythm as table before.
  431.  
  432. local tSystemArguments={
  433.     --['rom/program/path']={['suggest']="mode",['args']={'get','set','clear'},['next']={},['specialArgs']=function() return peripherallook("drive") end},
  434.     ['!lua']={['suggest']="lua code",},
  435.     ['!rlua']={},
  436.     ['!wild']={['suggest']="Wildcard path",},
  437.     ['!cat']={['suggest']="path",['file']=true,['next']={}},
  438.     ['!find']={['suggest']="Filename to find",},
  439. }
  440. tSystemArguments['!cat']['next']=setany(tSystemArguments['!cat'])
  441.     --locals for SystemFunctions
  442. local LuatEnv = {["_echo"] = function( ... ) return ... end,}
  443. setmetatable( LuatEnv, { __index = getfenv() } )
  444. local nextPrefix=""
  445.  
  446.     --Those are the functions you run providing whole sLine.
  447. local tSystemFunctions={
  448.     --['!name']=function(_String) end,
  449.     ['!lua']=function(_String)
  450.         local nForcePrint = 0
  451.         local func, e = loadstring( _String, "lua" )
  452.         local func2, e2 = loadstring( "return _echo(".._String..");", "lua" )
  453.         if not func then
  454.             if func2 then
  455.                 func = func2
  456.                 e = nil
  457.                 nForcePrint = 1
  458.             end
  459.         else
  460.             if func2 then
  461.                 func = func2
  462.             end
  463.         end
  464.         if func then
  465.             setfenv( func, LuatEnv )
  466.             local tResults = { pcall( func ) }
  467.             if tResults[1] then
  468.                 local n = 1
  469.                 while (tResults[n + 1] ~= nil) or (n <= nForcePrint) do
  470.                     local value = tResults[ n + 1 ]
  471.                     if type( value ) == "table" then
  472.                         local ok, serialised = pcall( textutils.serialise, value )
  473.                         if ok then
  474.                             print( serialised )
  475.                         else
  476.                             print( tostring( value ) )
  477.                         end
  478.                     else
  479.                         print( tostring( value ) )
  480.                     end
  481.                     n = n + 1
  482.                 end
  483.             else
  484.                 printError( tResults[2] )
  485.             end
  486.         else
  487.             printError( e )
  488.         end
  489.         nextPrefix="!lua "
  490.     end,
  491.     ['!rlua']=function(_String) LuatEnv = {["_echo"] = function( ... ) return ... end,} setmetatable( LuatEnv, { __index = getfenv() } ) print("Lua Enviroment Reset") end,
  492.     ['!cowsay']=function(_String) local offline = {"Allan please add cow and tux detail","Play Minecraft. Oh wait, you are!","Go home, you're drunk.","VIFIIIINOOOOOOO!!!! I'M WATCHING YOU!!!!","WOOF!","Meow!","Don't cry because it's over, smile because it happened.","You only live once, but if you do it right, once is enough.","Behind every great man, there is a woman rolling her eyes.","A day without sunshine is like, you know, night.","Housework can't kill you, but why take the chance?","Localize your variables Csstform","I don't care if I'm a lemming. I'm not going.","I program, therefore I am.","I support everyone's right to be an idiot. I may need it someday.","I think we're all Bozos on this bus.","If a listener nods his head when you're explaining your program, wake him up.","If at first you don't succeed, redefine success.","Misery loves company, but company does not reciprocate.","Many receive advice, few profit from it.","Make it right before you make it faster.",} print(offline[math.random(1,#offline)]) end,
  493.     ['!wild']=function(_String) print("Wildcard result:") textutils.pagedTabulate( programColour , fs.find(shell.resolve(tokenise(_String)[1]))) end,
  494.     ['!cat']=function(_String)
  495.         local out={}
  496.         for i,k in ipairs(tokenise(_String)) do
  497.             local path=shell.resolve(k)
  498.             if not fs.exists(path) then print("File "..k.." don't exists") return end
  499.             if fs.isDir(path) then print(k.." is a directory") return end
  500.             local file = fs.open(path, "r")
  501.             table.insert(out,file.readAll())
  502.             file.close()
  503.         end
  504.         textutils.pagedPrint(table.concat(out))
  505.     end,
  506.     ['!find']=function(_String)
  507.         local target = tokenise(_String)[1]
  508.         if not target or target == "" then print("No target specified") return end
  509.         local matches={}
  510.         target=string.lower(target)
  511.    
  512.         local function process(path)
  513.             local T=fs.list(path)
  514.             for _,k in ipairs(T) do
  515.                 local cpath=path.."/"..k
  516.                 if string.find(string.lower(k), target , 1 , not patt) then table.insert(matches,cpath) end
  517.                 if fs.isDir(cpath) then process(cpath) end
  518.             end
  519.         end
  520.        
  521.         local function list(_Tlist)
  522.             local tFiles = {}
  523.             local tDirs = {}
  524.             for n, sItem in pairs(_Tlist) do
  525.                 if fs.isDir( sItem ) then
  526.                     table.insert( tDirs, sItem )
  527.                 else
  528.                     table.insert( tFiles, sItem )
  529.                 end
  530.             end
  531.             table.sort( tDirs ) table.sort( tFiles )
  532.             textutils.pagedTabulate(dirColour , tDirs , fileColour, tFiles)
  533.         end
  534.        
  535.         process("")
  536.         list(matches)
  537.     end,
  538.     ['!test']=function(_String) print("s",_String,"e") end,
  539. }
  540.  
  541.     --This is list of them generated from tSystemArguments and sorted by alphabeth.
  542. local tSystemFunctionsNames={}
  543. for i,k in pairs(tSystemArguments) do
  544.     table.insert( tSystemFunctionsNames, i )
  545. end
  546. table.sort( tSystemFunctionsNames )
  547.  
  548. --Tab completiton Main functions
  549.  
  550. local function tabtokenise( sLine ) --cuts line into table of words and gives 2nd table of start/stop points of each word - ("") matter.
  551.     local tWords = {}
  552.     local tPoints = {}
  553.     local bQuoted = false
  554.     local nOff = 0
  555.     for start,match,stop in string.gmatch( sLine .. "\"", "()(.-)()\"" ) do
  556.         if bQuoted then
  557.             table.insert( tWords, match )
  558.             table.insert( tPoints, {start,stop})
  559.             nOff = stop
  560.         else
  561.             for sta,m,sto in string.gmatch( match, "()([^ \t]+)()" ) do
  562.                 table.insert( tWords, m )
  563.                 table.insert( tPoints, {sta+nOff,sto+nOff})
  564.             end
  565.         end
  566.         bQuoted = not bQuoted
  567.     end
  568.     if string.sub( sLine, #sLine, #sLine )==" " and #tWords>0 then table.insert( tWords, "" ) table.insert( tPoints, {#sLine,#sLine+1} ) end
  569.     return tWords,tPoints
  570. end
  571.  
  572. local function common(_String,_Cur) --need a total rewrite propably. Is supposed to find common start part of 2 given strings.
  573.     if not _Cur then return _String end
  574.     for i=#_String,1,-1 do
  575.         local a,b=string.sub (_String,1,i),string.sub(_Cur,1,i)
  576.         if a==b then return a end      
  577.     end
  578.     return ""
  579. end
  580.  
  581. --if first is in arguments then go next until you reach current word
  582. --list programs on first word (or in case of restart) use shell.resolveProgram on firs word to get path
  583. --if no next then stop suggesting and just do programs+filesystem
  584. --if there is 'restart' look for word in arguments and repeat starting process
  585.  
  586. --when you reach word you need
  587. --if there is an ['file']=true then is simply to start filesystem pathfinding
  588. --if there is an 'specialArgs' then run it and add result it to args content using sumtab
  589. --if you are outside of whole table do filesystem tab-complete without programs
  590.  
  591. local function tabcomplete(sLine,nPos,SpecialMode) --Main tabcomplete
  592.  
  593.     local tWords,tPoints = tabtokenise(sLine) --create 2 tables and fill then with content.
  594.  
  595.     --list of functions used by this function
  596.    
  597.     local function loopstarter(nCurrent,nNeeded,bFirstLoop) --returns sSuggestion,tArguments,bSystem,bPrograms,bFilesystem
  598.         if nNeeded==0 then return "",{},false,false,true end
  599.        
  600.         local function loop(tab,num,current)
  601.             if not tab then tab={} end
  602.             if tab['restart'] then
  603.                 if num==current then
  604.                     local suggest,t1,b1,b2,b3 = loopstarter(num,current)
  605.                     suggest = (#tWords[num]==0 and tab['suggest']) or suggest
  606.                     return suggest,t1,b1,b2,b3
  607.                 else return loopstarter(num,current) end
  608.             elseif num==current then
  609.                 if tab['specialArgs'] and SpecialMode then tab['args']=tab['specialArgs']() end
  610.                 return ((#tWords[num]==0 and tab['suggest']) or ""),(tab['args'] or {}),false,false,tab['file']
  611.             elseif tab['next'] then return loop(tab['next'][tWords[num]],num+1,current)
  612.             else return "",{},false,false,true end
  613.         end
  614.        
  615.         local first=shell.resolveProgram( tWords[nCurrent] or "" )
  616.         if tProgramsArguments[first] then --written word matches one of programs
  617.             local tab=tProgramsArguments[first]
  618.             if nCurrent==nNeeded then --nothing else written - return suggestions for that program,{} for arguments
  619.                 return (tab['suggest'] and " "..tab['suggest']),{},bFirstLoop,true,true
  620.             else return loop(tab,nCurrent+1,nNeeded) end --look deeper into system along this program path.
  621.            
  622.         elseif bFirstLoop and tSystemArguments[ tWords[nCurrent] or "" ] then
  623.        
  624.             local tab=tSystemArguments[ tWords[nCurrent] or "" ]
  625.             if nCurrent==nNeeded then --nothing else written - return suggestions for that command,{} for arguments
  626.                 return (tab['suggest'] and " "..tab['suggest']),{},bFirstLoop,true,true
  627.             else return loop(tab,nCurrent+1,nNeeded) end --look deeper into system along this command path.
  628.            
  629.         end
  630.         --we have no match in system
  631.         return "",{},bFirstLoop and nCurrent==nNeeded,nCurrent==nNeeded,true --,filesystem and Syst
  632.     end
  633.  
  634.     --New Code
  635.         --find if you are in word or at end if it
  636.     local Word=0
  637.     local ending=0
  638.     local letter=nPos+1
  639.     for i,k in ipairs(tPoints) do
  640.         if letter>=k[1] and letter<=k[2] then Word=i break end 
  641.         ending=k[2]
  642.     end
  643.    
  644.     --start stop points of said word.
  645.     local starts=tPoints[Word] and tPoints[Word][1] or nPos
  646.     local stops=tPoints[Word] and tPoints[Word][2] or nPos
  647.    
  648.     --list tables of possible completitions.
  649.     local sSuggestion,tArguments,tSystem,tPrograms,tFiles = loopstarter(1,Word,true)
  650.    
  651.    
  652.     --Sanitizing magic for pattern needs ^$()%.[]*+-?
  653.     local sWord=tWords[Word] or ""
  654.     local pattern="^"..string.gsub(sWord,"[%^%$%(%)%%%.%[%]%*%+%-%?]",function(A) return "%"..A end) --..".*"
  655.    
  656.     local sCommon=nil --common part of all names in the list.
  657.    
  658.     --tArguments Analisis.
  659.    
  660.     local tTemp=tArguments
  661.     tArguments = {}
  662.     for i,k in pairs(tTemp) do
  663.         if string.match(k, pattern ) then table.insert( tArguments, k ) sCommon=common(k,sCommon) end
  664.     end
  665.  
  666.     --tSystem analisis.
  667.    
  668.     if tSystem then
  669.         tSystem = {}
  670.         for i,k in pairs(tSystemFunctionsNames) do
  671.             if string.match(k, pattern ) then table.insert( tSystem, k ) sCommon=common(k,sCommon) end
  672.         end
  673.     else tSystem={} end
  674.    
  675.     --tPrograms analisis.
  676.    
  677.     if tPrograms then
  678.         tPrograms = {}
  679.         -- Add programs from the path
  680.         for sPath in string.gmatch(sPath, "[^:]+") do
  681.             if sPath~="." then --skip current folder contents.
  682.                 sPath = shell.resolve( sPath )
  683.                 if fs.isDir( sPath ) then
  684.                     local tList = fs.list( sPath )
  685.                     for n,sFile in pairs( tList ) do
  686.                         if not fs.isDir( fs.combine( sPath, sFile ) ) then
  687.                             if string.match(sFile, pattern ) then table.insert( tPrograms, sFile ) sCommon=common(sFile,sCommon) end
  688.                         end
  689.                     end
  690.                 end
  691.             end
  692.         end
  693.         table.sort( tPrograms )
  694.     else tPrograms={} end
  695.    
  696.     --future plans?:
  697.     --CTRL+!-0 combos in multishell
  698.     --so you type " and it does "|"
  699.     --if ghost has " " inside and is added quote whole word in if its not quoted already.
  700.    
  701.     --tFiles analisis
  702.     local tDirs = {}
  703.     if tFiles then
  704.         tFiles={}
  705.         local AllFiles={} --result from list       
  706.        
  707.         local name=fs.getName(sWord.."a")
  708.         name=string.sub( name , 1 , #name-1) --fix for . handling
  709.        
  710.         local rootpath = shell.resolve(sWord.."a")
  711.         rootpath=string.sub( rootpath , 1 , #rootpath-1) --fix for . handling
  712.        
  713.         local namelessrootpath = #rootpath==0 and "" or string.sub( rootpath , 1 , #rootpath-#name)
  714.        
  715.         local path = sWord
  716.        
  717.         local namelesspath = #path==0 and "" or  string.sub( path , 1 , #path-#name)
  718.        
  719.         local patternfile="^"..string.gsub(name,"[%^%$%(%)%%%.%[%]%*%+%-%?]",function(A) return "%"..A end) --..".*"
  720.        
  721.         if path=="" then --we are at sDir
  722.             AllFiles=fs.list(sDir)
  723.             namelesspath=sWord
  724.             namelessrootpath=sDir
  725.         elseif string.match(sWord,"[\\/]$") and fs.exists(rootpath) then --ends with / or\ - foldercase or rootcase.
  726.             AllFiles=fs.list(rootpath)
  727.             namelesspath=sWord
  728.             namelessrootpath=rootpath
  729.         elseif fs.exists(namelessrootpath) then --has some content i can match to.
  730.             for i,k in pairs(fs.list(namelessrootpath)) do
  731.                 if string.match(k, patternfile ) then
  732.                     table.insert(AllFiles,k)
  733.                 end
  734.             end
  735.         end
  736.  
  737.         for i,k in pairs(AllFiles) do
  738.             sCommon=common(namelesspath..k,sCommon)
  739.             if fs.isDir(namelessrootpath..k) then
  740.             table.insert(tDirs,k)
  741.             else
  742.             table.insert(tFiles,k)
  743.             end
  744.         end
  745.  
  746.         table.sort( tFiles )
  747.         table.sort( tDirs )
  748.     else tFiles={} end
  749.    
  750.     --if SpecialMode and more then one possibility - print them all.
  751.     if SpecialMode and #tArguments+#tSystem+#tPrograms+#tFiles+#tDirs>1 then
  752.    
  753.         pcall( function()
  754.             print()
  755.             textutils.pagedTabulate( argumentColour, tArguments , systemColour , tSystem , programColour , tPrograms , dirColour , tDirs , fileColour, tFiles)
  756.         end)
  757.    
  758.     end
  759.    
  760.     sCommon = sCommon or ""
  761.     if #tWords>0 then sCommon= (sCommon and string.sub(sCommon, #sWord+1)) or "" end
  762.     local ghost = sCommon..(sSuggestion or "")
  763.    
  764.     return starts,stops,ghost,sCommon,#tArguments+#tSystem+#tPrograms+#tFiles+#tDirs,#tDirs
  765. end
  766.  
  767.  
  768. local function tabread( _sReplaceChar, _tHistory , _sPrefix)
  769.     term.setCursorBlink( true )
  770.  
  771.     local sLine = _sPrefix or ""
  772.     local sGhost = ""
  773.     local nHistoryPos
  774.     local nPos = #sLine
  775.     if _sReplaceChar then
  776.         _sReplaceChar = string.sub( _sReplaceChar, 1, 1 )
  777.     end
  778.    
  779.     local w = term.getSize()
  780.     local sx = term.getCursorPos()
  781.        
  782.     local function ghost()
  783.         local last = string.match(sLine,"()[ \t]*$")+1
  784.         if nPos==#sLine then
  785.             local _,_,suggest=tabcomplete(sLine,nPos)
  786.             sGhost = suggest or ""
  787.         else
  788.             sGhost = ""
  789.         end
  790.        
  791.     end
  792.    
  793.     local function redraw( _sCustomReplaceChar )
  794.         if _sCustomReplaceChar~=" " then ghost() end
  795.         local nScroll = 0
  796.         if sx + nPos + #sGhost >= w then
  797.             nScroll = (sx + nPos + #sGhost) - w
  798.         end
  799.  
  800.         local cx,cy = term.getCursorPos()
  801.         term.setCursorPos( sx, cy )
  802.         local sReplace = _sCustomReplaceChar or _sReplaceChar
  803.         if sReplace then
  804.             term.write( string.rep( sReplace, math.max( string.len(sLine) - nScroll, 0 )+string.len(sGhost) ) )
  805.         else
  806.             term.write( string.sub( sLine, nScroll + 1 ) )
  807.             term.setTextColour(ghostColor)
  808.             term.write( sGhost )
  809.             term.setTextColour(textColour)
  810.         end
  811.         term.setCursorPos( sx + nPos - nScroll, cy )
  812.     end
  813.     redraw()
  814.    
  815.     while true do
  816.         local sEvent, param = os.pullEvent()
  817.         if sEvent == "char" then
  818.             -- Typed key
  819.             redraw(" ")
  820.             sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
  821.             nPos = nPos + 1
  822.             redraw()
  823.            
  824.         elseif sEvent == "paste" then
  825.             -- Pasted text
  826.             redraw(" ")
  827.             sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
  828.             nPos = nPos + string.len( param )
  829.             redraw()
  830.  
  831.         elseif sEvent == "key" then
  832.             if param == keys.enter then
  833.                 -- Enter
  834.                 if sLine ~= "" and sLine ~= _sPrefix then break end
  835.                
  836.             elseif param == keys.left then
  837.                 -- Left
  838.                 if nPos > 0 then
  839.                     redraw(" ")
  840.                     nPos = nPos - 1
  841.                     redraw()
  842.                 end
  843.                
  844.             elseif param == keys.right then
  845.                 -- Right                
  846.                 if nPos < string.len(sLine) then
  847.                     redraw(" ")
  848.                     nPos = nPos + 1
  849.                     redraw()
  850.                 end
  851.            
  852.             elseif param == keys.up or param == keys.down then
  853.                 -- Up or down
  854.                 if _tHistory then
  855.                     redraw(" ")
  856.                     if param == keys.up then
  857.                         -- Up
  858.                         if nHistoryPos == nil then
  859.                             if #_tHistory > 0 then
  860.                                 nHistoryPos = #_tHistory
  861.                             end
  862.                         elseif nHistoryPos > 1 then
  863.                             nHistoryPos = nHistoryPos - 1
  864.                         end
  865.                     else
  866.                         -- Down
  867.                         if nHistoryPos == #_tHistory then
  868.                             nHistoryPos = nil
  869.                         elseif nHistoryPos ~= nil then
  870.                             nHistoryPos = nHistoryPos + 1
  871.                         end                        
  872.                     end
  873.                     if nHistoryPos then
  874.                         sLine = _tHistory[nHistoryPos]
  875.                         nPos = string.len( sLine )
  876.                     else
  877.                         sLine = ""
  878.                         nPos = 0
  879.                     end
  880.                     redraw()
  881.                 end
  882.             elseif param == keys.backspace then
  883.                 -- Backspace
  884.                 if nPos > 0 then
  885.                     redraw(" ")
  886.                     sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
  887.                     nPos = nPos - 1                    
  888.                     redraw()
  889.                 end
  890.             elseif param == keys.home then
  891.                 -- Home
  892.                 redraw(" ")
  893.                 nPos = 0
  894.                 redraw()        
  895.             elseif param == keys.delete then
  896.                 -- Delete
  897.                 if nPos < string.len(sLine) then
  898.                     redraw(" ")
  899.                     sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 )                
  900.                     redraw()
  901.                 end
  902.             elseif param == keys["end"] then
  903.                 -- End
  904.                 redraw(" ")
  905.                 nPos = string.len(sLine)
  906.                 redraw()
  907.             elseif param == keys["tab"] then
  908.                 -- Tab
  909.                 --redraw(" ")
  910.                 term.setCursorBlink( false )
  911.                 local Pos1,Pos2,suggest,common,finds,folders=tabcomplete(sLine,nPos,true)
  912.                     if finds>0 then --do  nothing
  913.                         local word=common or ""
  914.                         if finds==1 and common then
  915.                             redraw(" ")
  916.                             if folders==1 then word=word.."/"
  917.                             elseif Pos2-1==#sLine then  word=word.." "  end
  918.                         else
  919.                             term.setTextColour( promptColour )
  920.                             write( shell.dir() .. "> " )
  921.                             term.setTextColour( textColour )
  922.                             sx, sy = term.getCursorPos()
  923.                         end
  924.                         --place common inplace
  925.                         sLine = string.sub( sLine, 1, Pos2-1 ) .. word .. string.sub( sLine, Pos2 )
  926.                         nPos=math.min(math.max(Pos2-1+#word,0),#sLine)
  927.                     end
  928.                 term.setCursorBlink( true )
  929.                 redraw()
  930.             elseif param == keys["pageUp"] then
  931.                 --scrollMove(parenth)
  932.             elseif param == keys["pageDown"] then
  933.                 --scrollMove(-parenth)
  934.             end
  935.         elseif sEvent == "mouse_scroll" then
  936.             --mouse scroll detected
  937.             --scrollMove(-param)
  938.         elseif sEvent == "term_resize" then
  939.             -- Terminal resized
  940.             w = term.getSize()
  941.             redraw()
  942.         end
  943.     end
  944.  
  945.     local cx, cy = term.getCursorPos()
  946.     term.setCursorBlink( false )
  947.     term.setCursorPos( w + 1, cy )
  948.     print()
  949.    
  950.     return sLine
  951. end
  952.  
  953. --special functions testing
  954. local function tabrun(sLine)
  955.     local tWords = tokenise(sLine)
  956.     local sCommand = tWords[1]
  957.     if tSystemFunctions[sCommand] then
  958.         return pcall( function() tSystemFunctions[sCommand](string.sub( sLine, #sCommand+1)) end)
  959.     end
  960.     return shell.run( sLine )
  961. end
  962.  
  963. --back to almost normal execution
  964.  
  965. local tArgs = { ... }
  966. if #tArgs > 0 then
  967.     -- "shell x y z"
  968.     -- Run the program specified on the commandline
  969.     shell.run( ... )
  970.  
  971. else
  972.     -- "shell"
  973.     -- Print the header
  974.     if term.getCursorPos()>1 then print() end
  975.     term.setBackgroundColor( bgColour )
  976.     term.setTextColour( promptColour )
  977.     print( os.version()," Extended" )
  978.     term.setTextColour( textColour )
  979.  
  980.     -- Run the startup program
  981.     if parentShell == nil then
  982.         shell.run( "/rom/startup" )
  983.     end
  984.  
  985.      --pernament history.
  986.     local tCommandHistoryfile=fs.combine(saveDir,".shell.log")
  987.     local tCommandHistory = {}
  988.     if fs.exists(tCommandHistoryfile) then tCommandHistory = getT(tCommandHistoryfile) or {} end
  989.      -- Read commands and execute them
  990.     while not bExit do
  991.         term.redirect( parentTerm )
  992.         term.setBackgroundColor( bgColour )
  993.         term.setTextColour( promptColour )
  994.         --local tx,ty=term.getCursorPos()
  995.         if term.getCursorPos()>1 then print() end
  996.         write( shell.dir() .. "> " )
  997.         term.setTextColour( textColour )
  998.         local sLine = tabread( nil, tCommandHistory, nextPrefix)
  999.         if sLine ~= tCommandHistory[#tCommandHistory] then
  1000.             table.insert( tCommandHistory, sLine )
  1001.             if tCommandHistory[101] then table.remove(tCommandHistory,1) end
  1002.             saveTL(tCommandHistoryfile,tCommandHistory)
  1003.         end
  1004.         nextPrefix=""
  1005.         tabrun( sLine )--shell.run( sLine )--
  1006.     end
  1007.     --fixing stuff
  1008.     term.setBackgroundColor( bgColour )
  1009.     term.setTextColour( promptColour )
  1010.     print( os.version()," Extended Shutting Down" )
  1011.     term.setTextColour( textColour )
  1012.     term.redirect( parentTerm )
  1013. end
Add Comment
Please, Sign In to add comment