mirror of
				https://github.com/LDDestroier/CC/
				synced 2025-10-31 07:22:59 +00:00 
			
		
		
		
	 3c075902e5
			
		
	
	3c075902e5
	
	
	
		
			
			Moving/deleting with CTRL+Arrows and CTRL+Backspace/Delete should be more accurate now. Also, you can't make new cursors from within selected areas now.
		
			
				
	
	
		
			1525 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			1525 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| --[[
 | |
| 	Eldit (still being made)
 | |
| 	 by LDDestroier
 | |
| 	wget https://raw.githubusercontent.com/LDDestroier/CC/master/eldit.lua
 | |
| 
 | |
| TO DO:
 | |
|  - MAJOR: Merge selections that intersect
 | |
|  - MAJOR: Allow selecting with Shift + ArrowKeys
 | |
|  - MAJOR: Fix deleting multiple selections (MUST delete selections from bottom to top)
 | |
|  - MAJOR: Add syntax highlighting
 | |
|  - Add more keyboard shortcuts
 | |
|  - Add help menu
 | |
|  - Eventually add simultaneous peer editing
 | |
| 
 | |
| --]]
 | |
| 
 | |
| local scr_x, scr_y = term.getSize()
 | |
| 
 | |
| local argData = {
 | |
| 	["-l"] = "number"
 | |
| }
 | |
| 
 | |
| local eldit, config = {}, {}
 | |
| eldit.buffer = {{}}			-- stores all text, organized like eldit.buffer[yPos][xPos]
 | |
| eldit.undoBuffer = {{{}}}	-- stores buffers for undoing/redoing
 | |
| eldit.allowUndo = true		-- whether or not to allow undoing/redoing
 | |
| eldit.maxUndo = 16			-- maximum size of the undo buffer
 | |
| eldit.undoPos = 1			-- current position in undo buffer
 | |
| eldit.undoDelay = 0.3		-- amount of time to wait after typing, before the buffer is put in the undo buffer
 | |
| eldit.clipboards = {}		-- all clipboard entries
 | |
| eldit.selectedClipboard = 1	-- which clipboard to use
 | |
| eldit.scrollX = 0			-- horizontal scroll
 | |
| eldit.scrollY = 0			-- vertical scroll
 | |
| eldit.selections = {}		-- all selected areas
 | |
| eldit.size = {
 | |
| 	x = 1,			-- top left corner X
 | |
| 	y = 1,			-- top left corner Y
 | |
| 	width = scr_x,	-- horizontal size
 | |
| 	height = scr_y	-- vertical size
 | |
| }
 | |
| 
 | |
| -- made-up keys for easier use of both left and right modifier keys
 | |
| keys.shift = 127
 | |
| keys.alt = 128
 | |
| keys.ctrl = 129
 | |
| 
 | |
| config.showLineNumberIndicator = false
 | |
| config.showWhitespace = true
 | |
| config.showTrailingSpace = true
 | |
| config.findExtension = true
 | |
| 
 | |
| -- minor optimizations, I think
 | |
| local concatTable = table.concat
 | |
| local sortTable = table.sort
 | |
| 
 | |
| -- I'm never using regular argument parsing again, this function rules
 | |
| local interpretArgs = function(tInput, tArgs)
 | |
| 	local output = {}
 | |
| 	local errors = {}
 | |
| 	local usedEntries = {}
 | |
| 	for aName, aType in pairs(tArgs) do
 | |
| 		output[aName] = false
 | |
| 		for i = 1, #tInput do
 | |
| 			if not usedEntries[i] then
 | |
| 				if tInput[i] == aName and not output[aName] then
 | |
| 					if aType then
 | |
| 						usedEntries[i] = true
 | |
| 						if type(tInput[i+1]) == aType or type(tonumber(tInput[i+1])) == aType then
 | |
| 							usedEntries[i+1] = true
 | |
| 							if aType == "number" then
 | |
| 								output[aName] = tonumber(tInput[i+1])
 | |
| 							else
 | |
| 								output[aName] = tInput[i+1]
 | |
| 							end
 | |
| 						else
 | |
| 							output[aName] = nil
 | |
| 							errors[1] = errors[1] and (errors[1] + 1) or 1
 | |
| 							errors[aName] = "expected " .. aType .. ", got " .. type(tInput[i+1])
 | |
| 						end
 | |
| 					else
 | |
| 						usedEntries[i] = true
 | |
| 						output[aName] = true
 | |
| 					end
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 	for i = 1, #tInput do
 | |
| 		if not usedEntries[i] then
 | |
| 			output[#output+1] = tInput[i]
 | |
| 		end
 | |
| 	end
 | |
| 	return output, errors
 | |
| end
 | |
| 
 | |
| local argList = interpretArgs({...}, argData)
 | |
| 
 | |
| eldit.filename = argList[1] and shell.resolve(argList[1])
 | |
| 
 | |
| if eldit.filename then
 | |
| 	if fs.isDir(eldit.filename) then
 | |
| 		error("Cannot edit a directory.", 0)
 | |
| 	end
 | |
| 
 | |
| 	if config.findExtension then
 | |
| 		if not fs.exists(eldit.filename) then
 | |
| 			local m
 | |
| 			local d = fs.list(fs.getDir(eldit.filename))
 | |
| 			for i = 1, #d do
 | |
| 				m = d[i]:match(fs.getName(eldit.filename) .. "%....$")
 | |
| 				if m then
 | |
| 					eldit.filename = fs.combine(fs.getDir(eldit.filename), m)
 | |
| 					break
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| eldit.cursors = {{
 | |
| 	x = 1,
 | |
| 	y = math.max(1, argList["-l"] or 1),
 | |
| 	lastX = 1
 | |
| }}
 | |
| 
 | |
| local eClearLine = function(y)
 | |
| 	local cx, cy = term.getCursorPos()
 | |
| 	term.setCursorPos(eldit.size.x, y or cy)
 | |
| 	term.write((" "):rep(eldit.size.width))
 | |
| 	term.setCursorPos(cx, cy)
 | |
| end
 | |
| 
 | |
| local eClear = function()
 | |
| 	local cx, cy = term.getCursorPos()
 | |
| 	for y = eldit.size.y, eldit.size.y + eldit.size.height - 1 do
 | |
| 		term.setCursorPos(eldit.size.x, y)
 | |
| 		term.write((" "):rep(eldit.size.width))
 | |
| 	end
 | |
| 	term.setCursorPos(cx, cy)
 | |
| end
 | |
| 
 | |
| -- sorts all selections based on each of their (x,y) positions (top left first)
 | |
| local sortSelections = function()
 | |
| 	for id,sel in pairs(eldit.selections) do
 | |
| 		sortTable(sel, function(a,b)
 | |
| 			return (a.y * eldit.size.width) + a.x < (b.y * eldit.size.width) + b.x
 | |
| 		end)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- sorts all cursors based on (x,y) position (top left first)
 | |
| local sortCursors = function()
 | |
| 	sortTable(eldit.cursors, function(a,b)
 | |
| 		return (a.y * eldit.size.width) + a.x < (b.y * eldit.size.width) + b.x
 | |
| 	end)
 | |
| end
 | |
| 
 | |
| local explode = function(div, str, replstr, includeDiv)
 | |
| 	if (div == '') then
 | |
| 		return false
 | |
| 	end
 | |
| 	local pos, arr = 0, {}
 | |
| 	for st, sp in function() return string.find(str, div, pos, false) end do
 | |
| 		table.insert(arr, string.sub(replstr or str, pos, st - 1 + (includeDiv and #div or 0)))
 | |
| 		pos = sp + 1
 | |
| 	end
 | |
| 	table.insert(arr, string.sub(replstr or str, pos))
 | |
| 	return arr
 | |
| end
 | |
| 
 | |
| local readFile = function(path)
 | |
| 	if fs.exists(path) then
 | |
| 		local file = fs.open(path, "r")
 | |
| 		local contents = file.readAll()
 | |
| 		file.close()
 | |
| 		return contents
 | |
| 	else
 | |
| 		return nil
 | |
| 	end
 | |
| end
 | |
| 
 | |
| local writeFile = function(path, contents)
 | |
| 	if fs.isReadOnly(path) or fs.isDir(path) then
 | |
| 		return false
 | |
| 	else
 | |
| 		local file = fs.open(path, "w")
 | |
| 		file.write(contents)
 | |
| 		file.close()
 | |
| 		return true
 | |
| 	end
 | |
| end
 | |
| 
 | |
| local deepCopy
 | |
| deepCopy = function(tbl)
 | |
| 	local output = {}
 | |
| 	for k,v in pairs(tbl) do
 | |
| 		if type(v) == "table" then
 | |
| 			output[k] = deepCopy(v)
 | |
| 		else
 | |
| 			output[k] = v
 | |
| 		end
 | |
| 	end
 | |
| 	return output
 | |
| end
 | |
| 
 | |
| local choice = function(input, breakKeys, returnNumber)
 | |
| 	local fpos = 0
 | |
| 	repeat
 | |
| 		event, key = os.pullEvent("char")
 | |
| 		key = key or ""
 | |
| 		if type(breakKeys) == "table" then
 | |
| 			for a = 1, #breakKeys do
 | |
| 				if key == breakKeys[a] then
 | |
| 					return ""
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 		fpos = string.find(input, key)
 | |
| 	until fpos
 | |
| 	return returnNumber and fpos or key
 | |
| end
 | |
| 
 | |
| prompt = function(prebuffer, precy, maxY, _eldit)
 | |
| 	term.setCursorBlink(false)
 | |
| 	local keysDown = {}					-- list of all keys being pressed
 | |
| 	local miceDown = {}					-- list of all mouse buttons being pressed
 | |
| 	eldit = _eldit or eldit				-- you can replace the "eldit" table if you want I guess
 | |
| 	maxY = maxY or math.huge			-- limits amount of lines
 | |
| 	local defaultBarLife = 10			-- default amount of time bar msg will stay onscreen
 | |
| 	local barmsg = "Started Eldit."		-- message displayed on bottom screen
 | |
| 	local barlife = defaultBarLife
 | |
| 	local lastMouse = {}				-- last place you clicked onscreen
 | |
| 	local isSelecting = false			-- whether or not you are selecting text
 | |
| 	if type(prebuffer) == "string" then	-- enter a "prebuffer" (string or table) to set the contents
 | |
| 		for i = 1, #prebuffer do
 | |
| 			if prebuffer:sub(i,i) == "\n" then
 | |
| 				eldit.buffer[#eldit.buffer + 1] = {}
 | |
| 			else
 | |
| 				eldit.buffer[#eldit.buffer][#eldit.buffer[#eldit.buffer] + 1] = prebuffer:sub(i,i)
 | |
| 			end
 | |
| 		end
 | |
| 	elseif type(prebuffer) == "table" then
 | |
| 		eldit.buffer = prebuffer
 | |
| 	end
 | |
| 	eldit.undoBuffer[1] = {
 | |
| 		buffer = deepCopy(eldit.buffer),
 | |
| 		cursors = deepCopy(eldit.cursors),
 | |
| 		selections = deepCopy(eldit.selections)
 | |
| 	}
 | |
| 	local isCursorBlink = false			-- blinks the background color on each cursor
 | |
| 	local isInsert = false				-- will overwrite characters instead of appending them
 | |
| 
 | |
| 	-- gets length of left line numbers, if enabled at all
 | |
| 	local getLineNoLen = function()
 | |
| 		if config.showLineNumberIndicator then
 | |
| 			return #tostring(#eldit.buffer)
 | |
| 		else
 | |
| 			return 0
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- list of all characters that will stop a CTRL+Backspace or CTRL+Delete or CTRL+Left/Right
 | |
| 	local interruptable = {
 | |
| 		[" "] = true,
 | |
| 		["["] = true, ["]"] = true,
 | |
| 		["{"] = true, ["}"] = true,
 | |
| 		["("] = true, [")"] = true,
 | |
| 		["|"] = true,
 | |
| 		["/"] = true,
 | |
| 		["\\"] = true,
 | |
| 		["+"] = true,
 | |
| 		["-"] = true,
 | |
| 		["*"] = true,
 | |
| 		["="] = true,
 | |
| 		["."] = true,
 | |
| 		[","] = true
 | |
| 	}
 | |
| 
 | |
| 	-- checks if (checkX, checkY) is between (x1, y1) and (x2, y2) in terms of selection (it's not a rectangular check)
 | |
| 	local checkWithinArea = function(checkX, checkY, x1, y1, x2, y2)
 | |
| 		if checkY == y1 then
 | |
| 			if y1 == y2 then
 | |
| 				return checkX >= x1 and checkX <= x2
 | |
| 			else
 | |
| 				return checkX >= x1
 | |
| 			end
 | |
| 		elseif checkY == y2 then
 | |
| 			if y1 == y2 then
 | |
| 				return checkX >= x1 and checkX <= x2
 | |
| 			else
 | |
| 				return checkX <= x2 and checkX >= 1
 | |
| 			end
 | |
| 		elseif checkY > y1 and checkY < y2 then
 | |
| 			return true
 | |
| 		else
 | |
| 			return false
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- goes over every selection and checks if it is selected
 | |
| 	-- (x, y) = position on buffer
 | |
| 	local checkIfSelected = function(x, y)
 | |
| 		sortSelections()
 | |
| 		local fin
 | |
| 		if y >= 1 and y <= #eldit.buffer then
 | |
| 			if x >= 1 and x <= #eldit.buffer[y] + 1 then
 | |
| 				for id, sel in pairs(eldit.selections) do
 | |
| 
 | |
| 					if checkWithinArea(x, y, sel[1].x, sel[1].y, sel[2].x, sel[2].y) then
 | |
| 						return id
 | |
| 					end
 | |
| 
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 		return false
 | |
| 	end
 | |
| 
 | |
| 	-- goes over every cursor and checks if they are at (x, y)
 | |
| 	-- (x,y) = position on buffer
 | |
| 	local checkIfCursor = function(x, y)
 | |
| 		for id, cur in pairs(eldit.cursors) do
 | |
| 			if x == cur.x and y == cur.y then
 | |
| 				return id
 | |
| 			end
 | |
| 		end
 | |
| 		return false
 | |
| 	end
 | |
| 
 | |
| 	-- returns character at (x, y) on the buffer
 | |
| 	local getChar = function(x, y)
 | |
| 		if eldit.buffer[y] then
 | |
| 			return eldit.buffer[y][x]
 | |
| 		else
 | |
| 			return nil
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- all characters that count as whitespace
 | |
| 	local tab = {
 | |
| 		[" "] = true,
 | |
| 		["\9"] = true
 | |
| 	}
 | |
| 
 | |
| 	-- the big boi function, draws **EVERYTHING**
 | |
| 	local render = function()
 | |
| 		local cx, cy
 | |
| 		local lineNoLen = getLineNoLen()
 | |
| 		local isHighlighted = false
 | |
| 		local textPoses = {math.huge, -math.huge}		-- used to identify space characters without text
 | |
| 		local screen = {{},{},{}}
 | |
| 		for y = 1, eldit.size.height - 1 do -- minus one because it reserves space for the bar
 | |
| 			screen[1][y] = {}
 | |
| 			screen[2][y] = {}
 | |
| 			screen[3][y] = {}
 | |
| 			cy = y + eldit.scrollY
 | |
| 			-- find text
 | |
| 			if eldit.buffer[cy] and (config.showWhitespace or config.showTrailingSpace) then
 | |
| 				textPoses = {
 | |
| 					#(concatTable(eldit.buffer[cy]):match("^ +") or "") + 1,
 | |
| 					#eldit.buffer[cy] - #(concatTable(eldit.buffer[y]):match(" +$") or "")
 | |
| 				}
 | |
| 			end
 | |
| 			if cy <= #eldit.buffer and lineNoLen > 0 then
 | |
| 				isHighlighted = false
 | |
| 				for id,cur in pairs(eldit.cursors) do
 | |
| 					if cy == cur.y then
 | |
| 						isHighlighted = true
 | |
| 						break
 | |
| 					end
 | |
| 				end
 | |
| 				if not isHighlighted then
 | |
| 					for id,sel in pairs(eldit.selections) do
 | |
| 						if cy >= sel[1].y and cy <= sel[2].y then
 | |
| 							isHighlighted = true
 | |
| 							break
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 				if isHighlighted then
 | |
| 					term.setBackgroundColor(colors.gray)
 | |
| 					term.setTextColor(colors.white)
 | |
| 				else
 | |
| 					term.setBackgroundColor(colors.black)
 | |
| 					term.setTextColor(colors.lightGray)
 | |
| 				end
 | |
| 				term.setCursorPos(eldit.size.x, eldit.size.y + y - 1)
 | |
| 				term.write(cy .. (" "):rep(lineNoLen - #tostring(y)))
 | |
| 			end
 | |
| 
 | |
| 			-- actually draw text
 | |
| 
 | |
| 			local cChar, cTxt, cBg = " ", " ", " "
 | |
| 			term.setCursorPos(eldit.size.x + lineNoLen, eldit.size.y + y - 1)
 | |
| 			for x = lineNoLen + 1, eldit.size.width do
 | |
| 				cx = x + eldit.scrollX - lineNoLen
 | |
| 
 | |
| 				if checkIfCursor(cx, cy) and isCursorBlink then
 | |
| 					if isInsert then
 | |
| 						cTxt, cBg = "8", "0"
 | |
| 					else
 | |
| 						cTxt, cBg = "f", "8"
 | |
| 					end
 | |
| 				else
 | |
| 					if checkIfSelected(cx, cy) then
 | |
| 						cBg = "b"
 | |
| 					else
 | |
| 						cBg = "f"
 | |
| 					end
 | |
| 					cTxt = "0"
 | |
| 				end
 | |
| 				if config.showWhitespace or config.showTrailingSpace then
 | |
| 					if textPoses[1] and textPoses[2] and eldit.buffer[cy] then
 | |
| 						if cx < textPoses[1] and eldit.buffer[cy][cx] then
 | |
| 							cTxt = "7"
 | |
| 							cChar = "|"
 | |
| 						elseif (cx > textPoses[2] and eldit.buffer[cy][cx]) then
 | |
| 							cTxt = "7"
 | |
| 							cChar = "-"
 | |
| 						else
 | |
| 							cChar = getChar(cx, cy) or " "
 | |
| 						end
 | |
| 					else
 | |
| 						cChar = getChar(cx, cy) or " "
 | |
| 					end
 | |
| 				else
 | |
| 					cChar = getChar(cx, cy) or " "
 | |
| 				end
 | |
| 				screen[1][y][x - lineNoLen] = cChar
 | |
| 				screen[2][y][x - lineNoLen] = cTxt
 | |
| 				screen[3][y][x - lineNoLen] = cBg
 | |
| 			end
 | |
| 			term.blit(
 | |
| 				concatTable(screen[1][y]),
 | |
| 				concatTable(screen[2][y]),
 | |
| 				concatTable(screen[3][y])
 | |
| 			)
 | |
| 		end
 | |
| 		term.setCursorPos(eldit.size.x, eldit.size.y + eldit.size.height - 1)
 | |
| 		term.setBackgroundColor(colors.black)
 | |
| 		eClearLine()
 | |
| 		if barlife > 0 then
 | |
| 			term.setTextColor(colors.yellow)
 | |
| 			term.write(barmsg)
 | |
| 		else
 | |
| 			term.setTextColor(colors.yellow)
 | |
| 			for id,cur in pairs(eldit.cursors) do
 | |
| 				term.write("(" .. cur.x .. "," .. cur.y .. ") ")
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- if all cursors are offscreen, will scroll so that at least one of them is onscreen
 | |
| 	local scrollToCursor = function()
 | |
| 		lineNoLen = getLineNoLen()
 | |
| 		local lowCur, highCur = eldit.cursors[1], eldit.cursors[1]
 | |
| 		local leftCur, rightCur = eldit.cursors[1], eldit.cursors[1]
 | |
| 		for id,cur in pairs(eldit.cursors) do
 | |
| 			if cur.y < lowCur.y then
 | |
| 				lowCur = cur
 | |
| 			elseif cur.y > highCur.y then
 | |
| 				highCur = cur
 | |
| 			end
 | |
| 			if cur.x < leftCur.x then
 | |
| 				leftCur = cur
 | |
| 			elseif cur.y > rightCur.x then
 | |
| 				rightCur = cur
 | |
| 			end
 | |
| 		end
 | |
| 		if lowCur.y - eldit.scrollY < 1 then
 | |
| 			eldit.scrollY = -1 + highCur.y
 | |
| 		elseif highCur.y - eldit.scrollY > -1 + eldit.size.height then
 | |
| 			eldit.scrollY = 1 + lowCur.y - eldit.size.height
 | |
| 		end
 | |
| 		if leftCur.x - eldit.scrollX < 1 then
 | |
| 			eldit.scrollX = -1 + rightCur.x
 | |
| 		elseif rightCur.x - eldit.scrollX > eldit.size.width - lineNoLen then
 | |
| 			eldit.scrollX = leftCur.x - (eldit.size.width - lineNoLen)
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- gets the widest line length in all the buffer
 | |
| 	local getMaximumWidth = function()
 | |
| 		local maxX = 0
 | |
| 		for y = 1, #eldit.buffer do
 | |
| 			maxX = math.max(maxX, #eldit.buffer[y])
 | |
| 		end
 | |
| 		return maxX
 | |
| 	end
 | |
| 
 | |
| 	-- scrolls the screen, and fixes it if it's set to some weird value
 | |
| 	local adjustScroll = function(modx, mody)
 | |
| 		modx, mody = modx or 0, mody or 0
 | |
| 		local lineNoLen = getLineNoLen()
 | |
| 		if mody then
 | |
| 			eldit.scrollY = math.min(
 | |
| 				math.max(
 | |
| 					0,
 | |
| 					eldit.scrollY + mody
 | |
| 				),
 | |
| 				math.max(
 | |
| 					0,
 | |
| 					1 + #eldit.buffer - eldit.size.height
 | |
| 				)
 | |
| 			)
 | |
| 		end
 | |
| 		if modx then
 | |
| 			eldit.scrollX = math.min(
 | |
| 				math.max(
 | |
| 					0,
 | |
| 					eldit.scrollX + modx
 | |
| 				),
 | |
| 				math.max(
 | |
| 					0,
 | |
| 					1 + getMaximumWidth() - eldit.size.width - lineNoLen
 | |
| 				)
 | |
| 			)
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- removes any cursors that share positions
 | |
| 	local removeRedundantCursors = function()
 | |
| 		local xes = {}
 | |
| 		for i = #eldit.cursors, 1, -1 do
 | |
| 			if xes[eldit.cursors[i].x] == eldit.cursors[i].y then
 | |
| 				table.remove(eldit.cursors, i)
 | |
| 			else
 | |
| 				xes[eldit.cursors[i].x] = eldit.cursors[i].y
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- deletes text at every cursor position, either forward or backward or neutral
 | |
| 	local deleteText = function(mode, direction, _cx, _cy)
 | |
| 		local xAdjList = {}
 | |
| 		local yAdj = 0
 | |
| 		sortCursors()
 | |
| 
 | |
| 		local rowBuff	-- represents the buffer row at the current cursor's Y
 | |
| 		local startOnInterruptable
 | |
| 
 | |
| 		for id,cur in pairs(eldit.cursors) do
 | |
| 			cx = _cx or cur.x - (xAdjList[_cy or cur.y] or 0)
 | |
| 			cy = _cy or cur.y - yAdj
 | |
| 
 | |
| 			rowBuff = eldit.buffer[cy] or {}
 | |
| 			startOnInterruptable = interruptable[rowBuff[cx]] or (not rowBuff[cx])
 | |
| 
 | |
| 			if mode == "single" or (direction == "forward" and cx == #eldit.buffer[cy] or (direction == "backward" and cx == 1)) then
 | |
| 				if direction == "forward" then
 | |
| 					if cx < #eldit.buffer[cy] then
 | |
| 						xAdjList[cy] = (xAdjList[cy] or 0) + 1
 | |
| 						table.remove(eldit.buffer[cy], cx)
 | |
| 					elseif cy < #eldit.buffer then
 | |
| 						for i = 1, #eldit.buffer[cy + 1] do
 | |
| 							table.insert(eldit.buffer[cy], eldit.buffer[cy + 1][i])
 | |
| 						end
 | |
| 						table.remove(eldit.buffer, cy + 1)
 | |
| 						yAdj = yAdj + 1
 | |
| 					end
 | |
| 				elseif direction == "backward" then
 | |
| 					if cx > 1 then
 | |
| 						cx = cx - 1
 | |
| 						xAdjList[cy] = (xAdjList[cy] or 0) + 1
 | |
| 						table.remove(eldit.buffer[cy], cx)
 | |
| 					elseif cy > 1 then
 | |
| 						cx = #eldit.buffer[cy - 1] + 1
 | |
| 						for i = 1, #eldit.buffer[cy] do
 | |
| 							table.insert(eldit.buffer[cy - 1], eldit.buffer[cy][i])
 | |
| 						end
 | |
| 						table.remove(eldit.buffer, cy)
 | |
| 						yAdj = yAdj + 1
 | |
| 						cy = cy - 1
 | |
| 					end
 | |
| 				else
 | |
| 					if cx >= 1 and cx <= #eldit.buffer[cy] then
 | |
| 						table.remove(eldit.buffer[cy], cx)
 | |
| 					elseif cx == #eldit.buffer[cy] + 1 and cy < #eldit.buffer then
 | |
| 						for i = 1, #eldit.buffer[cy + 1] do
 | |
| 							table.insert(eldit.buffer[cy], eldit.buffer[cy + 1][i])
 | |
| 						end
 | |
| 						table.remove(eldit.buffer, cy + 1)
 | |
| 						yAdj = yAdj + 1
 | |
| 					end
 | |
| 				end
 | |
| 			elseif mode == "word" then
 | |
| 				local pos = cx
 | |
| 				if direction == "forward" then
 | |
| 					while true do
 | |
| 						pos = pos + 1
 | |
| 						if startOnInterruptable then
 | |
| 							if (not interruptable[rowBuff[pos]]) or (not rowBuff[pos]) then
 | |
| 								startOnInterruptable = false
 | |
| 							end
 | |
| 						else
 | |
| 							if interruptable[rowBuff[pos]] or (not rowBuff[pos]) then
 | |
| 								break
 | |
| 							end
 | |
| 						end
 | |
| 
 | |
| 						if (pos + 1) < 0 or (pos + 1) > #rowBuff + 1 then
 | |
| 							break
 | |
| 						end
 | |
| 					end
 | |
| 					for i = pos, cx, -1 do
 | |
| 						xAdjList[cy] = (xAdjList[cy] or 0) + 1
 | |
| 						table.remove(eldit.buffer[cy], i)
 | |
| 					end
 | |
| 				else
 | |
| 					while true do
 | |
| 						pos = pos - 1
 | |
| 						if startOnInterruptable then
 | |
| 							if (not interruptable[rowBuff[pos]]) or (not rowBuff[pos]) then
 | |
| 								startOnInterruptable = false
 | |
| 							end
 | |
| 						else
 | |
| 							if interruptable[rowBuff[pos]] or (not rowBuff[pos]) then
 | |
| 								break
 | |
| 							end
 | |
| 						end
 | |
| 
 | |
| 						if (pos - 1) < 0 or (pos - 1) > #rowBuff + 1 then
 | |
| 							break
 | |
| 						end
 | |
| 					end
 | |
| 					pos = math.max(1, pos)
 | |
| 					for i = cx - 1, pos, -1 do
 | |
| 						table.remove(eldit.buffer[cy], i)
 | |
| 					end
 | |
| 					cx = pos
 | |
| 				end
 | |
| 			elseif mode == "line" then -- like word but is only interrupted by newline
 | |
| 				if direction == "forward" then
 | |
| 					for i = cx, #eldit.buffer[cy] do
 | |
| 						eldit.buffer[cy][i] = nil
 | |
| 					end
 | |
| 				else
 | |
| 					for i = cx, 1, -1 do
 | |
| 						table.remove(eldit.buffer[cy], i)
 | |
| 					end
 | |
| 				end
 | |
| 			end
 | |
| 
 | |
| 			if _cx then
 | |
| 				return yAdj
 | |
| 			else
 | |
| 				cur.x = cx
 | |
| 				cur.y = cy
 | |
| 				cur.lastX = cx
 | |
| 			end
 | |
| 
 | |
| 		end
 | |
| 		removeRedundantCursors()
 | |
| 		if not isSelecting then
 | |
| 			scrollToCursor()
 | |
| 		end
 | |
| 		return yAdj
 | |
| 	end
 | |
| 
 | |
| 	local indentLines = function(goBackward)
 | |
| 		sortSelections()
 | |
| 		local safeY = {}
 | |
| 		for id,sel in pairs(eldit.selections) do
 | |
| 			for y = sel[1].y, sel[2].y do
 | |
| 				if not safeY[y] then
 | |
| 					if goBackward then
 | |
| 						if eldit.buffer[y][1] == "\9" or eldit.buffer[y][1] == " " then
 | |
| 							table.remove(eldit.buffer[y], 1)
 | |
| 							if y == sel[1].y then
 | |
| 								sel[1].x = math.max(1, -1 + sel[1].x)
 | |
| 							elseif y == sel[2].y then
 | |
| 								sel[2].x = math.max(1, -1 + sel[2].x)
 | |
| 							end
 | |
| 							for idd,cur in pairs(eldit.cursors) do
 | |
| 								if cur.y == y and cur.x > 1 then
 | |
| 									cur.x = -1 + cur.x
 | |
| 									cur.lastX = cur.x
 | |
| 								end
 | |
| 							end
 | |
| 						end
 | |
| 					elseif eldit.buffer[y] then
 | |
| 						table.insert(eldit.buffer[y], 1, "\9")
 | |
| 						if y == sel[1].y then
 | |
| 							sel[1].x = 1 + sel[1].x
 | |
| 						elseif y == sel[2].y then
 | |
| 							sel[2].x = 1 + sel[2].x
 | |
| 						end
 | |
| 						for idd,cur in pairs(eldit.cursors) do
 | |
| 							if cur.y == y and cur.x < #eldit.buffer[y] then
 | |
| 								cur.x = 1 + cur.x
 | |
| 								cur.lastX = cur.x
 | |
| 							end
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 				safeY[y] = true
 | |
| 			end
 | |
| 		end
 | |
| 		for id,cur in pairs(eldit.cursors) do
 | |
| 			if not safeY[cur.y] then
 | |
| 				if goBackward then
 | |
| 					if eldit.buffer[cur.y][1] == "\9" or eldit.buffer[cur.y][1] == " " then
 | |
| 						table.remove(eldit.buffer[cur.y], 1)
 | |
| 						cur.x = -1 + cur.x
 | |
| 						cur.lastX = cur.x
 | |
| 					end
 | |
| 				else
 | |
| 					table.insert(eldit.buffer[cur.y], 1, "\9")
 | |
| 					cur.x = 1 + cur.x
 | |
| 					cur.lastX = cur.x
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- moves the cursor by (xmod, ymod), and fixes its position if it's set to an invalid one
 | |
| 	local adjustCursor = function(_xmod, _ymod, setLastX, mode, doNotDelSelections, adjustSelections, doNotTouchScroll)
 | |
| 		local step = (_xmod / math.abs(_xmod))
 | |
| 		local rowBuff	-- represents the buffer row at the current cursor's Y
 | |
| 		local startOnInterruptable
 | |
| 
 | |
| 		local origCX, origCY
 | |
| 
 | |
| 		for id,cur in pairs(eldit.cursors) do
 | |
| 			origCX, origCY = cur.x, cur.y
 | |
| 			rowBuff = eldit.buffer[cur.y] or {}
 | |
| 			startOnInterruptable = interruptable[rowBuff[cur.x + step]]
 | |
| 			if mode == "word" then
 | |
| 				xmod = step
 | |
| 				ymod = 0
 | |
| 				while true do
 | |
| 					xmod = xmod + step
 | |
| 					if math.abs(xmod) > math.abs(step) then
 | |
| 						if startOnInterruptable then
 | |
| 							if (not interruptable[rowBuff[cur.x + xmod]]) or (not rowBuff[cur.x + xmod]) then
 | |
| 								startOnInterruptable = false
 | |
| 							end
 | |
| 						else
 | |
| 							if interruptable[rowBuff[cur.x + xmod]] or (not rowBuff[cur.x + xmod]) then
 | |
| 								break
 | |
| 							end
 | |
| 						end
 | |
| 					end
 | |
| 
 | |
| 					if (cur.x + xmod + step) < 0 or (cur.x + xmod + step) > #rowBuff + 1 then
 | |
| 						break
 | |
| 					end
 | |
| 				end
 | |
| 				xmod = xmod - math.min(0, math.max(xmod, -1))
 | |
| 			else
 | |
| 				xmod = _xmod
 | |
| 				ymod = _ymod
 | |
| 			end
 | |
| 			if mode == "flip" then
 | |
| 				if eldit.buffer[cur.y + ymod] then
 | |
| 					eldit.buffer[cur.y], eldit.buffer[cur.y + ymod] = eldit.buffer[cur.y + ymod], eldit.buffer[cur.y]
 | |
| 				end
 | |
| 			end
 | |
| 			cur.x = cur.x + xmod
 | |
| 			cur.y = math.max(1, math.min(cur.y + ymod, #eldit.buffer))
 | |
| 			if xmod ~= 0 then
 | |
| 				repeat
 | |
| 				if cur.x < 1 and cur.y > 1 then
 | |
| 						cur.y = cur.y - 1
 | |
| 						cur.x = cur.x + #eldit.buffer[cur.y] + 1
 | |
| 					elseif cur.x > #eldit.buffer[cur.y] + 1 and cur.y < #eldit.buffer then
 | |
| --						cur.x = cur.x - #eldit.buffer[cur.y] - 1
 | |
| 						cur.x = 1
 | |
| 						cur.y = cur.y + 1
 | |
| 					end
 | |
| 				until (cur.x >= 1 and cur.x <= #eldit.buffer[cur.y] + 1) or ((cur.y == 1 and xmod < 0) or (cur.y == #eldit.buffer and xmod > 0))
 | |
| 			end
 | |
| 			cur.lastX = setLastX and cur.x or cur.lastX
 | |
| 			if cur.y < 1 then
 | |
| 				cur.y = math.max(1, math.min(cur.y, #eldit.buffer))
 | |
| 				cur.x = 1
 | |
| 			elseif cur.y > #eldit.buffer then
 | |
| 				cur.y = math.max(1, math.min(cur.y, #eldit.buffer))
 | |
| 				cur.x = #eldit.buffer[cur.y] + 1
 | |
| 			else
 | |
| 				cur.y = math.max(1, math.min(cur.y, #eldit.buffer))
 | |
| 				cur.x = math.max(1, math.min(cur.x, #eldit.buffer[cur.y] + 1))
 | |
| 			end
 | |
| 
 | |
| 			if adjustSelections then
 | |
| 				for sid, sel in pairs(eldit.selections) do
 | |
| 
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 		removeRedundantCursors()
 | |
| 		if (not keysDown[keys.ctrl]) and not (xmod == 0 and ymod == 0) and not doNotDelSelections then
 | |
| 			eldit.selections = {}
 | |
| 			isSelecting = false
 | |
| 		end
 | |
| 		if (not isSelecting) and (not doNotTouchScroll) then
 | |
| 			scrollToCursor()
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- deletes the parts of the buffer that are selected, then clears the selection list
 | |
| 	local deleteSelections = function()
 | |
| 		sortSelections()
 | |
| 		if #eldit.selections == 0 then
 | |
| 			return {}, {}
 | |
| 		end
 | |
| 		local xAdjusts = {}
 | |
| 		local yAdjusts = {}
 | |
| 		local xAdj = 0
 | |
| 		local yAdj = 0
 | |
| 		for id,sel in pairs(eldit.selections) do
 | |
| 			for y = sel[1].y, sel[2].y do
 | |
| 				xAdj = 0
 | |
| 				if eldit.buffer[y] then
 | |
| 					xAdjusts[y] = xAdjusts[y] or {}
 | |
| 					if checkWithinArea(#eldit.buffer[y] + 1, y, sel[1].x, sel[1].y, sel[2].x, sel[2].y) then
 | |
| 						yAdj = yAdj + 1
 | |
| 					end
 | |
| 					yAdjusts[y + 1] = math.min(yAdjusts[y + 1] or math.huge, yAdj)
 | |
| 					for x = 2, #eldit.buffer[y] do
 | |
| 						xAdjusts[y][x] = math.min(xAdjusts[y][x] or math.huge, xAdj)
 | |
| 						if checkWithinArea(x, y, sel[1].x, sel[1].y, sel[2].x, sel[2].y) then
 | |
| 							xAdj = xAdj + 1
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 		for id,sel in pairs(eldit.selections) do
 | |
| 			for y = sel[2].y, sel[1].y, -1 do
 | |
| 				if eldit.buffer[y] then
 | |
| 					for x = #eldit.buffer[y] + 1, 1, -1 do
 | |
| 						if checkWithinArea(x, y, sel[1].x, sel[1].y, sel[2].x, sel[2].y) then
 | |
| 							if x == #eldit.buffer[y] + 1 then
 | |
| 								if eldit.buffer[y + 1] then
 | |
| 									for i = 1, #eldit.buffer[y + 1] do
 | |
| 										table.insert(eldit.buffer[y], eldit.buffer[y + 1][i])
 | |
| 									end
 | |
| 									table.remove(eldit.buffer, y + 1)
 | |
| 								end
 | |
| 							else
 | |
| 								deleteText("single", nil, x, y)
 | |
| 							end
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 		eldit.selections = {}
 | |
| 		adjustCursor(0, 0, true)
 | |
| 		return xAdjusts, yAdjusts
 | |
| 	end
 | |
| 
 | |
| 	-- puts text at every cursor position
 | |
| 	local placeText = function(text, cursorList)
 | |
| 		local xAdjusts, yAdjusts = deleteSelections()
 | |
| 		removeRedundantCursors()
 | |
| 		sortCursors()
 | |
| 		local xAdjList = {}
 | |
| 		for id,cur in pairs(cursorList or eldit.cursors) do
 | |
| 			cur.y = cur.y - (yAdjusts[cur.y] or 0)
 | |
| 			cur.x = cur.x - ((xAdjusts[cur.y] or {})[cur.x] or 0) + (xAdjList[cur.y] or 0)
 | |
| 			for i = 1, #text do
 | |
| 				if isInsert then
 | |
| 					if cur.x == #eldit.buffer[cur.y] + 1 then
 | |
| 						for i = 1, #eldit.buffer[cur.y + 1] do
 | |
| 							table.insert(eldit.buffer[cur.y], eldit.buffer[cur.y + 1][i])
 | |
| 						end
 | |
| 						table.remove(eldit.buffer, cur.y + 1)
 | |
| 					end
 | |
| 					eldit.buffer[cur.y][cur.x + i - 1] = text:sub(i,i)
 | |
| 				else
 | |
| 					table.insert(eldit.buffer[cur.y], cur.x, text:sub(i,i))
 | |
| 					if #xAdjusts + #yAdjusts == 0 then
 | |
| 						xAdjList[cur.y] = (xAdjList[cur.y] or 0) + 1
 | |
| 					end
 | |
| 				end
 | |
| 				cur.x = cur.x + 1
 | |
| 			end
 | |
| 			cur.lastX = cur.x
 | |
| 		end
 | |
| 		if not isSelecting then
 | |
| 			scrollToCursor()
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- adds a new line to the buffer at every cursor position
 | |
| 	local makeNewLine = function(cursorList)
 | |
| 		for id,cur in pairs(cursorList or eldit.cursors) do
 | |
| 			table.insert(eldit.buffer, cur.y + 1, {})
 | |
| 			for i = cur.x, #eldit.buffer[cur.y] do
 | |
| 				if i > cur.x or not isInsert then
 | |
| 					table.insert(eldit.buffer[cur.y + 1], eldit.buffer[cur.y][i])
 | |
| 				end
 | |
| 				eldit.buffer[cur.y][i] = nil
 | |
| 			end
 | |
| 			cur.x = 1
 | |
| 			cur.y = cur.y + 1
 | |
| 		end
 | |
| 		if not isSelecting then
 | |
| 			scrollToCursor()
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	local compareBuffers
 | |
| 	compareBuffers = function(left, right)
 | |
| 		for k,v in pairs(left) do
 | |
| 			if type(v) == "table" then
 | |
| 				if not compareBuffers(v, right[k]) then
 | |
| 					return false
 | |
| 				end
 | |
| 			elseif right then
 | |
| 				if left[k] ~= right[k] then
 | |
| 					return false
 | |
| 				end
 | |
| 			else
 | |
| 				return false
 | |
| 			end
 | |
| 		end
 | |
| 		return true
 | |
| 	end
 | |
| 
 | |
| 	-- simulate key inputs, for pre-entering text into read()
 | |
| 	local simType = function(text)
 | |
| 		for i = 1, #text do
 | |
| 			os.queueEvent("key", keys[text:sub(i,i)])
 | |
| 			os.queueEvent("char", text:sub(i,i))
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- saves to file, duhh
 | |
| 	local saveFile = function(preSaveAs)
 | |
| 		keysDown, miceDown = {}, {}
 | |
| 		local compiled = ""
 | |
| 		for y = 1, #eldit.buffer do
 | |
| 			compiled = compiled .. concatTable(eldit.buffer[y])
 | |
| 			if y < #eldit.buffer then
 | |
| 				compiled = compiled .. "\n"
 | |
| 			end
 | |
| 		end
 | |
| 		if preSaveAs or (not eldit.filename) then
 | |
| 			local newName, cx, cy = ""
 | |
| 			if type(preSaveAs) == "string" then
 | |
| 				simType(preSaveAs)
 | |
| 			end
 | |
| 			repeat
 | |
| 				render()
 | |
| 				term.setCursorPos(eldit.size.y, eldit.size.y + eldit.size.height - 1)
 | |
| 				eClearLine()
 | |
| 				term.setTextColor(colors.yellow)
 | |
| 				term.write("Save as: ")
 | |
| 				term.setTextColor(colors.white)
 | |
| 				cx, cy = term.getCursorPos()
 | |
| 				term.setCursorPos(cx, cy)
 | |
| 				newName = read()
 | |
| 				if tab[newName:sub(-1,-1)] then
 | |
| 					render()
 | |
| 					term.setCursorPos(eldit.size.y, eldit.size.y + eldit.size.height - 1)
 | |
| 					term.write("Path cannot have trailing space!")
 | |
| 					sleep(0.5)
 | |
| 					simType(newName)
 | |
| 				elseif fs.exists(newName) and newName ~= "" then
 | |
| 					render()
 | |
| 					term.setCursorPos(eldit.size.y, eldit.size.y + eldit.size.height - 1)
 | |
| 					eClearLine()
 | |
| 					if fs.isDir(newName) then
 | |
| 						term.write("Cannot overwrite a directory!")
 | |
| 						sleep(0.5)
 | |
| 						simType(newName)
 | |
| 					else
 | |
| 						term.write("Overwrite? (Y/N)")
 | |
| 						if choice("yn", nil, false) == "n" then
 | |
| 							barmsg = "Cancelled save."
 | |
| 							barlife = defaultBarLife
 | |
| 							return
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 			until (
 | |
| 				(not fs.isDir(newName) or newName == "") and
 | |
| 				(#newName:gsub(" ", "") > 0 or newName == "") and
 | |
| 				not tab[newName:sub(-1,-1)]
 | |
| 			)
 | |
| 			if newName == "" then
 | |
| 				barmsg = "Cancelled save."
 | |
| 				barlife = defaultBarLife
 | |
| 				return
 | |
| 			else
 | |
| 				eldit.filename = newName
 | |
| 			end
 | |
| 		end
 | |
| 		writeFile(eldit.filename, compiled)
 | |
| 		barmsg = "Saved to '" .. eldit.filename .. "'."
 | |
| 		barlife = defaultBarLife
 | |
| 	end
 | |
| 
 | |
| 	local evt
 | |
| 	local tID = os.startTimer(0.5)		-- timer for cursor blinking
 | |
| 	local bartID = os.startTimer(0.1)	-- timer for bar message to go away
 | |
| 	local undotID						-- timer for when the buffer is put in the undo buffer
 | |
| 	local doRender = true				-- if true, renders
 | |
| 
 | |
| 	-- converts numerical key events to usable numbers
 | |
| 	local numToKey = {
 | |
| 		-- number bar
 | |
| 		[2] = 1,
 | |
| 		[3] = 2,
 | |
| 		[4] = 3,
 | |
| 		[5] = 4,
 | |
| 		[6] = 5,
 | |
| 		[7] = 6,
 | |
| 		[8] = 7,
 | |
| 		[9] = 8,
 | |
| 		[10] = 9,
 | |
| 		[11] = 0,
 | |
| 		-- number pad
 | |
| 		[79] = 1,
 | |
| 		[80] = 2,
 | |
| 		[81] = 3,
 | |
| 		[75] = 4,
 | |
| 		[76] = 5,
 | |
| 		[77] = 6,
 | |
| 		[71] = 7,
 | |
| 		[72] = 8,
 | |
| 		[73] = 9,
 | |
| 		[82] = 0,
 | |
| 	}
 | |
| 
 | |
| 	local startedSelecting = false
 | |
| 
 | |
| 	-- here we go my man
 | |
| 	scrollToCursor()
 | |
| 	while true do
 | |
| 		evt = {os.pullEvent()}
 | |
| 		repeat
 | |
| 			if evt[1] == "timer" then
 | |
| 				if evt[2] == tID then
 | |
| 					if isCursorBlink then
 | |
| 						tID = os.startTimer(0.4)
 | |
| 					else
 | |
| 						tID = os.startTimer(0.3)
 | |
| 					end
 | |
| 					isCursorBlink = not isCursorBlink
 | |
| 					doRender = true
 | |
| 				elseif evt[2] == bartID then
 | |
| 					bartID = os.startTimer(0.1)
 | |
| 					barlife = math.max(0, barlife - 1)
 | |
| 				elseif evt[2] == undotID then
 | |
| 					if not compareBuffers(eldit.buffer, eldit.undoBuffer[#eldit.undoBuffer].buffer or {}) then
 | |
| 						if #eldit.undoBuffer >= eldit.maxUndo then
 | |
| 							repeat
 | |
| 								table.remove(eldit.undoBuffer, 1)
 | |
| 							until #eldit.undoBuffer < eldit.maxUndo
 | |
| 						end
 | |
| 						if eldit.undoPos < #eldit.undoBuffer then
 | |
| 							repeat
 | |
| 								table.remove(eldit.undoBuffer, 0)
 | |
| 							until eldit.undoPos == #eldit.undoBuffer
 | |
| 						end
 | |
| 						eldit.undoPos = math.min(eldit.undoPos + 1, eldit.maxUndo)
 | |
| 						table.insert(eldit.undoBuffer, {
 | |
| 							buffer = deepCopy(eldit.buffer),
 | |
| 							cursors = deepCopy(eldit.cursors),
 | |
| 							selections = deepCopy(eldit.selections),
 | |
| 						})
 | |
| 					end
 | |
| 				end
 | |
| 			elseif (evt[1] == "char" and not keysDown[keys.ctrl]) then
 | |
| 				placeText(evt[2])
 | |
| 				if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 				doRender = true
 | |
| 			elseif evt[1] == "paste" then
 | |
| 				if keysDown[keys.shift] then
 | |
| 					local cb = eldit.clipboards[eldit.selectedClipboard]
 | |
| 					local cbb = {}
 | |
| 					if cb then
 | |
| 						deleteSelections()
 | |
| 						sortCursors()
 | |
| 						for i = 1, math.max(#cb, #eldit.cursors) do
 | |
| 							cbb[i] = cb[(i % #cb) + 1]
 | |
| 						end
 | |
| 						for i = 1, #cbb do
 | |
| 							if eldit.cursors[i] then
 | |
| 								for y = 1, #cbb[i] do
 | |
| 									placeText(concatTable(cbb[i][y]), {eldit.cursors[i]})
 | |
| 									if y < #cbb[i] then
 | |
| 										makeNewLine({eldit.cursors[i]})
 | |
| 									end
 | |
| 								end
 | |
| 							else
 | |
| 								makeNewLine({eldit.cursors[#eldit.cursors]})
 | |
| 								for y = 1, #cbb[i] do
 | |
| 									placeText(concatTable(cbb[i][y]), {eldit.cursors[#eldit.cursors]})
 | |
| 									if y < #cbb[i] then
 | |
| 										makeNewLine({eldit.cursors[#eldit.cursors]})
 | |
| 									end
 | |
| 								end
 | |
| 							end
 | |
| 						end
 | |
| 						barmsg = "Pasted from clipboard " .. eldit.selectedClipboard .. "."
 | |
| 						if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 					else
 | |
| 						barmsg = "Clipboard " .. eldit.selectedClipboard .. " is empty."
 | |
| 					end
 | |
| 					barlife = defaultBarLife
 | |
| 				else
 | |
| 					placeText(evt[2])
 | |
| 					if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 				end
 | |
| 				doRender = true
 | |
| 			elseif evt[1] == "key" then
 | |
| 				keysDown[evt[2]] = true
 | |
| 
 | |
| 				keysDown[keys.shift] = keysDown[keys.leftShift] or keysDown[keys.rightShift]
 | |
| 				keysDown[keys.alt] = keysDown[keys.leftAlt] or keysDown[keys.rightAlt]
 | |
| 				keysDown[keys.ctrl] = keysDown[keys.leftCtrl] or keysDown[keys.rightCtrl]
 | |
| 
 | |
| 
 | |
| 				-- KEYBOARD SHORTCUTS
 | |
| 				if keysDown[keys.ctrl] then
 | |
| 
 | |
| 					if keysDown[keys.shift] then
 | |
| 						if evt[2] == keys.c or evt[2] == keys.x then
 | |
| 							doRender = true
 | |
| 							if #eldit.selections == 0 then
 | |
| 								barmsg = "No selections have been made."
 | |
| 								barlife = defaultBarLife
 | |
| 							else
 | |
| 								eldit.clipboards[eldit.selectedClipboard] = {}
 | |
| 								local cb = eldit.clipboards[eldit.selectedClipboard]
 | |
| 								sortSelections()
 | |
| 								local id, selY
 | |
| 								for y = 1, #eldit.buffer do
 | |
| 									for x = 1, #eldit.buffer[y] + 1 do
 | |
| 										id = checkIfSelected(x, y)
 | |
| 										if id then
 | |
| 											selY = y - eldit.selections[id][1].y + 1
 | |
| 											cb[id] = cb[id] or {}
 | |
| 											cb[id][selY] = cb[id][selY] or {}
 | |
| 											table.insert(cb[id][selY], eldit.buffer[y][x])
 | |
| 										end
 | |
| 									end
 | |
| 								end
 | |
| 								if evt[2] == keys.x then
 | |
| 									deleteSelections()
 | |
| 									barmsg = "Cut to clipboard " .. eldit.selectedClipboard .. "."
 | |
| 									barlife = defaultBarLife
 | |
| 									if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 								else
 | |
| 									barmsg = "Copied to clipboard " .. eldit.selectedClipboard .. "."
 | |
| 									barlife = defaultBarLife
 | |
| 								end
 | |
| 							end
 | |
| 
 | |
| 						elseif evt[2] == keys.z then
 | |
| 							if eldit.undoPos < #eldit.undoBuffer then
 | |
| 								eldit.undoPos = math.min(#eldit.undoBuffer, eldit.maxUndo, eldit.undoPos + 1)
 | |
| 								eldit.selections = deepCopy(eldit.undoBuffer[eldit.undoPos].selections)
 | |
| 								eldit.cursors = deepCopy(eldit.undoBuffer[eldit.undoPos].cursors)
 | |
| 								eldit.buffer = deepCopy(eldit.undoBuffer[eldit.undoPos].buffer)
 | |
| 								adjustCursor(0, 0, true)
 | |
| 								barmsg = "Redone. (" .. eldit.undoPos .. "/" .. #eldit.undoBuffer .. ")"
 | |
| 								barlife = defaultBarLife
 | |
| 							else
 | |
| 								barmsg = "Reached top of undo buffer. (" .. eldit.undoPos .. "/" .. #eldit.undoBuffer .. ")"
 | |
| 								barlife = defaultBarLife
 | |
| 							end
 | |
| 							doRender = true
 | |
| 
 | |
| 						elseif evt[2] == keys.s then
 | |
| 							saveFile(eldit.filename)
 | |
| 							tID = os.startTimer(0.4)
 | |
| 							bartID = os.startTimer(0.1)
 | |
| 							doRender = true
 | |
| 
 | |
| 						end
 | |
| 						-- In-editor pasting is done with the "paste" event!
 | |
| 					else
 | |
| 						if numToKey[evt[2]] then -- if that's a number then
 | |
| 							eldit.selectedClipboard = numToKey[evt[2]]
 | |
| 							barmsg = "Selected clipboard " .. eldit.selectedClipboard
 | |
| 							if eldit.clipboards[eldit.selectedClipboard] then
 | |
| 								barmsg = barmsg .. ": " .. table.concat(eldit.clipboards[eldit.selectedClipboard][1][1])
 | |
| 							else
 | |
| 								barmsg = barmsg .. "."
 | |
| 							end
 | |
| 							barlife = defaultBarLife
 | |
| 							doRender = true
 | |
| 
 | |
| 						elseif evt[2] == keys.rightBracket then
 | |
| 							indentLines(false)
 | |
| 							doRender, isCursorBlink = true, true
 | |
| 
 | |
| 						elseif evt[2] == keys.leftBracket then
 | |
| 							indentLines(true)
 | |
| 							doRender, isCursorBlink = true, true
 | |
| 
 | |
| 						elseif evt[2] == keys.backspace then
 | |
| 							if #eldit.selections > 0 then
 | |
| 								deleteSelections()
 | |
| 							else
 | |
| 								deleteText("word", "backward")
 | |
| 							end
 | |
| 							if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 							doRender, isCursorBlink = true, false
 | |
| 
 | |
| 						elseif evt[2] == keys.delete then
 | |
| 							if #eldit.selections > 0 then
 | |
| 								deleteSelections()
 | |
| 							else
 | |
| 								deleteText("word", "forward")
 | |
| 							end
 | |
| 							if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 							doRender, isCursorBlink = true, false
 | |
| 
 | |
| 						elseif evt[2] == keys.q then
 | |
| 							return "exit"
 | |
| 
 | |
| 						elseif evt[2] == keys.s then
 | |
| 							saveFile(false)
 | |
| 							tID = os.startTimer(0.4)
 | |
| 							bartID = os.startTimer(0.1)
 | |
| 							doRender = true
 | |
| 
 | |
| 						elseif evt[2] == keys.a then
 | |
| 							eldit.selections = {{
 | |
| 								{
 | |
| 									x = 1,
 | |
| 									y = 1
 | |
| 								},{
 | |
| 									x = #eldit.buffer[#eldit.buffer],
 | |
| 									y = #eldit.buffer
 | |
| 								}
 | |
| 							}}
 | |
| 							doRender = true
 | |
| 
 | |
| 						elseif evt[2] == keys.z then
 | |
| 
 | |
| 							if eldit.undoPos > 1 then
 | |
| 								eldit.undoPos = math.max(1, eldit.undoPos - 1)
 | |
| 								eldit.selections = deepCopy(eldit.undoBuffer[eldit.undoPos].selections)
 | |
| 								eldit.cursors = deepCopy(eldit.undoBuffer[eldit.undoPos].cursors)
 | |
| 								eldit.buffer = deepCopy(eldit.undoBuffer[eldit.undoPos].buffer)
 | |
| 								adjustCursor(0, 0, true)
 | |
| 								barmsg = "Undone. (" .. eldit.undoPos .. "/" .. #eldit.undoBuffer .. ")"
 | |
| 								barlife = defaultBarLife
 | |
| 							else
 | |
| 								barmsg = "Reached back of undo buffer."
 | |
| 								barlife = defaultBarLife
 | |
| 							end
 | |
| 							doRender = true
 | |
| 
 | |
| 						elseif evt[2] == keys.left then
 | |
| 							adjustCursor(-1, 0, true, "word", false, keysDown[keys.shift])
 | |
| 							doRender, isCursorBlink = true, true
 | |
| 							eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
 | |
| 							eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
 | |
| 
 | |
| 						elseif evt[2] == keys.right then
 | |
| 							adjustCursor(1, 0, true, "word", false, keysDown[keys.shift])
 | |
| 							doRender, isCursorBlink = true, true
 | |
| 							eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
 | |
| 							eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
 | |
| 
 | |
| 						elseif evt[2] == keys.up then
 | |
| 							adjustCursor(0, -1, false, "flip")
 | |
| 							doRender, isCursorBlink = true, true
 | |
| 							if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 							eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
 | |
| 							eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
 | |
| 
 | |
| 						elseif evt[2] == keys.down then
 | |
| 							adjustCursor(0, 1, false, "flip")
 | |
| 							doRender, isCursorBlink = true, true
 | |
| 							if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 							eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
 | |
| 							eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
 | |
| 
 | |
| 						end
 | |
| 					end
 | |
| 
 | |
| 				else
 | |
| 
 | |
| 					if evt[2] == keys.tab then
 | |
| 						if keysDown[keys.shift] then
 | |
| 							indentLines(true)
 | |
| 						elseif #eldit.selections > 0 then
 | |
| 							indentLines(false)
 | |
| 						else
 | |
| 							placeText("\9")
 | |
| 						end
 | |
| 						doRender = true
 | |
| 						if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 
 | |
| 					elseif evt[2] == keys.insert then
 | |
| 						isInsert = not isInsert
 | |
| 						doRender, isCursorBlink = true, true
 | |
| 
 | |
| 					elseif evt[2] == keys.enter then
 | |
| 						deleteSelections()
 | |
| 						makeNewLine()
 | |
| 						doRender, isCursorBlink = true, true
 | |
| 						if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 
 | |
| 					elseif evt[2] == keys.home then
 | |
| 						eldit.cursors = {{
 | |
| 							x = 1,
 | |
| 							y = eldit.cursors[1].y,
 | |
| 							lastX = 1
 | |
| 						}}
 | |
| 						scrollToCursor()
 | |
| 						doRender, isCursorBlink = true, true
 | |
| 
 | |
| 					elseif evt[2] == keys["end"] then
 | |
| 						eldit.cursors = {{
 | |
| 							x = 1 + #eldit.buffer[eldit.cursors[1].y],
 | |
| 							y = eldit.cursors[1].y,
 | |
| 							lastX = 1 + #eldit.buffer[eldit.cursors[1].y]
 | |
| 						}}
 | |
| 						scrollToCursor()
 | |
| 						doRender, isCursorBlink = true, true
 | |
| 
 | |
| 					elseif evt[2] == keys.pageUp then
 | |
| 						adjustScroll(0, -eldit.size.height)
 | |
| 						if isSelecting then
 | |
| 							os.queueEvent("mouse_drag", 1, (miceDown[1] or miceDown[2]).x, (miceDown[1] or miceDown[2]).y)
 | |
| 						end
 | |
| 						doRender = true
 | |
| 
 | |
| 					elseif evt[2] == keys.pageDown then
 | |
| 						adjustScroll(0, eldit.size.height)
 | |
| 						if isSelecting then
 | |
| 							os.queueEvent("mouse_drag", 1, (miceDown[1] or miceDown[2]).x, (miceDown[1] or miceDown[2]).y)
 | |
| 						end
 | |
| 						doRender = true
 | |
| 
 | |
| 					elseif evt[2] == keys.backspace then
 | |
| 						if #eldit.selections > 0 then
 | |
| 							deleteSelections()
 | |
| 						else
 | |
| 							deleteText("single", "backward")
 | |
| 						end
 | |
| 						doRender, isCursorBlink = true, false
 | |
| 						if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 
 | |
| 					elseif evt[2] == keys.delete then
 | |
| 						if #eldit.selections > 0 then
 | |
| 							deleteSelections()
 | |
| 						else
 | |
| 							deleteText("single", "forward")
 | |
| 						end
 | |
| 						doRender, isCursorBlink = true, false
 | |
| 						if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
 | |
| 
 | |
| 					elseif evt[2] == keys.left then
 | |
| 						adjustCursor(-1, 0, true, nil, false, keysDown[keys.shift])
 | |
| 						eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
 | |
| 						eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
 | |
| 						doRender, isCursorBlink = true, true
 | |
| 
 | |
| 					elseif evt[2] == keys.right then
 | |
| 						adjustCursor(1, 0, true, nil, false, keysDown[keys.shift])
 | |
| 						eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
 | |
| 						eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
 | |
| 						doRender, isCursorBlink = true, true
 | |
| 
 | |
| 					elseif evt[2] == keys.up then
 | |
| 						adjustCursor(0, -1, false, nil, false, keysDown[keys.shift])
 | |
| 						eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
 | |
| 						eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
 | |
| 						doRender, isCursorBlink = true, true
 | |
| 
 | |
| 					elseif evt[2] == keys.down then
 | |
| 						adjustCursor(0, 1, false, nil, false, keysDown[keys.shift])
 | |
| 						doRender, isCursorBlink = true, true
 | |
| 						eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
 | |
| 						eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
 | |
| 					end
 | |
| 
 | |
| 				end
 | |
| 			elseif evt[1] == "key_up" then
 | |
| 				keysDown[evt[2]] = nil
 | |
| 
 | |
| 				keysDown[keys.shift] = keysDown[keys.leftShift] or keysDown[keys.rightShift]
 | |
| 				keysDown[keys.alt] = keysDown[keys.leftAlt] or keysDown[keys.rightAlt]
 | |
| 				keysDown[keys.ctrl] = keysDown[keys.leftCtrl] or keysDown[keys.rightCtrl]
 | |
| 			elseif evt[1] == "mouse_click" then
 | |
| 				local lineNoLen = getLineNoLen()
 | |
| 				startedSelecting = false
 | |
| 				miceDown[evt[2]] = {x = evt[3], y = evt[4]}
 | |
| 				if evt[4] == -1 + eldit.size.y + eldit.size.height then
 | |
| 
 | |
| 				else
 | |
| 					if keysDown[keys.ctrl] and (
 | |
| 						not checkIfSelected(
 | |
| 							math.min(
 | |
| 								evt[3] + eldit.scrollX - lineNoLen,
 | |
| 								#eldit.buffer[evt[4] + eldit.scrollY] + 1
 | |
| 							),
 | |
| 							evt[4] + eldit.scrollY
 | |
| 						)
 | |
| 					) then
 | |
| 						table.insert(eldit.cursors, {
 | |
| 							x = evt[3] + eldit.scrollX - lineNoLen,
 | |
| 							y = evt[4] + eldit.scrollY,
 | |
| 							lastX = evt[3] + eldit.scrollX - lineNoLen
 | |
| 						})
 | |
| 						startedSelecting = true
 | |
| 					else
 | |
| 						eldit.cursors = {{
 | |
| 							x = evt[3] + eldit.scrollX - lineNoLen,
 | |
| 							y = evt[4] + eldit.scrollY,
 | |
| 							lastX = evt[3] + eldit.scrollX - lineNoLen
 | |
| 						}}
 | |
| 						eldit.selections = {}
 | |
| 					end
 | |
| 					lastMouse = {
 | |
| 						x = evt[3],
 | |
| 						y = evt[4],
 | |
| 						scrollX = eldit.scrollX,
 | |
| 						scrollY = eldit.scrollY,
 | |
| 						lineNoLen = lineNoLen,
 | |
| 						ctrl = keysDown[keys.ctrl],
 | |
| 						curID = #eldit.cursors,
 | |
| 					}
 | |
| 					sortSelections()
 | |
| 					adjustCursor(0, 0, true, nil, nil, nil, startedSelecting)
 | |
| 					eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
 | |
| 					eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
 | |
| 				end
 | |
| 				doRender = true
 | |
| 			elseif evt[1] == "mouse_drag" then
 | |
| 				if evt[4] == -1 + eldit.size.y + eldit.size.height then
 | |
| 
 | |
| 				else
 | |
| 					local lineNoLen = getLineNoLen()
 | |
| 					local lastMX, lastMY
 | |
| 					if miceDown[evt[2]] then
 | |
| 						lastMX, lastMY = miceDown[evt[2]].x, miceDown[evt[2]].y
 | |
| 					else
 | |
| 						lastMX, lastMY = evt[3], evt[4]
 | |
| 					end
 | |
| 					miceDown[evt[2]] = {x = evt[3], y = evt[4]}
 | |
| 					if lastMouse.x and lastMouse.y and lastMouse.curID then
 | |
| 						local adjMY = lastMouse.y + lastMouse.scrollY
 | |
| 						local adjMX = math.min(lastMouse.x + lastMouse.scrollX, #(eldit.buffer[adjMY] or "") + 1)
 | |
| 						local adjEY = evt[4] + eldit.scrollY
 | |
| 						local adjEX = math.min(evt[3] + eldit.scrollX, #(eldit.buffer[adjEY] or "") + 1)
 | |
| 						local selID
 | |
| 						local cSelID = checkIfSelected(adjMX, adjMY)
 | |
| 						if (lastMouse.ctrl and not isSelecting) or #eldit.selections == 0 then
 | |
| 							selID = cSelID or (1 + #eldit.selections)
 | |
| 						else
 | |
| 							selID = #eldit.selections
 | |
| 						end
 | |
| 						if cSelID and not (eldit.selections[cSelID][1].x == adjMX and eldit.selections[cSelID][1].y == adjMY) then
 | |
| 							for id,cur in pairs(eldit.cursors) do
 | |
| 								if cur.x == eldit.selections[cSelID][1].x and cur.y == eldit.selections[cSelID][1].y then
 | |
| 									table.remove(eldit.cursors, id)
 | |
| 									break
 | |
| 								end
 | |
| 							end
 | |
| 							eldit.selections[cSelID][1] = {
 | |
| 								x = math.min(adjMX, #(eldit.buffer[adjMY] or "") + lineNoLen) - lineNoLen,
 | |
| 								y = adjMY
 | |
| 							}
 | |
| 						end
 | |
| 						eldit.selections[selID] = {
 | |
| 							{
 | |
| 								x = math.min(adjMX, #(eldit.buffer[adjMY] or "") + lineNoLen) - lineNoLen,
 | |
| 								y = adjMY
 | |
| 							},
 | |
| 							{
 | |
| 								x = math.min(adjEX, #(eldit.buffer[adjEY] or "") + lineNoLen) - lineNoLen,
 | |
| 								y = adjEY
 | |
| 							}
 | |
| 						}
 | |
| 						sortSelections()
 | |
| 						eldit.cursors[lastMouse.curID] = {
 | |
| 							x = eldit.selections[selID][2].x,
 | |
| 							y = eldit.selections[selID][2].y,
 | |
| 							lastX = eldit.selections[selID][1].x
 | |
| 						}
 | |
| 
 | |
| 						isSelecting = true
 | |
| 						adjustCursor(0, 0)
 | |
| 						eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
 | |
| 						eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
 | |
| 					end
 | |
| 					doRender = true
 | |
| 				end
 | |
| 			elseif evt[1] == "mouse_up" then
 | |
| 				miceDown[evt[2]] = nil
 | |
| 				isSelecting = false
 | |
| 				sortSelections()
 | |
| 			elseif evt[1] == "mouse_scroll" then
 | |
| 				if keysDown[keys.alt] then
 | |
| 					adjustScroll(((keysDown[keys.ctrl] and not (isSelecting or startedSelecting)) and eldit.size.width or 1) * evt[2], 0)
 | |
| 				else
 | |
| 					adjustScroll(0, ((keysDown[keys.ctrl] and not (isSelecting or startedSelecting)) and eldit.size.height or 1) * evt[2])
 | |
| 				end
 | |
| 				if isSelecting then
 | |
| 					os.queueEvent("mouse_drag", 1, evt[3], evt[4])
 | |
| 				end
 | |
| 				doRender = true
 | |
| 			end
 | |
| 			if doRender then
 | |
| 				if not (evt[1] == "mouse_scroll" and isSelecting) then
 | |
| 					render()
 | |
| 					doRender = false
 | |
| 				end
 | |
| 			end
 | |
| 		until true
 | |
| 	end
 | |
| end
 | |
| 
 | |
| local contents = eldit.filename and readFile(eldit.filename) or nil
 | |
| 
 | |
| local result = {prompt(contents)}
 | |
| if result[1] == "exit" then
 | |
| 	term.setBackgroundColor(colors.black)
 | |
| 	term.scroll(1)
 | |
| 	term.setCursorPos(1, scr_y)
 | |
| end
 |