This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Espen's profile picture

[CC 1.45+] Shell Tab-Completion v2.2

Started by Espen, 10 November 2012 - 10:01 AM
Espen #1
Posted 10 November 2012 - 11:01 AM
Description:
This is a modification of the original shell program with added TAB-completion functionality.
There's also an alternative download which injects the modified read() function into the already running shell.



Details:
SpoilerIf there is more than one match it will fill up as much as it can on first TAB.
If you TAB again, it'll show a list of all possible matches.
I also added the "–More–" functionality if there are more than [screen-height] matches.

Keys for the "More" functionality are:
  • ENTER - Scroll a line
  • SPACE - Scroll a page
  • BACKSPACE - Cancel

Known Limitations:
  • TAB completion doesn't work inbetween arguments, but only on the last.
  • There's only list-view (instead of the separate column-view for small numbers of completions).

Installation:
SpoilerInstallation for all computers:
  1. Download the "Shell file".
  2. Save the downloaded file as "shell" into the following folder (create folders where necessary):
  3. .minecraft/mods/ComputerCraft/lua/rom/programs
  4. Reboot your CC computer(s).
  5. Done!

Installation for individual computers:
  • Variant: Read-Override
    1. Download the "Read-Override file".
    2. Save the downloaded file on a CC computer.
    3. Run it (needs to be done on every startup)
    4. Done!
  • Variant: Shell file
    1. Download the "Shell file".
    2. Save the downloaded file on a CC computer.
    3. Run it (will start a new shell on top of the old one)
    4. Done!
    5. (Type "exit" to get back to the old shell.)


Download:
Pastebin: Shell file ( to start a new shell or to replace shell for all computers )
Pastebin: Read-Override file ( runtime override of read() )


Changelog:
Spoiler
  • v2.2
  • Fixed: Entering a folder name completely and then pressing TAB didn't append a slash.
  • v2.1
  • Added: Directory-Matches now have slashes appended to them. Changed: COMPLETION_QUERY_ITEMS was hardcoded to 19, is now height of respective terminal. Fixed: Pressing SPACE for listing multiple pages of matches "blanked out" the screen. Fixed: TAB completion was engaged even if cursor was not at the end of the line. Fixed: Path-resolving for directory-detection wasn't quite right.
  • v2.0 - Changed the previously mixed Windows<->Linux TAB completion behaviour completely to Linux-style (except for column-display).
  • Added: Display of all possible matches. Added: MORE-functionality for displaying possibilities greater than a certain number ( COMPLETION_QUERY_ITEMS )
  • v1.2
  • Added: a separate program to to allow runtime-injection of the custom read() into the running shell of an individual computer. Thanks @BigSHinyToys for making me aware of the global table's dropped protection. ^_^/>
  • v1.12 - Fixed: Pressing Tab without entering anything threw an error.
  • v1.11 - Removed previously introduced, but now unnecessary parameter from read()
  • v1.1 - Path traversal implemented
  • v1.0 - Initial release
Edited on 16 May 2013 - 02:45 PM
Sammich Lord #2
Posted 10 November 2012 - 11:12 AM
I love it! Can you upload just the new read function to pastebin? I would like to see how you did it.
Espen #3
Posted 10 November 2012 - 11:17 AM
I simply inserted the modified read() function into the shell program, so you can easily extract it from that.
But sure, I can upload it separately, why not. Just give me a sec…

EDIT: There you go -> http://pastebin.com/gTRF9NBN
[strike]Oh shoot, that was an earlier, incomplete version, just a sec…[/strike]
Ok, correct version is up, same link. Have fun :unsure:/>/>

EDIT: v1.2 added some execution code at the end of the file.
So if you want to get the read() function only, simply remove the following from the end of the file:
_G.read = read
Edited on 12 November 2012 - 05:10 AM
Sammich Lord #4
Posted 10 November 2012 - 12:00 PM
I simply inserted the modified read() function into the shell program, so you can easily extract it from that.
But sure, I can upload it separately, why not. Just give me a sec…

EDIT: There you go -> http://pastebin.com/gTRF9NBN
Oh shoot, that was an earlier, incomplete version, just a sec…
Ok, correct version is up, same link. Have fun :unsure:/>/>
Thanks man!
gknova61 #5
Posted 10 November 2012 - 04:26 PM
Never cease to amaze. But, one question. Would it be possible to install a modified shell onto just 1 computer? Like if you're playing on a server with no FTP access :unsure:/>/>
Espen #6
Posted 10 November 2012 - 04:47 PM
Hmm, you could theoretically call your custom shell via startup.
But then you'd basically have two shells running, i.e. when you type exit, the last run shell ends and you'll be back in the first one.

Modifying read() at runtime would also work, but only for any program that is executed AFTER you did that.
And since the shell is already running when you made that change, it will not have any effect on it.

Maybe there is some Lua magic that can change the running shell's read() during runtime, I would have to investigate and experiment with this myself.
But at the moment, off the top of my head, nothing comes to mind I'm afraid. :unsure:/>/>
Sammich Lord #7
Posted 11 November 2012 - 10:15 AM
Mind if I use the modified shell in a new "OS"(I called it that due to a lack of better terms)? I will give full credit to you.(I am not a douche like that)
Espen #8
Posted 11 November 2012 - 11:16 AM
Sure, no problem, go right ahead. Why else would I share it? :unsure:/>/>
You can mention me if you want, but it's not required.
Nice of you to ask though, appreciate the gesture.
Cruor #9
Posted 11 November 2012 - 12:49 PM
You would share it just to get mad at people that wants to include it in stuff!

Nice work bro :3 looks fancy.
Espen #10
Posted 11 November 2012 - 01:16 PM
You would share it just to get mad at people that wants to include it in stuff!

Nice work bro :3 looks fancy.
Thanks, learned a bit regex while coding this, was well worth the time.^^
I've also just updated the links to v1.12 which fixes an issue where hitting TAB without having entered anything beforehand would throw an error.
BigSHinyToys #11
Posted 11 November 2012 - 10:11 PM
@Espen

This section of code shuts down the shell and allows custom code to run from the BIOS it does this by injecting your code into a function printError then terminating the shell and printError is run running your code. Not very clean but a possible solution to running this on only one machine.

_G.printError = function()
    myFunction = function()
	    _G.printError = function( ... )
		    if term.isColour() then
			    term.setTextColour( colours.red )
		    end
		    print( ... )
		    term.setTextColour( colours.white )
	    end
	    -- start User Program
	    term.clear()
	    term.setCursorPos(1,1)
	    term.write("BootlegOS")
	    while true do
	    local event = {os.pullEvent()}
		    if event[1] == "key" and event[2] == 28 then
			    break
		    end
	    end
	    os.run({},"rom/user/selectFile3.lua")
	    -- end user program
    end
    return myFunction()
end
os.queueEvent("terminate")
error()
Cruor #12
Posted 11 November 2012 - 11:57 PM
You would share it just to get mad at people that wants to include it in stuff!

Nice work bro :3 looks fancy.
Thanks, learned a bit regex while coding this, was well worth the time.^^
I've also just updated the links to v1.12 which fixes an issue where hitting TAB without having entered anything beforehand would throw an error.
Heh, same thing i did when i added "" support to shell :unsure:/>/> was just to learn them patterns XD i have no need for the program myself xd
Espen #13
Posted 12 November 2012 - 02:46 AM
@BigSHinyToys:
Very nice idea, I tip my hat.^^
That's a clever injection, and it's also not permanent. I like it.
Thanks for sharing. :unsure:/>/>

Only one minor suggestion to the code:
Instead of retyping the original printError you could back it up to another variable and restore it from that, like so:
Spoiler
-- Backup printError
local printErrorNative = _G.printError

_G.printError = function()
    myFunction = function()
            -- Restore native printError
            _G.printError = printErrorNative
            -- start User Program
            term.clear()
            term.setCursorPos(1,1)
            term.write("BootlegOS")
            while true do
            local event = {os.pullEvent()}
                    if event[1] == "key" and event[2] == 28 then
                            break
                    end
            end
            print()
            os.run({},"rom/user/selectFile3.lua")
            -- end user program
    end
    return myFunction()
end
os.queueEvent("terminate")
error()
But I guess you already knew this and just didn't think about it at the time. We're only human, right?^^
BigSHinyToys #14
Posted 12 November 2012 - 03:21 AM
@BigSHinyToys:
Very nice idea, I tip my hat.^^
That's a clever injection, and it's also not permanent. I like it.
Thanks for sharing. :unsure:/>/>

Only one minor suggestion to the code:
Instead of retyping the original printError you could back it up to another variable and restore it from that, like so:
Spoiler
-- Backup printError
local printErrorNative = _G.printError

_G.printError = function()
	myFunction = function()
			-- Restore native printError
			_G.printError = printErrorNative
			-- start User Program
			term.clear()
			term.setCursorPos(1,1)
			term.write("BootlegOS")
			while true do
			local event = {os.pullEvent()}
					if event[1] == "key" and event[2] == 28 then
							break
					end
			end
			print()
			os.run({},"rom/user/selectFile3.lua")
			-- end user program
	end
	return myFunction()
end
os.queueEvent("terminate")
error()
But I guess you already knew this and just didn't think about it at the time. We're only human, right?^^
I don't know if storing it a local would work as the code is run at a higher level and lua is lexically scoped I just re included it as a simplification I could be wrong about that.

In reply to you PM I have no problem with you including the code or modified variant and instructions in the OP
Espen #15
Posted 12 November 2012 - 03:54 AM
@BigSHinyToys:
Very nice idea, I tip my hat.^^
That's a clever injection, and it's also not permanent. I like it.
Thanks for sharing. ^_^/>/>

Only one minor suggestion to the code:
Instead of retyping the original printError you could back it up to another variable and restore it from that, like so:
Spoiler
-- Backup printError
local printErrorNative = _G.printError

_G.printError = function()
	myFunction = function()
			-- Restore native printError
			_G.printError = printErrorNative
			-- start User Program
			term.clear()
			term.setCursorPos(1,1)
			term.write("BootlegOS")
			while true do
			local event = {os.pullEvent()}
					if event[1] == "key" and event[2] == 28 then
							break
					end
			end
			print()
			os.run({},"rom/user/selectFile3.lua")
			-- end user program
	end
	return myFunction()
end
os.queueEvent("terminate")
error()
But I guess you already knew this and just didn't think about it at the time. We're only human, right?^^
I don't know if storing it a local would work as the code is run at a higher level and lua is lexically scoped I just re included it as a simplification I could be wrong about that.

In reply to you PM I have no problem with you including the code or modified variant and instructions in the OP
Nah it works fine, just tested it. I guess it really just cares about the reference, regardless if it comes from a local variable or not.
And thanks for the permission. I think I will strip the injection code down to its barest minimum for the OP after having played around with it a bit, something like:
local printErrorNative = _G.printError

function _G.printError()
	_G.printError = printErrorNative
	os.run( {}, tabShellPath )
end

os.queueEvent("terminate")
error()

I must say, I never really thought about touching any global variables as they were protected in the past and you always got "Attempt to write to global".
But looking at bios.lua now, the protection is gone!? Since what version did that change? :unsure:/>/>
BigSHinyToys #16
Posted 12 November 2012 - 04:21 AM
It has always been possible to over write a function the most common is

os.pullEvent = os.pullEventRaw
I think this would still work with older versions as it only overwrites not creating a new global.
trying to keep stack level is the reason I tail called in the new function btw. I don't now when the _G protection was removed my guess would be when dan200 / Cloudy were trying to add state save on game exit and load on starting and needed to serialize the hole _G leading to BIOS rewrite and loss of _G protection.
Espen #17
Posted 12 November 2012 - 04:42 AM
Ok, at the beginning I was always creating a local os.pullEvent without overwriting the global one.
I can remember though that a while after that I changed to backing up the global one and then actually overwriting it and then restore it when the program ended.
But somehow it didn't register in my mind, that I had just changed the way I was doing this and that I was actually changing the global table. *duh*

Now that this has fully entered my noggin, I just come to think that I don't really need to inject the tab shell in its entirety.
I can now just simply inject the modified read() function directly into the already running shell.
No protection anymore… well that just made things a lot easier. I like it. :unsure:/>/>
KaoS #18
Posted 01 December 2012 - 03:59 AM
Ok, at the beginning I was always creating a local os.pullEvent without overwriting the global one.

how did you do that :huh:/> you cannot make local table entries and os is a table. did you make a fake os or something, a lot of trouble to go through when you can just
rawset(os,pullEvent,os.pullEventRaw)
Espen #19
Posted 01 December 2012 - 04:51 AM
Ok, at the beginning I was always creating a local os.pullEvent without overwriting the global one.
how did you do that :huh:/> you cannot make local table entries and os is a table. did you make a fake os or something, a lot of trouble to go through when you can just
rawset(os,pullEvent,os.pullEventRaw)
No I meant something like this:
local rawPull = os.pullEventRaw
local os = {}
os.pullEvent = rawPull
That way I didn't touch any global variable at all.
The idea was that you could put these three lines on top of every arbitrary program that already made use of os.pullEvent without having to change the code AND guaranteeing that the native os.pullEvent remains untouched even while the program is running.
At the time I was thinking about parallel running programs and that I wanted to preserve os.pullEvent no matter what.

That was a bit over the top though, so later I just resorted to use os.pullEventRaw directly instead of os.pullEvent
Still didn't touch global, but without the hassle.

Nowadays I just backup os.pullEvent, overwrite it, and then restore it after the program ends.
But in case I'd want to preserve the native function at all times (b/c of parallel programs), then I'd probably still use os.pullEventRaw and pcall() every native function that makes use of os.pullEvent. Here's a thread way back from february where I delved into the latter two: [topic='152']Prevent Program Termination (Ctrl+T)[/topic]
Or something like that. ^_^/>
KaoS #20
Posted 01 December 2012 - 05:13 AM
No I meant something like this:
local rawPull = os.pullEventRaw
local os = {}
os.pullEvent = rawPull
that was what I meant by create a fake os :)/> I assume you would actually use mt to access other os functions like so


local oldOS=os
local os=setmetatable({pullEvent=oldOS.pullEventRaw},{__index=oldOS})
Espen #21
Posted 01 December 2012 - 06:07 AM
Ah sorry, didn't read you correctly then. My bad. :)/>
Ballistic Buddha #22
Posted 22 December 2012 - 08:05 AM
Excellent work, this is a godsend! I really can't imagine using the in-game terminal without this now.

However, I do have one suggestion. Is it possible you could add the ability to double-tap tab to display all possibilities for tab-completion, perhaps with a user prompt asking if you want to display all possibilities if there are too many to display on the terminal without scrolling?

If you don't want to add this, that's fine, and I'll probably take a stab at implementing it myself, but I figured I might as well ask.
Espen #23
Posted 22 December 2012 - 09:03 AM
@Ballistic Buddha:
Glad to hear it's of use to you. :)/>

Adding the functionality you suggested is definitely a possibility and shouldn't be too complicated (I guess).
I like the idea, will probably take a look at it in a few days (christmas time = busy time *sigh*), but if you want to give it a shot, be my guest.^^
apottere #24
Posted 02 March 2013 - 09:32 PM
Hey, I finally got fed up with the default shell and went looking for something like this. So far it looks pretty sweet, I just had a few questions/comments:

1. How difficult would it be to append a "/" to the end of a directory name that's completed? This would make it more enjoyable, although not necessary.

2. Have you looked into Ballistic Buddha's suggestion yet? Also some more sugar I would appreciate in my shell.

3. With three directories in a folder: foo, foobar, fooness, your autocomplete completes fooness -> foo -> foobar. This is a little wacky (as far as most shell completion goes), would this be a simple fix?

Let me know if/when you'll look into this, I know how pet project timelines go. Thanks!
Espen #25
Posted 02 March 2013 - 11:47 PM
@apottere:
1. Adding a slash to the end of a directory name should be possible. That would just need a simple fs.isDir() check in the appropriate place.

2. Not yet, sorry. For reasons see below.

3. It goes through the filelist as fs.list() provides it.
So it would show the directories in the order that e.g. the command "ls" or "dir" should show them.
I could, theoretically, sort the table that fs.list() returns alphabetically.
That would create a little overhead in processing though, the length of which depends on the amount of files and folders in a given directory.
But then again I don't think the average CC user has hundreds or thousands of files/folders in a folder, so that shouldn't be that big of a problem really.

RL-Stuff for the interested:
SpoilerUnfortunately at the moment I don't have the time to tinker around with any hobby-related projects except for answering questions.
Since december my free time has been diminishing more and more because I had to prepare for exams, two of which I've yet to write.
And after that I'm not sure exactly when I'll be getting some free time, as then the new semester begins which involves a lot of organizational stuff and making sure everything is set for the new semester.
After the first of my two remaining exams though I'll take a look at the program.
That would be the weekend after March 15th. I've just marked it on my calendar, so that's a promise! :)/>

Thanks for the feedback, much appreciated. ^^
Edited on 02 March 2013 - 10:48 PM
apottere #26
Posted 03 March 2013 - 08:15 AM
Thanks for the quick response. As for 3, it would make it a lot more intuitive (usually you expect shorter names to be completed first). As for 2, I completely understand, take your time and focus on school. Let me know when you get around to implementing some of this, computercraft desperately needs some better shell completion, and its surprisingly lacking.
Espen #27
Posted 03 March 2013 - 09:26 AM
Will do. I never saw this as finished and had a few ideas left the last time I released, so I'll definitely return to it as soon as I can.
And it's university, not school. ^_^/>
I'm from germany though so I'm not entirely sure if "school" actually means the same in colloquial conversations (in the context of exams).
It's just that in germany "school" only covers everything up to "secondary school" / "highschool", but in english conversations I've seldom heard people speak of "university", even though you'd expect that given the age of the participants involved.
apottere #28
Posted 03 March 2013 - 11:31 AM
Yeah, here we mostly just refer to college/university as school, so I was referring to university previously.
elexx #29
Posted 19 March 2013 - 04:01 AM
Really love this shell extension, great work!
Espen #30
Posted 26 March 2013 - 11:02 PM
@apottere:
Unfortunately I'm not going to have much time to improve the program at the moment.
I have 3 group projects this semester and some personal stuff, all of which swallow my time like a vacuum.
That means my plate is full to the brim and I can't work on any hobby-projects. :(/>

But I invite other people to improve on any of my programs if they want to.
No permission necessary. :)/>

Really love this shell extension, great work!
Glad you like it, much appreciated. ^^
electrodude512 #31
Posted 08 May 2013 - 12:22 PM
I made a version that puts a / at the end if it's a directory. It's the injection version. I didn't test it a lot but it seems to work.

http://pastebin.com/d05EZHVz

EDIT: I found a very bad bug, you have to put in the entire path to run a program, don't use this yet…

EDIT2: it's not my bug, the original has this same problem…


EDIT3: no, I just herped up my CC-emu, my version should work for you if the original does.
Espen #32
Posted 11 May 2013 - 03:07 PM
Updated to v2.0
The way TAB completions work was completely overhauled.
Before it was really more like in the Windows command prompt, whereas now it's more like Linux TAB completion behaves.

This means if there is more than one match it will fill up as much as it can on first TAB.
If you TAB again, it'll show a list of all possible matches.
I also added the "–More–" functionality if there are more than [screen-height] matches.

Keys for the "More" functionality are:
  • ENTER - Scroll a line
  • SPACE - Scroll a page
  • BACKSPACE - Cancel

Known Limitations:
  • TAB completion doesn't work inbetween arguments, but only on the last.
  • There's only list-view (instead of the separate column-view for small numbers of completions).

Screenshots:
Spoiler
Edited on 13 May 2013 - 06:07 AM
electrodude512 #33
Posted 15 May 2013 - 07:37 PM
Yay! Real unix-type tab completions! Can you make it put / at the end if it's a folder? I tried this but it didn't completely work:

http://pastebin.com/d05EZHVz

My changes are on lines 258-260

This is the same thing I did in reply #31. If you type "/ro<tab>" it will print "/rom/" but "/rom<tab>" will leave the prompt at "/rom" and won't put the final "/"
Espen #34
Posted 16 May 2013 - 05:29 AM
@electrodude512:
Hehe, I actually had the / code in my working copy for a long time, but didn't release because I already decided to completely overhaul the program for v2.0
And then I simply forgot about putting it in there again, sorry. :\

I'll try to put it in again, but I'll first take a look at how Linux handles it exactly for each case (folder and file matches mixed, only folder matches, only one folder match, etc.).
Espen #35
Posted 16 May 2013 - 10:59 AM
Updated to v2.1
Slashes are now appended to directories and I also fixed some bugs I only noticed during development for v2.1

Detailed Changelog for this version:
  • v2.1
  • Added: Directory-Matches now have slashes appended to them. Changed: COMPLETION_QUERY_ITEMS was hardcoded to 19, is now height of respective terminal. Fixed: Pressing SPACE for listing multiple pages of matches "blanked out" the screen. Fixed: TAB completion was engaged even if cursor was not at the end of the line. Fixed: Path-resolving for directory-detection wasn't quite right.

Enjoy! :)/>
electrodude512 #36
Posted 16 May 2013 - 01:23 PM
Thanks!
Espen #37
Posted 16 May 2013 - 04:47 PM
Thanks!

You're welcome, glad it's of use for someone. ^_^/>
I've uploaded a new update since the last time:
  • v2.2
  • Fixed: Entering a folder name completely and then pressing TAB didn't append a slash.
Ballistic Buddha #38
Posted 04 December 2013 - 07:06 PM
For those of you who still use this. I've made a resource pack that contains only this program as a shell replacement.

It can be downloaded here: https://dl.dropboxusercontent.com/u/20588547/Resource%20Packs/TabComplete.zip