Improved cursor navigation with keyboard

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.
This commit is contained in:
LDDestroier 2019-10-28 03:37:13 -04:00 committed by GitHub
parent 9497203bf2
commit 3c075902e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 181 additions and 44 deletions

225
eldit.lua
View File

@ -1,6 +1,18 @@
-- Eldit (still being made) --[[
-- by LDDestroier Eldit (still being made)
-- wget https://raw.githubusercontent.com/LDDestroier/CC/master/eldit.lua 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 scr_x, scr_y = term.getSize()
@ -27,9 +39,15 @@ eldit.size = {
height = scr_y -- vertical 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.showLineNumberIndicator = false
config.showWhitespace = true config.showWhitespace = true
config.showTrailingSpace = true config.showTrailingSpace = true
config.findExtension = true
-- minor optimizations, I think -- minor optimizations, I think
local concatTable = table.concat local concatTable = table.concat
@ -77,7 +95,28 @@ end
local argList = interpretArgs({...}, argData) local argList = interpretArgs({...}, argData)
eldit.filename = argList[1] 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 = {{ eldit.cursors = {{
x = 1, x = 1,
y = math.max(1, argList["-l"] or 1), y = math.max(1, argList["-l"] or 1),
@ -302,7 +341,7 @@ prompt = function(prebuffer, precy, maxY, _eldit)
[" "] = true, [" "] = true,
["\9"] = true ["\9"] = true
} }
-- the big boi function, draws **EVERYTHING** -- the big boi function, draws **EVERYTHING**
local render = function() local render = function()
local cx, cy local cx, cy
@ -358,7 +397,7 @@ prompt = function(prebuffer, precy, maxY, _eldit)
if checkIfCursor(cx, cy) and isCursorBlink then if checkIfCursor(cx, cy) and isCursorBlink then
if isInsert then if isInsert then
cTxt, cBg = "0", "f" cTxt, cBg = "8", "0"
else else
cTxt, cBg = "f", "8" cTxt, cBg = "f", "8"
end end
@ -496,10 +535,17 @@ prompt = function(prebuffer, precy, maxY, _eldit)
local xAdjList = {} local xAdjList = {}
local yAdj = 0 local yAdj = 0
sortCursors() sortCursors()
local rowBuff -- represents the buffer row at the current cursor's Y
local startOnInterruptable
for id,cur in pairs(eldit.cursors) do for id,cur in pairs(eldit.cursors) do
cx = _cx or cur.x - (xAdjList[_cy or cur.y] or 0) cx = _cx or cur.x - (xAdjList[_cy or cur.y] or 0)
cy = _cy or cur.y - yAdj 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 mode == "single" or (direction == "forward" and cx == #eldit.buffer[cy] or (direction == "backward" and cx == 1)) then
if direction == "forward" then if direction == "forward" then
if cx < #eldit.buffer[cy] then if cx < #eldit.buffer[cy] then
@ -540,17 +586,43 @@ prompt = function(prebuffer, precy, maxY, _eldit)
elseif mode == "word" then elseif mode == "word" then
local pos = cx local pos = cx
if direction == "forward" then if direction == "forward" then
repeat while true do
pos = pos + 1 pos = pos + 1
until interruptable[eldit.buffer[cy][pos]] or pos >= #eldit.buffer[cy] 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 for i = pos, cx, -1 do
xAdjList[cy] = (xAdjList[cy] or 0) + 1 xAdjList[cy] = (xAdjList[cy] or 0) + 1
table.remove(eldit.buffer[cy], i) table.remove(eldit.buffer[cy], i)
end end
else else
repeat while true do
pos = pos - 1 pos = pos - 1
until interruptable[eldit.buffer[cy][pos]] or 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) pos = math.max(1, pos)
for i = cx - 1, pos, -1 do for i = cx - 1, pos, -1 do
table.remove(eldit.buffer[cy], i) table.remove(eldit.buffer[cy], i)
@ -642,14 +714,38 @@ prompt = function(prebuffer, precy, maxY, _eldit)
end end
-- moves the cursor by (xmod, ymod), and fixes its position if it's set to an invalid one -- 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) 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 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 if mode == "word" then
xmod = (_xmod / math.abs(_xmod)) xmod = step
ymod = 0 ymod = 0
repeat while true do
xmod = xmod + (_xmod / math.abs(_xmod)) xmod = xmod + step
until interruptable[eldit.buffer[cur.y][cur.x + xmod]] or cur.x + xmod >= #eldit.buffer[cur.y] or cur.x + xmod <= 1 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)) xmod = xmod - math.min(0, math.max(xmod, -1))
else else
xmod = _xmod xmod = _xmod
@ -668,7 +764,8 @@ prompt = function(prebuffer, precy, maxY, _eldit)
cur.y = cur.y - 1 cur.y = cur.y - 1
cur.x = cur.x + #eldit.buffer[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 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 = cur.x - #eldit.buffer[cur.y] - 1
cur.x = 1
cur.y = cur.y + 1 cur.y = cur.y + 1
end 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)) 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))
@ -684,13 +781,19 @@ prompt = function(prebuffer, precy, maxY, _eldit)
cur.y = math.max(1, math.min(cur.y, #eldit.buffer)) 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)) cur.x = math.max(1, math.min(cur.x, #eldit.buffer[cur.y] + 1))
end end
if adjustSelections then
for sid, sel in pairs(eldit.selections) do
end
end
end end
removeRedundantCursors() removeRedundantCursors()
if (not keysDown[keys.leftCtrl]) and not (xmod == 0 and ymod == 0) and not doNotDelSelections then if (not keysDown[keys.ctrl]) and not (xmod == 0 and ymod == 0) and not doNotDelSelections then
eldit.selections = {} eldit.selections = {}
isSelecting = false isSelecting = false
end end
if not isSelecting then if (not isSelecting) and (not doNotTouchScroll) then
scrollToCursor() scrollToCursor()
end end
end end
@ -707,6 +810,7 @@ prompt = function(prebuffer, precy, maxY, _eldit)
local yAdj = 0 local yAdj = 0
for id,sel in pairs(eldit.selections) do for id,sel in pairs(eldit.selections) do
for y = sel[1].y, sel[2].y do for y = sel[1].y, sel[2].y do
xAdj = 0
if eldit.buffer[y] then if eldit.buffer[y] then
xAdjusts[y] = xAdjusts[y] or {} 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 if checkWithinArea(#eldit.buffer[y] + 1, y, sel[1].x, sel[1].y, sel[2].x, sel[2].y) then
@ -922,6 +1026,8 @@ prompt = function(prebuffer, precy, maxY, _eldit)
[82] = 0, [82] = 0,
} }
local startedSelecting = false
-- here we go my man -- here we go my man
scrollToCursor() scrollToCursor()
while true do while true do
@ -959,12 +1065,12 @@ prompt = function(prebuffer, precy, maxY, _eldit)
}) })
end end
end end
elseif (evt[1] == "char" and not keysDown[keys.leftCtrl]) then elseif (evt[1] == "char" and not keysDown[keys.ctrl]) then
placeText(evt[2]) placeText(evt[2])
if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
doRender = true doRender = true
elseif evt[1] == "paste" then elseif evt[1] == "paste" then
if keysDown[keys.leftShift] then if keysDown[keys.shift] then
local cb = eldit.clipboards[eldit.selectedClipboard] local cb = eldit.clipboards[eldit.selectedClipboard]
local cbb = {} local cbb = {}
if cb then if cb then
@ -1004,10 +1110,16 @@ prompt = function(prebuffer, precy, maxY, _eldit)
doRender = true doRender = true
elseif evt[1] == "key" then elseif evt[1] == "key" then
keysDown[evt[2]] = true keysDown[evt[2]] = true
-- KEYBOARD SHORTCUTS
if keysDown[keys.leftCtrl] or keysDown[keys.rightCtrl] then
if keysDown[keys.leftShift] or keysDown[keys.rightShift] then 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 if evt[2] == keys.c or evt[2] == keys.x then
doRender = true doRender = true
if #eldit.selections == 0 then if #eldit.selections == 0 then
@ -1054,7 +1166,7 @@ prompt = function(prebuffer, precy, maxY, _eldit)
barlife = defaultBarLife barlife = defaultBarLife
end end
doRender = true doRender = true
elseif evt[2] == keys.s then elseif evt[2] == keys.s then
saveFile(eldit.filename) saveFile(eldit.filename)
tID = os.startTimer(0.4) tID = os.startTimer(0.4)
@ -1066,18 +1178,23 @@ prompt = function(prebuffer, precy, maxY, _eldit)
else else
if numToKey[evt[2]] then -- if that's a number then if numToKey[evt[2]] then -- if that's a number then
eldit.selectedClipboard = numToKey[evt[2]] eldit.selectedClipboard = numToKey[evt[2]]
barmsg = "Selected clipboard " .. eldit.selectedClipboard .. "." 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 barlife = defaultBarLife
doRender = true doRender = true
elseif evt[2] == keys.rightBracket then elseif evt[2] == keys.rightBracket then
indentLines(false) indentLines(false)
doRender, isCursorBlink = true, true doRender, isCursorBlink = true, true
elseif evt[2] == keys.leftBracket then elseif evt[2] == keys.leftBracket then
indentLines(true) indentLines(true)
doRender, isCursorBlink = true, true doRender, isCursorBlink = true, true
elseif evt[2] == keys.backspace then elseif evt[2] == keys.backspace then
if #eldit.selections > 0 then if #eldit.selections > 0 then
deleteSelections() deleteSelections()
@ -1134,13 +1251,13 @@ prompt = function(prebuffer, precy, maxY, _eldit)
doRender = true doRender = true
elseif evt[2] == keys.left then elseif evt[2] == keys.left then
adjustCursor(-1, 0, true, "word") adjustCursor(-1, 0, true, "word", false, keysDown[keys.shift])
doRender, isCursorBlink = true, true doRender, isCursorBlink = true, true
eldit.undoBuffer[eldit.undoPos].selections = eldit.selections eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
elseif evt[2] == keys.right then elseif evt[2] == keys.right then
adjustCursor(1, 0, true, "word") adjustCursor(1, 0, true, "word", false, keysDown[keys.shift])
doRender, isCursorBlink = true, true doRender, isCursorBlink = true, true
eldit.undoBuffer[eldit.undoPos].selections = eldit.selections eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
@ -1165,7 +1282,7 @@ prompt = function(prebuffer, precy, maxY, _eldit)
else else
if evt[2] == keys.tab then if evt[2] == keys.tab then
if keysDown[keys.leftShift] then if keysDown[keys.shift] then
indentLines(true) indentLines(true)
elseif #eldit.selections > 0 then elseif #eldit.selections > 0 then
indentLines(false) indentLines(false)
@ -1205,10 +1322,16 @@ prompt = function(prebuffer, precy, maxY, _eldit)
elseif evt[2] == keys.pageUp then elseif evt[2] == keys.pageUp then
adjustScroll(0, -eldit.size.height) 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 doRender = true
elseif evt[2] == keys.pageDown then elseif evt[2] == keys.pageDown then
adjustScroll(0, eldit.size.height) 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 doRender = true
elseif evt[2] == keys.backspace then elseif evt[2] == keys.backspace then
@ -1230,25 +1353,25 @@ prompt = function(prebuffer, precy, maxY, _eldit)
if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end if eldit.allowUndo then undotID = os.startTimer(eldit.undoDelay) end
elseif evt[2] == keys.left then elseif evt[2] == keys.left then
adjustCursor(-1, 0, true) adjustCursor(-1, 0, true, nil, false, keysDown[keys.shift])
eldit.undoBuffer[eldit.undoPos].selections = eldit.selections eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
doRender, isCursorBlink = true, true doRender, isCursorBlink = true, true
elseif evt[2] == keys.right then elseif evt[2] == keys.right then
adjustCursor(1, 0, true) adjustCursor(1, 0, true, nil, false, keysDown[keys.shift])
eldit.undoBuffer[eldit.undoPos].selections = eldit.selections eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
doRender, isCursorBlink = true, true doRender, isCursorBlink = true, true
elseif evt[2] == keys.up then elseif evt[2] == keys.up then
adjustCursor(0, -1, false) adjustCursor(0, -1, false, nil, false, keysDown[keys.shift])
eldit.undoBuffer[eldit.undoPos].selections = eldit.selections eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
doRender, isCursorBlink = true, true doRender, isCursorBlink = true, true
elseif evt[2] == keys.down then elseif evt[2] == keys.down then
adjustCursor(0, 1, false) adjustCursor(0, 1, false, nil, false, keysDown[keys.shift])
doRender, isCursorBlink = true, true doRender, isCursorBlink = true, true
eldit.undoBuffer[eldit.undoPos].selections = eldit.selections eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
@ -1257,18 +1380,32 @@ prompt = function(prebuffer, precy, maxY, _eldit)
end end
elseif evt[1] == "key_up" then elseif evt[1] == "key_up" then
keysDown[evt[2]] = nil 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 elseif evt[1] == "mouse_click" then
local lineNoLen = getLineNoLen() local lineNoLen = getLineNoLen()
startedSelecting = false
miceDown[evt[2]] = {x = evt[3], y = evt[4]} miceDown[evt[2]] = {x = evt[3], y = evt[4]}
if evt[4] == -1 + eldit.size.y + eldit.size.height then if evt[4] == -1 + eldit.size.y + eldit.size.height then
else else
if keysDown[keys.leftCtrl] then 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, { table.insert(eldit.cursors, {
x = evt[3] + eldit.scrollX - lineNoLen, x = evt[3] + eldit.scrollX - lineNoLen,
y = evt[4] + eldit.scrollY, y = evt[4] + eldit.scrollY,
lastX = evt[3] + eldit.scrollX - lineNoLen lastX = evt[3] + eldit.scrollX - lineNoLen
}) })
startedSelecting = true
else else
eldit.cursors = {{ eldit.cursors = {{
x = evt[3] + eldit.scrollX - lineNoLen, x = evt[3] + eldit.scrollX - lineNoLen,
@ -1283,18 +1420,18 @@ prompt = function(prebuffer, precy, maxY, _eldit)
scrollX = eldit.scrollX, scrollX = eldit.scrollX,
scrollY = eldit.scrollY, scrollY = eldit.scrollY,
lineNoLen = lineNoLen, lineNoLen = lineNoLen,
ctrl = keysDown[keys.leftCtrl], ctrl = keysDown[keys.ctrl],
curID = #eldit.cursors, curID = #eldit.cursors,
} }
sortSelections() sortSelections()
adjustCursor(0, 0, true) adjustCursor(0, 0, true, nil, nil, nil, startedSelecting)
eldit.undoBuffer[eldit.undoPos].selections = eldit.selections eldit.undoBuffer[eldit.undoPos].selections = eldit.selections
eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors eldit.undoBuffer[eldit.undoPos].cursors = eldit.cursors
end end
doRender = true doRender = true
elseif evt[1] == "mouse_drag" then elseif evt[1] == "mouse_drag" then
if evt[4] == -1 + eldit.size.y + eldit.size.height then if evt[4] == -1 + eldit.size.y + eldit.size.height then
else else
local lineNoLen = getLineNoLen() local lineNoLen = getLineNoLen()
local lastMX, lastMY local lastMX, lastMY
@ -1340,8 +1477,8 @@ prompt = function(prebuffer, precy, maxY, _eldit)
} }
sortSelections() sortSelections()
eldit.cursors[lastMouse.curID] = { eldit.cursors[lastMouse.curID] = {
x = eldit.selections[selID][1].x, x = eldit.selections[selID][2].x,
y = eldit.selections[selID][1].y, y = eldit.selections[selID][2].y,
lastX = eldit.selections[selID][1].x lastX = eldit.selections[selID][1].x
} }
@ -1357,10 +1494,10 @@ prompt = function(prebuffer, precy, maxY, _eldit)
isSelecting = false isSelecting = false
sortSelections() sortSelections()
elseif evt[1] == "mouse_scroll" then elseif evt[1] == "mouse_scroll" then
if keysDown[keys.leftAlt] then if keysDown[keys.alt] then
adjustScroll((keysDown[keys.leftCtrl] and eldit.size.width or 1) * evt[2], 0) adjustScroll(((keysDown[keys.ctrl] and not (isSelecting or startedSelecting)) and eldit.size.width or 1) * evt[2], 0)
else else
adjustScroll(0, (keysDown[keys.leftCtrl] and eldit.size.height or 1) * evt[2]) adjustScroll(0, ((keysDown[keys.ctrl] and not (isSelecting or startedSelecting)) and eldit.size.height or 1) * evt[2])
end end
if isSelecting then if isSelecting then
os.queueEvent("mouse_drag", 1, evt[3], evt[4]) os.queueEvent("mouse_drag", 1, evt[3], evt[4])