Advertisement
Alakazard12

TAbs

Apr 14th, 2013
114
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 10.69 KB | None | 0 0
  1.  
  2. ---improved read() function with two modes for TAB completion:
  3. --1: reads keys from a given environment
  4. --2: reads file system paths
  5. ---use:
  6. --read(_sReplaceChar, _tHistory,_tEnv,_mode)
  7. --_tEnv is the environment to scan; default is _G
  8. --_mode is by default true, what means mode 2 is chosen by default
  9. --rightShift pressed once resets the matches; pressed twice changes the mode.
  10. --tab-completing a full match adds a termination when possible, e.g. try calling test() (down below)
  11. --and pressing: [tab][rigthShift][tab]
  12.  
  13. --fix for CC twisted bits
  14. -- local old_getmetatable=getmetatable
  15. -- local getmetatable=function(t)
  16.     -- if type(t)=='string' then return string end
  17.     -- return old_getmetatable(t)
  18. -- end
  19.  
  20. local pathutils={} --API
  21. do
  22.     local segments, get, concat, find
  23.  
  24.     local function pathconcat(pt, starti, endi)
  25.         local t = {}
  26.         local prev
  27.         local empties = 0
  28.         starti = starti or 1
  29.         endi = endi or #pt
  30.        
  31.         for i = starti, endi do
  32.             local v = pt[i]
  33.             if not v then break
  34.             elseif v == '' then
  35.                 empties = empties+1
  36.             else
  37.                 table.insert(t, prev)
  38.                 prev = v
  39.             end
  40.         end
  41.         table.insert(t, prev)
  42.         --log('PATH', 'INFO', "pathconcat(%s, %d, %d) generates table %s, wants indexes %d->%d",
  43.         --    sprint(pt), starti, endi, sprint(t), 1, endi-starti+1-empties)
  44.         return table.concat(t, '.', 1, endi-starti+1-empties)
  45.     end
  46.  
  47.     function get(t, path)
  48.         local p = type(path)=='string' and segments(path) or path
  49.         local k = table.remove(p)
  50.         if not k then return t end
  51.         local t = find(t, p)
  52.         return t and t[k]
  53.     end
  54.  
  55.  
  56.  
  57.     function segments(path)
  58.         local t = {}    
  59.         local index, newindex, elt = 1
  60.         repeat
  61.             newindex = path:find(".", index, true) or #path+1 --last round
  62.             elt = path:sub(index, newindex-1)
  63.             elt = tonumber(elt) or elt        
  64.             if elt and elt ~= "" then table.insert(t, elt) end
  65.             index = newindex+1    
  66.         until newindex==#path+1  
  67.         return t
  68.     end
  69.  
  70.     function find(t, path, force)
  71.         path = type(path)=="string" and segments(path) or path
  72.         for i, n in ipairs(path) do
  73.             local v  = t[n]
  74.             if type(v) ~= "table" then
  75.                 if not force or (force=="noowr" and v~=nil) then return nil, pathconcat(path, 1, i)
  76.                 else v = {} t[n] = v end
  77.             end
  78.             t = v
  79.         end
  80.         return t
  81.     end
  82.    
  83.     pathutils.get=get
  84. end
  85.  
  86.  
  87. local function autocomplete(path, env)
  88.     env = env or _G
  89.     path = path or ""
  90.  
  91.     path = path:match("([%w_][%w_%.%:]*)$") or "" -- get the significant end part of the path (non alphanum are spliters...)
  92.     local p, s, l = path:match("(.-)([%.%:]?)([^%.%:]*)$") -- separate into sub path and leaf, getting the separator
  93.     local t = pathutils.get(env, p)
  94.     local funconly =  s == ":"
  95.     local tr = {}
  96.  
  97.     local function copykeys(src, dst)
  98.         if type(src) ~= 'table' then return end
  99.         for k, v in pairs(src) do
  100.             local tv = type(v)
  101.             if type(k)=='string' and k:match("^"..l) and (not funconly or tv=='function' or (tv=='table' and getmetatable(v) and getmetatable(v).__call)) then dst[k] = true end
  102.         end
  103.     end
  104.  
  105.     copykeys(t, tr)
  106.  
  107.     if s == ":" or s == "." then
  108.         local m = getmetatable(t)
  109.         if m then
  110.             local i, n = m.__index, m.__newindex
  111.             copykeys(i, tr)
  112.             if n ~= i then copykeys(n, tr) end
  113.             if m~= i and m ~= n then copykeys(m, tr) end -- far from being perfect, but it happens that __index or __newindex are functions that uses the metatable as a object method holder
  114.         end
  115.     end
  116.     local r = {}
  117.     for k, v in pairs(tr) do table.insert(r, k) end
  118.  
  119.     -- sort the entries by lexical order
  120.     table.sort(r)
  121.     --when we have a complete match we can add a ".", ":" or "(" if the object is a table or a function
  122.     if l == r[1] then
  123.         local tl = t[l]
  124.         local ttl = type(tl)
  125.         if ttl == 'function' then r[1]=r[1].."("
  126.         elseif getmetatable(tl) and getmetatable(tl).__index then
  127.             r[1]=r[1]..":" -- pure guess, object that have __index metamethod may be an 'object', so add ':'
  128.         elseif ttl == 'table' then r[1]=r[1].."."
  129.         end
  130.     end
  131.     l=l or ''
  132.     return r,l:len()
  133. end
  134.  
  135.  
  136. local function get_to_match(sLine,nPos)
  137.     if sLine:sub(nPos,nPos)==' ' then
  138.         return ''
  139.     end
  140.     local sRev=sLine:reverse()
  141.     sLeft=sRev:match('[^_]*',sRev:len()-nPos):reverse()
  142.     --sRigth=sLine:match('[^ ]*',nPos+1)
  143.     return sLeft--..sRigth
  144. end
  145.  
  146.  
  147. local function esc(x)
  148.   return (x:gsub('%%', '%%%%')
  149.            :gsub('%^', '%%%^')
  150.            :gsub('%$', '%%%$')
  151.            :gsub('%(', '%%%(')
  152.            :gsub('%)', '%%%)')
  153.            :gsub('%.', '%%%.')
  154.            :gsub('%[', '%%%[')
  155.            :gsub('%]', '%%%]')
  156.            :gsub('%*', '%%%*')
  157.            :gsub('%+', '%%%+')
  158.            :gsub('%-', '%%%-')
  159.            :gsub('%?', '%%%?'))
  160. end
  161.  
  162. function get_fs_matches(s)
  163.     local tMatches={}
  164.     local path = s:match([[[^%[%]%'%"]+$]]) or "" -- get the significant end part of the path
  165.    local p, s, l = path:match("^(.-)([/\\]?)([^/\\]*)$") -- separate into sub path and leaf, getting the separator
  166.     local sAbsPath = _G.shell and _G.shell.resolve( p ) or p
  167.     if fs.isDir( sAbsPath ) then
  168.         -- Search for matches in the resolved folder.
  169.         local ok, tFileList = pcall( fs.list, sAbsPath )
  170.         if ok then
  171.             local match = nil
  172.             -- Populate table with all matches.
  173.             local pat="^"..esc(l)..'.*'
  174.             for k, v in ipairs(tFileList) do
  175.                 match = string.match( v, pat)
  176.                 if match then
  177.                     -- print(match)
  178.                     table.insert( tMatches,match )
  179.                 end
  180.             end
  181.         end
  182.     end
  183.     table.sort(tMatches)
  184.     if l == tMatches[1] then
  185.         local tl = tMatches[1]
  186.         if fs.isDir(sAbsPath..'/'..l) then
  187.              tMatches[1]= tMatches[1]..'/'
  188.         end
  189.     end
  190.     return tMatches,l:len()
  191. end
  192.  
  193. local get_env_matches=autocomplete
  194.  
  195. local function read( _sReplaceChar, _tHistory,_tEnv,_mode)
  196.     local mode=_mode==nil or _mode==true
  197.    
  198.    term.setCursorBlink( true )
  199.  
  200.    local sLine = ""
  201.     local nHistoryPos = nil
  202.     local nPos = 0
  203.    if _sReplaceChar then
  204.        _sReplaceChar = string.sub( _sReplaceChar, 1, 1 )
  205.    end
  206.     local count=0
  207.    
  208.     local tMatches = { n = 0}
  209.     function reset_matches()
  210.         tMatches = { n = 0}
  211.     end
  212.    
  213.     function get_matches(s)
  214.         if mode then
  215.             return get_fs_matches(s)
  216.         else
  217.             return get_env_matches(s,_tEnv)
  218.         end
  219.     end
  220.    
  221.     local w, h = term.getSize()
  222.     local sx, sy = term.getCursorPos()    
  223.  
  224.     local function redraw( _sCustomReplaceChar )
  225.             local nScroll = 0
  226.             if sx + nPos >= w then
  227.                     nScroll = (sx + nPos) - w
  228.             end
  229.             term.setCursorPos( sx, sy )
  230.             local sReplace = _sCustomReplaceChar or _sReplaceChar
  231.             if sReplace then
  232.                     term.write( string.rep(sReplace, string.len(sLine) - nScroll) )
  233.             else
  234.                     term.write( string.sub( sLine, nScroll + 1 ) )
  235.             end
  236.             term.setCursorPos( sx + nPos - nScroll, sy )
  237.     end
  238.  
  239.     while true do
  240.             local sEvent, param = os.pullEvent()
  241.             if sEvent == "char" then
  242.                     reset_matches()
  243.                     sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
  244.                     nPos = nPos + 1
  245.                     redraw()
  246.                    
  247.             elseif sEvent == "key" then
  248.                 if not (param == keys.tab) then
  249.                     reset_matches() -- Reset completion match-table.
  250.                 end
  251.                 if not(param == keys.rightShift) then
  252.                     count=0
  253.                 end
  254.                 if param == keys.enter then
  255.                     -- Enter
  256.                     break          
  257.                 elseif param == keys.tab then
  258.                     -- Tab
  259.                     if tMatches[1] then   -- tab was pressed before.
  260.                         -- [[ We already have matches, show the next one ]]
  261.                         local nLastMatchSize = string.len( tMatches[tMatches.n])
  262.                        
  263.                         -- Shift pointer to next match.
  264.                         tMatches.n = (tMatches.n)%#tMatches +1  -- Wrap around if the pointer has gone past the end.
  265.                        
  266.                         -- Clear the line if the new match is smaller than the previous.
  267.                         if string.len(tMatches[tMatches.n]) < nLastMatchSize then redraw(" ") end
  268.                         -- Assemble the new line.
  269.                         sLine = string.sub( sLine, 1, nPos - nLastMatchSize ) .. tMatches[tMatches.n]..string.sub( sLine, nPos+1)
  270.                         nPos = nPos-nLastMatchSize+tMatches[tMatches.n]:len()
  271.                         redraw()
  272.                     else
  273.                         -- [[ No matches yet, look for some now. ]]
  274.                         tMatches,len=get_matches(get_to_match(sLine,nPos))
  275.                         tMatches.n=0
  276.                         -- Show first match.
  277.                         if #tMatches > 0 then
  278.                             tMatches.n=1
  279.                             local nLastMatchSize = len
  280.                             sLine = string.sub( sLine, 1, nPos - nLastMatchSize ) .. tMatches[tMatches.n]..string.sub( sLine, nPos+1)
  281.                             nPos = nPos-nLastMatchSize+tMatches[tMatches.n]:len()
  282.                             redraw()
  283.                         end
  284.                         if #tMatches==1 then reset_matches() end
  285.                     end
  286.                 elseif param==keys.rightShift then
  287.                     count=(count+1)%2
  288.                     if count==0 then
  289.                         mode=not mode
  290.                     end
  291.                     reset_matches()
  292.                 elseif param == keys.left then
  293.                         -- Left
  294.                         if nPos > 0 then
  295.                                 nPos = nPos - 1
  296.                                 redraw()
  297.                         end
  298.                        
  299.                 elseif param == keys.right then
  300.                         -- Right                              
  301.                         if nPos < string.len(sLine) then
  302.                                 nPos = nPos + 1
  303.                                 redraw()
  304.                         end
  305.                
  306.                 elseif param == keys.up or param == keys.down then
  307.                     -- Up or down
  308.                     if _tHistory then
  309.                         redraw(" ");
  310.                         if param == keys.up then
  311.                                 -- Up
  312.                                 if nHistoryPos == nil then
  313.                                         if #_tHistory > 0 then
  314.                                                 nHistoryPos = #_tHistory
  315.                                         end
  316.                                 elseif nHistoryPos > 1 then
  317.                                         nHistoryPos = nHistoryPos - 1
  318.                                 end
  319.                         else
  320.                                 -- Down
  321.                                 if nHistoryPos == #_tHistory then
  322.                                         nHistoryPos = nil
  323.                                 elseif nHistoryPos ~= nil then
  324.                                         nHistoryPos = nHistoryPos + 1
  325.                                 end                                            
  326.                         end
  327.                                            
  328.                         if nHistoryPos then
  329.                             sLine = _tHistory[nHistoryPos]
  330.                             nPos = string.len( sLine )
  331.                         else
  332.                             sLine = ""
  333.                             nPos = 0
  334.                         end
  335.                         redraw()
  336.                     end
  337.                 elseif param == keys.backspace then
  338.                         -- Backspace
  339.                         if nPos > 0 then
  340.                             redraw(" ");
  341.                             sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
  342.                             nPos = nPos - 1                                
  343.                             redraw()
  344.                         end
  345.                 elseif param == keys.home then
  346.                         -- Home
  347.                         nPos = 0
  348.                         redraw()              
  349.                 elseif param == keys.delete then
  350.                         if nPos < string.len(sLine) then
  351.                             redraw(" ");
  352.                             sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 )                          
  353.                             redraw()
  354.                         end
  355.                 elseif param == keys["end"] then
  356.                         -- End
  357.                         nPos = string.len(sLine)
  358.                         redraw()
  359.                 end
  360.             end
  361.     end
  362.  
  363.     term.setCursorBlink( false )
  364.     term.setCursorPos( w + 1, sy )
  365.     print()
  366.     return sLine
  367. end
  368. local function test()
  369.     a=setmetatable({},{__index={b=1,c=function()end},__newindex={},__tostring=function() return'aaa' end})
  370.     repeat
  371.         read(nil,nil,getfenv(),false)
  372.         print()
  373.     until false
  374. end
  375. return read
  376. -- test()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement