Advertisement
electrodude512

CC read() tab completion with /

May 8th, 2013
75
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 11.36 KB | None | 0 0
  1. --[[
  2. Modified read() with Tab-Completion v2.0 by Espen
  3. Original from shell-file of ComputerCraft v1.46
  4.  
  5. Changelog:
  6. v2.0 -  Changed the previously mixed Windows<->Linux TAB completion behaviour completely to Linux-style (except for column-display).
  7.         Added: Display of all possible matches.
  8.         Added: MORE-functionality for displaying possibilities greater than a certain number ( COMPLETION_QUERY_ITEMS )
  9. v1.2 -  Added a separate program to to allow runtime-injection of the custom read() into the running shell of an individual computer.
  10.         Thanks @BigSHinyToys for making me aware of the global table's dropped protection. ^_^
  11. v1.12 - Fixed: Pressing Tab without entering anything threw an error.
  12. v1.11 - Removed previously introduced, but now unnecessary parameter from read()
  13. v1.1 -  Path traversal implemented
  14. v1.0 -  Initial release
  15. --]]
  16.  
  17. local COMPLETION_QUERY_ITEMS = 19
  18.  
  19. local promptColour, textColour, bgColour
  20. if term.isColor() then
  21.   promptColor = colors.yellow
  22.   textColor = colors.white
  23.   bgColor = colors.black
  24. else
  25.   promptColor = colors.white
  26.   textColor = colors.white
  27.   bgColor = colors.black
  28. end
  29.  
  30. -- Returns the index up to which the two passed strings equal.
  31. -- E.g.: "Peanut" and "Peacock" would result in 3
  32. local function getMaxCommonIndex( _sOne, _sTwo )
  33.   local longWord
  34.   local shortWord
  35.  
  36.   -- Determine longer word
  37.   if #_sOne > #_sTwo then
  38.     longWord = _sOne
  39.     shortWord = _sTwo
  40.   else
  41.     longWord = _sTwo
  42.     shortWord = _sOne
  43.   end
  44.  
  45.   -- Iterate over all chars from longWord
  46.   for i = 1, #longWord do
  47.     -- Check if the current char of longWord equals the one of shortWord
  48.     if (longWord:sub(i, i) ~= shortWord:sub(i, i)) then
  49.       -- Return the last index at which the chars were still equal
  50.       return (i - 1)
  51.     end
  52.   end
  53. end
  54.  
  55. -- Returns the string which is common to all strings in the passed table, starting from the first character.
  56. -- E.g.: { "Peanut", "Peacock" } would result in "Pea"
  57. local function getMaxCommonString( _tStrings )
  58.   local nInCommon
  59.   local nSmallestCommonality
  60.   local sResult = _tStrings[1]
  61.  
  62.   -- Iterate over all strings in the passed table
  63.   for i = 1, #_tStrings do
  64.     -- As long as there is one more string to compare with, then...
  65.     if _tStrings[i + 1] ~= nil then
  66.       nInCommon = getMaxCommonIndex(_tStrings[i], _tStrings[i + 1])
  67.  
  68.       -- Only retain the smallest index (and string) up to which the last two compared strings were equal.
  69.       if (not nSmallestCommonality) or (nInCommon < nSmallestCommonality) then
  70.         nSmallestCommonality = nInCommon
  71.         sResult = _tStrings[i + 1]:sub(1, nSmallestCommonality)
  72.       end
  73.     end
  74.   end
  75.  
  76.   return sResult
  77. end
  78.  
  79.  
  80. local function read( _sReplaceChar, _tHistory )
  81.   term.setCursorBlink( true )
  82.  
  83.   local sLine = ""
  84.   local tMatches = {}
  85.   local previousTAB = false
  86.   local nHistoryPos = nil
  87.   local nPos = 0
  88.   if _sReplaceChar then
  89.     _sReplaceChar = string.sub( _sReplaceChar, 1, 1 )
  90.   end
  91.  
  92.   local w, h = term.getSize()
  93.   local sx, sy = term.getCursorPos()
  94.  
  95.   local function redraw( _sCustomReplaceChar )
  96.     local nScroll = 0
  97.     if sx + nPos >= w then
  98.       nScroll = (sx + nPos) - w
  99.     end
  100.  
  101.     term.setCursorPos( sx, sy )
  102.     local sReplace = _sCustomReplaceChar or _sReplaceChar
  103.     if sReplace then
  104.       term.write( string.rep(sReplace, string.len(sLine) - nScroll) )
  105.     else
  106.       term.write( string.sub( sLine, nScroll + 1 ) )
  107.     end
  108.     term.setCursorPos( sx + nPos - nScroll, sy )
  109.   end
  110.  
  111.   while true do
  112.     local sEvent, param = os.pullEvent()
  113.     if sEvent == "char" then
  114.       previousTAB = false  -- Reset tab-hit.
  115.       tMatches = {}  -- Reset completion match-table.
  116.       sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
  117.       nPos = nPos + 1
  118.       redraw()
  119.  
  120.     elseif sEvent == "key" then
  121.       if not (param == keys.tab) then
  122.         previousTAB = false   -- Reset tab-hit.
  123.         tMatches = {}   -- Reset completion match-table.
  124.       end
  125.  
  126.       -- Enter
  127.       if param == keys.enter then
  128.         break
  129.  
  130.       -- Tab
  131.       elseif param == keys.tab then
  132.         -- [[ TAB was hit the second time in a row. ]]
  133.         if previousTAB then
  134.           if #tMatches >= COMPLETION_QUERY_ITEMS then
  135.  
  136.             local function askForContinuation( _count )
  137.               write("\nDisplay all " .. _count .. " possibilities? (y or n)")
  138.               while true do
  139.                 local _, char = os.pullEvent("char")
  140.  
  141.                 if char:lower() == "y" then
  142.                   return true
  143.                 elseif char:lower() == "n" then
  144.                   return false
  145.                 end
  146.               end
  147.             end
  148.  
  149.             -- Arguments: _tLines (expected: String table)
  150.             local function more( _tLines )
  151.               -- Asking for continuation if too many lines
  152.               if #_tLines >= COMPLETION_QUERY_ITEMS and ( not askForContinuation(#_tLines) ) then
  153.                 print()
  154.                 return
  155.               end
  156.  
  157.               -- Initializing
  158.               local pressedENTER
  159.               local _, h = term.getSize()
  160.               print()
  161.  
  162.               -- Iterate through lines and display them
  163.               for i = 1, #_tLines do
  164.                 print(_tLines[i])
  165.                 local _, y = term.getCursorPos()
  166.  
  167.                 -- If we reach the end of the screen and there is more to come => wait for user input.
  168.                 if ( pressedENTER or ( i % (h - 1) == 0 ) ) and ( i < #_tLines ) then
  169.                   pressedENTER = false
  170.                   write("--More--")
  171.                   while true do
  172.                     local event, param = os.pullEvent()
  173.  
  174.                     if event == "key" then
  175.                       -- ENTER (Scroll a line)
  176.                       if param == keys.enter then
  177.                         pressedENTER = true
  178.                         term.scroll(1)
  179.                         term.setCursorPos(1, y - 1)
  180.                         term.clearLine()
  181.                         break
  182.  
  183.                       -- BACKSPACE (Cancel)
  184.                       elseif param == keys.backspace then
  185.                         term.setCursorPos(1, y)
  186.                         term.clearLine()
  187.                         return
  188.                       end
  189.  
  190.                     -- SPACE (Scroll a page)
  191.                     elseif event == "char" and param == " " then
  192.                       term.scroll(#_tLines - i)
  193.                       local _, y = term.getCursorPos()
  194.                       term.setCursorPos(1, y - (#_tLines - i))
  195.                       term.clearLine()
  196.                       break
  197.                     end
  198.                   end
  199.                 end
  200.               end
  201.             end
  202.  
  203.             more(tMatches)
  204.             local _, y = term.getCursorPos()
  205.             term.setBackgroundColor( bgColor )
  206.             term.setTextColor( promptColor )
  207.             write( shell.dir() .. "> " .. sLine )
  208.             term.setTextColor( textColor )
  209.             sy = y
  210.             redraw()
  211.  
  212.           elseif #tMatches > 1 then
  213.             local x, y = term.getCursorPos()
  214.            
  215.             print()
  216.             for k, v in ipairs(tMatches) do
  217.               y = y + 1
  218.               print(v)
  219.             end
  220.  
  221.             term.setBackgroundColor( bgColor )
  222.             term.setTextColor( promptColor )
  223.             write( shell.dir() .. "> " .. sLine )
  224.             term.setTextColor( textColor )
  225.            
  226.             -- Increment y coordinate only if not on the last line.
  227.             if y < h then
  228.               sy = y + 1
  229.             else
  230.               sy = h
  231.             end
  232.  
  233.             redraw()
  234.           end
  235.  
  236.         -- [[ TAB was hit once => no matches yet, look for some now. ]]
  237.         else
  238.           previousTAB = true
  239.           local sLastPar = string.match( sLine, "[^%s]+$" ) or ""     -- Get last entered parameter.
  240.           -- local sToMatch = string.match( sLastPar, "[^%s/\.]+$" ) or ""   -- Get filename-part of sLastPar.
  241.           local sToMatch = string.match( sLastPar, "[^/%.]+$" ) or ""   -- Get filename-part of sLastPar.
  242.  
  243.  
  244.           local sAbsPath = shell.resolve( sLastPar )  -- Get absolute path of the entered location.
  245.           if not fs.isDir( sAbsPath ) then
  246.             sAbsPath = string.match( sAbsPath, "^([^%s]+[/\])[^%s/\]+[/\]?$" )  -- Cut off filename, e.g. /some/test/path -> /some/test/
  247.           end
  248.           if not sAbsPath then sAbsPath = shell.resolve( "/" ) end
  249.  
  250.           -- Search for matches in the resolved folder.
  251.           local ok, tFileList = pcall( fs.list, sAbsPath )
  252.           if ok then
  253.             local match = nil
  254.             -- Populate table with all matches.
  255.             for k, v in ipairs(tFileList) do
  256.               match = string.match( v, "^"..sToMatch..".*$" )
  257.               if match then
  258.                 if fs.isDir(sAbsPath..v) then   --new
  259.                   match = match .. "/"          --new
  260.                 end                             --new
  261.                 table.insert( tMatches, match )
  262.               end
  263.             end
  264.           end
  265.  
  266.           -- Show the smallest match
  267.           local partialMatch = string.gsub( getMaxCommonString( tMatches ) or "", "^"..sToMatch, "" ) -- Cut off partial input from match.
  268.           sLine = sLine..partialMatch -- Complete partial input with prior cut off match.
  269.           nPos = nPos + string.len( partialMatch )
  270.           redraw()
  271.         end
  272.  
  273.       -- Left
  274.       elseif param == keys.left then
  275.         if nPos > 0 then
  276.           nPos = nPos - 1
  277.           redraw()
  278.         end
  279.  
  280.       -- Right
  281.       elseif param == keys.right then
  282.         if nPos < string.len(sLine) then
  283.           nPos = nPos + 1
  284.           redraw()
  285.         end
  286.  
  287.       -- Up or down
  288.       elseif param == keys.up or param == keys.down then
  289.         if _tHistory then
  290.           redraw(" ")
  291.  
  292.           -- Up
  293.           if param == keys.up then
  294.             if nHistoryPos == nil then
  295.               if #_tHistory > 0 then
  296.                 nHistoryPos = #_tHistory
  297.               end
  298.             elseif nHistoryPos > 1 then
  299.               nHistoryPos = nHistoryPos - 1
  300.             end
  301.  
  302.           -- Down
  303.           else
  304.             if nHistoryPos == #_tHistory then
  305.               nHistoryPos = nil
  306.             elseif nHistoryPos ~= nil then
  307.               nHistoryPos = nHistoryPos + 1
  308.             end
  309.           end
  310.  
  311.           if nHistoryPos then
  312.             sLine = _tHistory[nHistoryPos]
  313.             nPos = string.len( sLine )
  314.           else
  315.             sLine = ""
  316.             nPos = 0
  317.           end
  318.           redraw()
  319.         end
  320.  
  321.       -- Backspace
  322.       elseif param == keys.backspace then
  323.         if nPos > 0 then
  324.           redraw(" ")
  325.           sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
  326.           nPos = nPos - 1
  327.           redraw()
  328.         end
  329.  
  330.       -- Home
  331.       elseif param == keys.home then
  332.         nPos = 0
  333.         redraw()
  334.  
  335.       -- Delete
  336.       elseif param == keys.delete then
  337.         if nPos < string.len(sLine) then
  338.           redraw(" ")
  339.           sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 )
  340.           redraw()
  341.         end
  342.  
  343.       -- End
  344.       elseif param == keys["end"] then
  345.         nPos = string.len(sLine)
  346.         redraw()
  347.       end
  348.     end
  349.   end
  350.  
  351.   term.setCursorBlink( false )
  352.   term.setCursorPos( w + 1, sy )
  353.   print()
  354.  
  355.   return sLine
  356. end
  357.  
  358. _G.read = read
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement