View difference between Paste ID: Uu4KHjwz and f8zycUS6
SHOW: | | - or go back to the newest paste.
1-
--# Shell Utility Extended v2.0 - Program to extend/modify Computercraft autocompletion system.
1+
--# Shell Utility Extended v2.1 - Program to extend/modify Computercraft autocompletion system.
2
--# Made By Wojbie
3
--# http://pastebin.com/f8zycUS6
4
5-
--   Copyright (c) 2016-2021 Wojbie (wojbie@wojbie.net)
5+
--   Copyright (c) 2017-2021 Wojbie (wojbie@wojbie.net)
6
--   Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the disclaimer below) provided that the following conditions are met:
7
--   1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8
--   2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
--   3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
--   4. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
11
--   5. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
12
--   NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. YOU ACKNOWLEDGE THAT THIS SOFTWARE IS NOT DESIGNED, LICENSED OR INTENDED FOR USE IN THE DESIGN, CONSTRUCTION, OPERATION OR MAINTENANCE OF ANY NUCLEAR FACILITY.
13
14-
--# New settings system usage functions. Allows auto correction of common errors.
14+
--# New settings system useage functions. Allows autocorection of common errors.
15
local defaultSettings = {
16
["shellUtil.use_ghost"] = true,
17
["shellUtil.stop_dofile"] = true,
18
["shellUtil.chat_names"] = "",
19
["shellUtil.pastebin_names"] = "",
20
}
21
22
--Restore cleared settings
23
for i,k in pairs(defaultSettings) do
24
	if settings.get(i) == nil then
25
		settings.set(i,k)
26
	end
27
end
28
29
local validSettingsTypes = {
30
["shellUtil.use_ghost"] = {["boolean"]=true},
31
["shellUtil.stop_dofile"] = {["boolean"]=true},
32
["shellUtil.chat_names"] = {["string"]=true},
33
["shellUtil.pastebin_names"] = {["string"]=true},
34
}
35
36
local validSettingsTest = {
37
}
38
39
local getSetting = function(A)
40
	local data = settings.get(A)
41
	if	(not validSettingsTypes[A] or validSettingsTypes[A][type(data)]) and --See if setting type matches (when defined).
42
		(not validSettingsTest[A] or validSettingsTest[A](data)) --See if testing function agrees (when defined).
43
	then --All tests OK
44
		return data
45
	else --Any of test Failed. Reset to default setting.
46
		data = defaultSettings[A]
47
		settings.set(A,data)
48
		return data
49
	end
50
end
51
52
--# Basic file operators.
53
local function append(A,B) local file = fs.open(tostring(A),"a") if not file then return false end file.write(B) file.close() return true end
54
55
local function save(A,B) local file = fs.open(tostring(A),"w") if not file then return false end file.write(B) file.close() return true end
56
local function saveT(A,B) return save(A,textutils.serialize(B)) end
57
local function saveTL(A,B) return save(A,string.gsub(textutils.serialize(B),"\n%s*","")) end
58
local function saveLines(A,B) local file = fs.open(tostring(A),"w") if not file then return false end for i=1,#B,1 do file.writeLine(B[i]) end file.close() return true end
59
local function saveBin(A,B) local file = fs.open(tostring(A),"wb") if not file then return false end for i=1,#B,1 do file.write(B[i]) end file.close() return true end
60
local function saveDump(A,B) return saveBin(A,{string.byte(B,1,#B)}) end
61
62
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
63
local function getT(A) local data = get(A) if data then data = textutils.unserialize(data) end if data then return data end end
64
local function getLines(A) local file = fs.open(tostring(A),"r") if not file then return false end local data = {} for k in file.readLine do data[#data+1] = k end file.close() return data end
65
local function getBin(A) local file = fs.open(tostring(A),"rb") if not file then return false end local data = {} local b = file.read() while b do table.insert(data,b) b = file.read() end file.close() if data then return data end end
66
local function getDump(A) local file = getBin(A) if not file then return false end local data = string.char(table.unpack(file)) if data then return data end end
67
local function getHttp(A,B,C) if not http.checkURL(A) then return false end	local file = B and http.post(A,B,C) or http.get(A,C) if not file then return false end local data = file.readAll() file.close() if data then return data end end
68
69
local function makeLog(A) local file = fs.open(tostring(A),"a") if not file then return false end local on = true return function(m) if not on then return false end file.writeLine(m) file.flush() return true end,function() on = false file.close() end end
70
local function makePrintLog(A) local logfile,logstop = makeLog(A) if not logfile then return false end return function(m) print(m) logfile(m) end,logstop,logfile end
71
72
--# Useafull functions
73
74
local function completeMultipleChoice( sText, tOptions, bAddSpaces, tOptionsGhosts ) --Copy of function cause its usefull
75
    local tResults = {}
76
	local tGhosts = {}
77
    for n=1,#tOptions do
78
        local sOption = tOptions[n]
79
        if #sOption + (bAddSpaces and 1 or 0) > #sText and string.sub( sOption, 1, #sText ) == sText then
80
            local sResult = string.sub( sOption, #sText + 1 )
81
            if bAddSpaces then
82
                table.insert( tResults, sResult .. " " )
83
            else
84
                table.insert( tResults, sResult )
85
            end
86
			if tOptionsGhosts then
87
				if bAddSpaces then
88
					table.insert( tGhosts, tOptionsGhosts[n] or "")
89
				else
90
					table.insert( tGhosts, (tOptionsGhosts[n] and " "..tOptionsGhosts[n]) or "")
91
				end
92
				
93
			end
94
        end
95
    end
96
    return tResults,tGhosts
97
end
98
99
local function peripherallook(sType,fTest) --Fast way to make table of peripheral names.
100
	local tNames={}
101
	peripheral.find(sType,function(sName,tObject) if ( not fTest ) or fTest(sName,tObject) then table.insert(tNames,sName) end return false end)
102
	return tNames
103
end
104
105
106
local function hostnameslook(sProtocol,nTime) --Program to lookup hostnames that are in set sProtocol. nTime is time it will look. Defaults to 0,5.
107
    -- Build list of host IDs
108
    local tResults = {}
109
	local close=false
110
	
111
    if not rednet.isOpen() then
112
		for i,k in pairs(rs.getSides()) do
113
			if peripheral.getType( k ) == "modem" then
114
				rednet.open(k)
115
				close=k
116
				break
117
			end
118
		end
119
		if not close then return tResults end
120
    end
121
122
    -- Broadcast a lookup packet
123
    rednet.broadcast( {
124
        sType = "lookup",
125
        sProtocol = sProtocol,
126
        sHostname = sHostname,
127
    }, "dns" )
128
129
    -- Start a timer
130
    local timer = os.startTimer( nTime or 0.5 )
131
132
    -- Wait for events
133
    while true do
134
        local event, p1, p2, p3 = os.pullEvent()
135
        if event == "rednet_message" then
136
            -- Got a rednet message, check if it's the response to our request
137
            local nSenderID, tMessage, sMessageProtocol = p1, p2, p3
138
            if sMessageProtocol == "dns" and tMessage.sType == "lookup response" then
139
                if tMessage.sProtocol == sProtocol then
140
                        table.insert( tResults, tMessage.sHostname )
141
                end
142
            end
143
        else
144
            -- Got a timer event, check it's the end of our timeout
145
            if p1 == timer then
146
                break
147
            end
148
        end
149
    end
150
151
	if close then
152
		rednet.close(close)
153
	end
154
	
155
    return tResults
156
end
157
158
--## Main Program Parts ##--
159
160
--# Disable autocompletition of rarely used parts/phrases that mess with the lives like xpcall and dofile
161
162
if getSetting("shellUtil.stop_dofile") and textutils.complete("do")[1] == "file(" then --Tests if not disabled already to not cause chaining effect.
163
164
	local limited = {["do"]="file(",["x"]="pcall("}
165
	
166
	local textutils_complete = textutils.complete
167
	textutils.complete = function(sName,tEnv)
168
		if not limited[sName] then
169
			return textutils_complete(sName,tEnv)
170
		else
171
			local ret = textutils_complete(sName,tEnv)
172
			for i=#ret,1,-1 do
173
				if ret[i] == limited[sName] then table.remove(ret,i) end
174
			end
175
			return ret
176
		end
177
	end
178
	
179
end
180
181
--# Read overwrite to add ghosting and change to few vanilla auto-completitions to add ghost capabilities.
182
183
if getSetting("shellUtil.use_ghost") then --This setting is only tested once at moment program is run.
184
185
	-- Overwriting read with one that supports 2nd Ghost table.
186
	function _G.read( _sReplaceChar, _tHistory, _fnComplete )
187
		term.setCursorBlink( true )
188
189
		local sLine = ""
190
		local nHistoryPos
191
		local nPos = 0
192
		if _sReplaceChar then
193
			_sReplaceChar = string.sub( _sReplaceChar, 1, 1 )
194
		end
195
196
		local tCompletions
197
		local nCompletion
198
		local tGhosts --#
199
		local function recomplete()
200
			if _fnComplete and nPos == string.len(sLine) then
201
				tCompletions,tGhosts = _fnComplete( sLine ) --#
202
				if tCompletions and #tCompletions > 0 then
203
					nCompletion = 1
204
					tGhosts = tGhosts or {} --#
205
				else
206
					nCompletion = nil
207
				end
208
			else
209
				tCompletions = nil
210
				nCompletion = nil
211
			end
212
		end
213
214
		local function uncomplete()
215
			tCompletions = nil
216
			nCompletion = nil
217
			tGhosts = nil --#
218
		end
219
220
		local w = term.getSize()
221
		local sx = term.getCursorPos()
222
223
		local function redraw( _bClear )
224
			local nScroll = 0
225
			if sx + nPos >= w then
226
				nScroll = (sx + nPos) - w
227
			end
228
229
			local cx,cy = term.getCursorPos()
230
			term.setCursorPos( sx, cy )
231
			local sReplace = (_bClear and " ") or _sReplaceChar
232
			if sReplace then
233
				term.write( string.rep( sReplace, math.max( string.len(sLine) - nScroll, 0 ) ) )
234
			else
235
				term.write( string.sub( sLine, nScroll + 1 ) )
236
			end
237
238
			if nCompletion then
239
				local sCompletion = tCompletions[ nCompletion ]
240
				local sGhost = tGhosts[ nCompletion ] --#
241
				local oldText, oldBg
242
				if not _bClear then
243
					oldText = term.getTextColor()
244
					oldBg = term.getBackgroundColor()
245
					term.setTextColor( colors.white )
246
					term.setBackgroundColor( colors.gray )
247
				end
248
				if sReplace then
249
					term.write( string.rep( sReplace, string.len( sCompletion ) ) )
250
				else
251
					term.write( sCompletion )
252
				end
253
				--#
254
				if sGhost then
255
					if not _bClear then
256
						term.setTextColor( colors.lightGray )
257
						--term.setBackgroundColor( colors.gray )
258
					end
259
					if sReplace then
260
						term.write( string.rep( sReplace, string.len( sGhost ) ) )
261
					else
262
						term.write( sGhost )
263
					end
264
				end
265
				--#
266
				if not _bClear then
267
					term.setTextColor( oldText )
268
					term.setBackgroundColor( oldBg )
269
				end
270
			end
271
272
			term.setCursorPos( sx + nPos - nScroll, cy )
273
		end
274
		
275
		local function clear()
276
			redraw( true )
277
		end
278
279
		recomplete()
280
		redraw()
281
282
		local function acceptCompletion()
283
			if nCompletion then
284
				-- Clear
285
				clear()
286
287
				-- Find the common prefix of all the other suggestions which start with the same letter as the current one
288
				local sCompletion = tCompletions[ nCompletion ]
289
				sLine = sLine .. sCompletion
290
				nPos = string.len( sLine )
291
292
				-- Redraw
293
				recomplete()
294
				redraw()
295
			end
296
		end
297
		while true do
298
			local sEvent, param = os.pullEvent()
299
			if sEvent == "char" then
300
				-- Typed key
301
				clear()
302
				sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
303
				nPos = nPos + 1
304
				recomplete()
305
				redraw()
306
307
			elseif sEvent == "paste" then
308
				-- Pasted text
309
				clear()
310
				sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
311
				nPos = nPos + string.len( param )
312
				recomplete()
313
				redraw()
314
315
			elseif sEvent == "key" then
316
				if param == keys.enter then
317
					-- Enter
318
					if nCompletion then
319
						clear()
320
						uncomplete()
321
						redraw()
322
					end
323
					break
324
					
325
				elseif param == keys.left then
326
					-- Left
327
					if nPos > 0 then
328
						clear()
329
						nPos = nPos - 1
330
						recomplete()
331
						redraw()
332
					end
333
					
334
				elseif param == keys.right then
335
					-- Right                
336
					if nPos < string.len(sLine) then
337
						-- Move right
338
						clear()
339
						nPos = nPos + 1
340
						recomplete()
341
						redraw()
342
					else
343
						-- Accept autocomplete
344
						acceptCompletion()
345
					end
346
347
				elseif param == keys.up or param == keys.down then
348
					-- Up or down
349
					if nCompletion then
350
						-- Cycle completions
351
						clear()
352
						if param == keys.up then
353
							nCompletion = nCompletion - 1
354
							if nCompletion < 1 then
355
								nCompletion = #tCompletions
356
							end
357
						elseif param == keys.down then
358
							nCompletion = nCompletion + 1
359
							if nCompletion > #tCompletions then
360
								nCompletion = 1
361
							end
362
						end
363
						redraw()
364
365
					elseif _tHistory then
366
						-- Cycle history
367
						clear()
368
						if param == keys.up then
369
							-- Up
370
							if nHistoryPos == nil then
371
								if #_tHistory > 0 then
372
									nHistoryPos = #_tHistory
373
								end
374
							elseif nHistoryPos > 1 then
375
								nHistoryPos = nHistoryPos - 1
376
							end
377
						else
378
							-- Down
379
							if nHistoryPos == #_tHistory then
380
								nHistoryPos = nil
381
							elseif nHistoryPos ~= nil then
382
								nHistoryPos = nHistoryPos + 1
383
							end                        
384
						end
385
						if nHistoryPos then
386
							sLine = _tHistory[nHistoryPos]
387
							nPos = string.len( sLine ) 
388
						else
389
							sLine = ""
390
							nPos = 0
391
						end
392
						uncomplete()
393
						redraw()
394
395
					end
396
397
				elseif param == keys.backspace then
398
					-- Backspace
399
					if nPos > 0 then
400
						clear()
401
						sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
402
						nPos = nPos - 1
403
						recomplete()
404
						redraw()
405
					end
406
407
				elseif param == keys.home then
408
					-- Home
409
					if nPos > 0 then
410
						clear()
411
						nPos = 0
412
						recomplete()
413
						redraw()
414
					end
415
416
				elseif param == keys.delete then
417
					-- Delete
418
					if nPos < string.len(sLine) then
419
						clear()
420
						sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 )                
421
						recomplete()
422
						redraw()
423
					end
424
425
				elseif param == keys["end"] then
426
					-- End
427
					if nPos < string.len(sLine ) then
428
						clear()
429
						nPos = string.len(sLine)
430
						recomplete()
431
						redraw()
432
					end
433
434
				elseif param == keys.tab then
435
					-- Tab (accept autocomplete)
436
					acceptCompletion()
437
438
				end
439
440
			elseif sEvent == "term_resize" then
441
				-- Terminal resized
442
				w = term.getSize()
443
				redraw()
444
445
			end
446
		end
447
448
		local cx, cy = term.getCursorPos()
449
		term.setCursorBlink( false )
450
		term.setCursorPos( w + 1, cy )
451
		print()
452
		
453
		return sLine
454
	end
455
	
456
457
end
458
459
460
--#Generate Shell Completitions
461
462
-- /rom/programs/
463
-- Eject --List only disk drives. Ghost drive content type and label/songname.
464
local function completeEject( shell, nIndex, sText, tPreviousText )
465
    if nIndex == 1 then
466
		local tNames = peripherallook("drive")
467
		local tGhosts = {}
468
		for i=1,#tNames do
469
			local sName = tNames[i]
470
			if disk.hasData(sName) then
471
				tGhosts[i] = " Data "..(disk.getLabel(sName) or "")
472
			elseif disk.hasAudio(sName) then
473
				tGhosts[i] = " Music "..(disk.getAudioTitle(sName) or "")
474
			elseif disk.isPresent(sName) then
475
				tGhosts[i] = " Unknown"
476
			else 
477
				tGhosts[i] = " Empty"
478
			end
479
		end
480
		return completeMultipleChoice(sText,tNames,false,tGhosts)
481
	end
482
end
483
-- Gps -- Move order of options so locate is first.
484
local tGPSOptions = {"locate" , "host", "host "}
485
local function completeGPS( shell, nIndex, sText, tPreviousText )
486
    if nIndex == 1 then
487
        return completeMultipleChoice( sText, tGPSOptions )
488
    end
489
end
490
-- Label -- List only disk drives.
491
local tLabelOptions = { "get", "get ", "set ", "clear", "clear " }
492
local function completeLabel( shell, nIndex, sText, tPreviousText )
493
    if nIndex == 1 then
494
        return completeMultipleChoice( sText, tLabelOptions )
495
    elseif nIndex == 2 then
496
        return completeMultipleChoice(sText,peripherallook("drive"))
497
    end
498
end
499
-- Monitor -- List only monitors. Ghost current size of selected monitor.
500
local function completeMonitor( shell, nIndex, sText, tPreviousText )
501
    if nIndex == 1 then
502
		local tNames = peripherallook("monitor")
503
		local tGhosts = {}
504
		for i=1,#tNames do
505
			local x,y = peripheral.call(tNames[i],"getSize")
506
			tGhosts[i]= x.."x"..y
507
		end
508
        return completeMultipleChoice(sText,peripherallook("monitor"), true ,tGhosts)
509
    elseif nIndex == 2 then
510
        return shell.completeProgram( sText )
511
    end
512
end
513
-- Set -- Ghost the current setting (if table say [table])
514
local function completeSet( shell, nIndex, sText, tPreviousText )
515
    if nIndex == 1 then
516
		local tNames = settings.getNames()
517
		local tGhosts = {}
518
		for i=1,#tNames do
519
			local data = settings.get(tNames[i])
520
			tGhosts[i] = type(data) == "table" and "Table Detected" or tostring(data)
521
		end
522
        return completeMultipleChoice( sText, tNames, true , tGhosts)
523
    end
524
end
525
526
--/rom/programs/fun
527
-- DJ -- List only disk drives with music. Ghosts dong names.
528
local tDJOptions = { "play", "play ", "stop" }
529
local function Audiotest(sName,tObject)
530
	return tObject.hasAudio()
531
end
532
local function completeDJ( shell, nIndex, sText, tPreviousText )
533
    if nIndex == 1 then
534
        return completeMultipleChoice( sText, tDJOptions )
535
    elseif nIndex == 2 and tPreviousText[2] == "play" then
536
		local tNames = peripherallook("drive",Audiotest)
537
		local tGhosts = {}
538
		for i=1,#tNames do
539
			tGhosts[i] = disk.getAudioTitle(tNames[i])
540
		end
541
		return completeMultipleChoice(sText,tNames,false,tGhosts)
542
    end
543
end
544
545
--rom/programs/http/
546
--Pastebin -- List pastes from "shellUtil.pastebin_names" user(s), Ghost paste names. Suggest File Names based of pasteName?
547
548-
--get website http://pastebin.com/u/..name
548+
--get website https://pastebin.com/u/..name
549
--<td><img src="/i/t.gif" class="i_p0" title="Public paste, anybody can see this paste." alt="" /> <a href="/DW3LCC3L">Monitor Mirror v2.1</a></td>
550
--local tPastes={"DW3LCC3L"}
551
--local tPasteNames={"Monitor Mirror v2.1"}
552
--local tPasteSuggestNames = {["DW3LCC3L"] = "Monitor_Mirror_v2.1}
553
554
--Make table of usernames from settings
555
local tPastebinUserNames = {}
556
for sName in string.gmatch( getSetting("shellUtil.pastebin_names"), "[^,]+" ) do table.insert(tPastebinUserNames,sName) end
557
558
--Code to get all the public pastes on Ext-util load.
559
local tPastes = {}
560
local tPasteNames = {}
561
local tPasteSuggestNames = {}
562
for _,name in pairs(tPastebinUserNames) do
563-
	local site = "http://pastebin.com/u/"..textutils.urlEncode( name )
563+
	local site = "https://pastebin.com/u/"..textutils.urlEncode( name )
564
	if http.checkURL(site) then
565
		local data = getHttp(site)
566
		if data then
567
			--get pastes from data here.
568
			for i,k in string.gmatch (data, '<td><img src="/i/t.gif" class="i_p0" title="Public paste, anybody can see this paste." alt="" /> <a href="/(%w+)">(.-)</a></td>') do
569
				table.insert(tPastes,i)
570
				table.insert(tPasteNames,k)
571
				tPasteSuggestNames[i] = string.gsub(k,"%s","_")
572
			end
573
		end
574
	end
575
end
576
local tPastebinOptions = { "get ", "run ", "put" }
577
local function completePastebin( shell, nIndex, sText, tPreviousText )
578
    if nIndex == 1 then
579
        return completeMultipleChoice( sText, tPastebinOptions )
580
    elseif nIndex == 2 then
581
        if tPreviousText[2] == "put" then
582
            return fs.complete( sText, shell.dir(), true, false )
583
		elseif tPreviousText[2] == "get" then
584
			return completeMultipleChoice( sText, tPastes, true,tPasteNames)
585
		elseif tPreviousText[2] == "run" then
586
			return completeMultipleChoice( sText, tPastes, true,tPasteNames)
587
        end
588
	elseif nIndex == 3 then
589
		if tPreviousText[2] == "get" and tPasteSuggestNames[tPreviousText[3]]  then 
590
			return completeMultipleChoice( sText, {tPasteSuggestNames[tPreviousText[3]]} )
591
		end
592
    end
593
end
594
595
--rom/programs/rednet/
596
-- Chat -- On join allow for duble tap of Tab to scan area for chat servers. Automaticly suggest name(s) from "shellUtil.chat_names"
597
--##FIND WAY TO EXTRACT ANY SERVER INFO DATA FROM CHAT SERVER WITHOUT LOGGING INTO IT
598
local tChatOptions = {"join ", "host "}
599
local tServers = {}
600
local nLasttab = 0
601
local function completeChat( shell, nIndex, sText, tPreviousText )
602
    if nIndex == 1 then
603
        return completeMultipleChoice( sText, tChatOptions )
604
	elseif nIndex == 2 and tPreviousText[2] == "join" then
605
		local tGhosts = {}
606
		if sText =="" then --Act only is sText field is empty.
607
			local nTime=os.clock()
608
			if (nTime-nLasttab) < 0 then --Still Blocked from last scan.
609
				--do nothing			
610
			elseif (nTime-nLasttab) < 0.5 then
611
				tServers = hostnameslook("chat") --When 2 empty inputs in 0.5 sec range do a rednet scan, if not empty dont re-scan.
612
				if #tServers == 0 then
613
					tServers = {""}
614
					tGhosts  =  {"[No Servers Found. Re-Tap to Re-Scan]"}
615
					nLasttab = os.clock()
616
				else
617
					nLasttab = os.clock() + 30 -- Block scanning for 30 sec so it won't scan over and over again in row.
618
				end
619
			else
620
				tServers = {""}
621
				tGhosts =  {"[Double-Tap Tab to Scan]"}
622
				nLasttab = os.clock()
623
			end
624
		end
625
		return completeMultipleChoice( sText, tServers , true ,tGhosts)
626
	elseif nIndex == 3 and tPreviousText[2] == "join" then
627
		local tNames = {}
628
		for sName in string.gmatch( getSetting("shellUtil.chat_names"), "[^,]+" ) do table.insert(tNames,sName) end
629
		return completeMultipleChoice( sText, tNames)
630
    end
631
end
632
633
--# Apply said functions
634-
shell.setCompletionFunction( "rom/programs/eject", completeEject )
634+
shell.setCompletionFunction( "rom/programs/eject.lua", completeEject )
635-
shell.setCompletionFunction( "rom/programs/gps", completeGPS )
635+
shell.setCompletionFunction( "rom/programs/gps.lua", completeGPS )
636-
shell.setCompletionFunction( "rom/programs/label", completeLabel )
636+
shell.setCompletionFunction( "rom/programs/label.lua", completeLabel )
637-
shell.setCompletionFunction( "rom/programs/monitor", completeMonitor )
637+
shell.setCompletionFunction( "rom/programs/monitor.lua", completeMonitor )
638-
shell.setCompletionFunction( "rom/programs/set", completeSet )
638+
shell.setCompletionFunction( "rom/programs/set.lua", completeSet )
639
640-
shell.setCompletionFunction( "rom/programs/fun/dj", completeDJ )
640+
shell.setCompletionFunction( "rom/programs/fun/dj.lua", completeDJ )
641
642-
shell.setCompletionFunction( "rom/programs/http/pastebin", completePastebin )
642+
shell.setCompletionFunction( "rom/programs/http/pastebin.lua", completePastebin )
643
644-
shell.setCompletionFunction( "rom/programs/rednet/chat", completeChat )
644+
shell.setCompletionFunction( "rom/programs/rednet/chat.lua", completeChat )