-- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe -- -- SPDX-License-Identifier: LicenseRef-CCPL -- Get file to edit local x, y = 1, 1 local w, h = term.getSize() local scrollX, scrollY = 0, 0 local tLines = {""} local bRunning = true local returnvalue = nil -- Colours local highlightColour, keywordColour, textColour, bgColour, errorColour bgColour = colours.black textColour = colours.white highlightColour = colours.white keywordColour = colours.white errorColour = colours.white -- Menus local menu = require "cc.internal.menu" local current_menu local menu_items = {} table.insert(menu_items, "Done") local status_ok, status_text local tCompletions local nCompletion local function redrawLines(line, endLine) if not endLine then endLine = line end local colour = term.getTextColour() -- Highlight all lines between line and endLine, highlighting further lines if their -- lexer state has changed and aborting at the end of the screen. local changed = false while (changed or line <= endLine) and line - scrollY < h do term.setCursorPos(1 - scrollX, line - scrollY) term.clearLine() local contents = tLines[line] if not contents then break end print(contents) line = line + 1 end term.setTextColor(colours.white) term.setCursorPos(x - scrollX, y - scrollY) end local function redrawText() redrawLines(scrollY + 1, scrollY + h - 1) end local function redrawMenu() -- Clear line term.setCursorPos(1, h) term.clearLine() -- Draw line numbers term.setCursorPos(w - #("Ln " .. y) + 1, h) term.setTextColour(highlightColour) term.write("Ln ") term.setTextColour(textColour) term.write(y) term.setCursorPos(1, h) if current_menu then -- Draw menu menu.draw(current_menu) else -- Draw status term.setTextColour(colors.yellow) term.write("Press ctrl to view options") term.setTextColour(textColour) end -- Reset cursor term.setCursorPos(x - scrollX, y - scrollY) term.setCursorBlink(not current_menu) end local tMenuFuncs = { Done = function() local order = table.concat(tLines,"\n") term.clear() returnvalue = order end } local function setCursor(newX, newY) local _, oldY = x, y x, y = newX, newY local screenX = x - scrollX local screenY = y - scrollY local bRedraw = false if screenX < 1 then scrollX = x - 1 screenX = 1 bRedraw = true elseif screenX > w then scrollX = x - w screenX = w bRedraw = true end if screenY < 1 then scrollY = y - 1 screenY = 1 bRedraw = true elseif screenY > h - 1 then scrollY = y - (h - 1) screenY = h - 1 bRedraw = true end if bRedraw then redrawText() elseif y ~= oldY then redrawLines(math.min(y, oldY), math.max(y, oldY)) else redrawLines(y) end redrawMenu() end -- Actual program functionality begins term.setBackgroundColour(bgColour) term.clear() term.setCursorPos(x, y) term.setCursorBlink(true) redrawText() redrawMenu() local function acceptCompletion() if nCompletion then -- Append the completion local sCompletion = tCompletions[nCompletion] tLines[y] = tLines[y] .. sCompletion setCursor(x + #sCompletion, y) end end local function handleMenuEvent(event) assert(current_menu) local result = menu.handle_event(current_menu, table.unpack(event, 1, event.n)) if result == false then current_menu = nil redrawMenu() elseif result ~= nil then tMenuFuncs[result]() current_menu = nil redrawMenu() end end -- Handle input while bRunning do local event = table.pack(os.pullEvent()) if event[1] == "key" then if current_menu then handleMenuEvent(event) if returnvalue then return returnvalue end else local key = event[2] if key == keys.up then if nCompletion then -- Cycle completions nCompletion = nCompletion - 1 if nCompletion < 1 then nCompletion = #tCompletions end redrawLines(y) elseif y > 1 then -- Move cursor up setCursor( math.min(x, #tLines[y - 1] + 1), y - 1 ) end elseif key == keys.down then if nCompletion then -- Cycle completions nCompletion = nCompletion + 1 if nCompletion > #tCompletions then nCompletion = 1 end redrawLines(y) elseif y < #tLines then -- Move cursor down setCursor( math.min(x, #tLines[y + 1] + 1), y + 1 ) end elseif key == keys.tab then if nCompletion and x == #tLines[y] + 1 then -- Accept autocomplete acceptCompletion() else -- Indent line local sLine = tLines[y] tLines[y] = string.sub(sLine, 1, x - 1) .. " " .. string.sub(sLine, x) setCursor(x + 4, y) end elseif key == keys.pageUp then -- Move up a page local newY if y - (h - 1) >= 1 then newY = y - (h - 1) else newY = 1 end setCursor( math.min(x, #tLines[newY] + 1), newY ) elseif key == keys.pageDown then -- Move down a page local newY if y + (h - 1) <= #tLines then newY = y + (h - 1) else newY = #tLines end local newX = math.min(x, #tLines[newY] + 1) setCursor(newX, newY) elseif key == keys.home then -- Move cursor to the beginning if x > 1 then setCursor(1, y) end elseif key == keys["end"] then -- Move cursor to the end local nLimit = #tLines[y] + 1 if x < nLimit then setCursor(nLimit, y) end elseif key == keys.left then if x > 1 then -- Move cursor left setCursor(x - 1, y) elseif x == 1 and y > 1 then setCursor(#tLines[y - 1] + 1, y - 1) end elseif key == keys.right then local nLimit = #tLines[y] + 1 if x < nLimit then -- Move cursor right setCursor(x + 1, y) elseif nCompletion and x == #tLines[y] + 1 then -- Accept autocomplete acceptCompletion() elseif x == nLimit and y < #tLines then -- Go to next line setCursor(1, y + 1) end elseif key == keys.delete then local nLimit = #tLines[y] + 1 if x < nLimit then local sLine = tLines[y] tLines[y] = string.sub(sLine, 1, x - 1) .. string.sub(sLine, x + 1) redrawLines(y) elseif y < #tLines then tLines[y] = tLines[y] .. tLines[y + 1] table.remove(tLines, y + 1) redrawText() end elseif key == keys.backspace then if x > 1 then -- Remove character local sLine = tLines[y] if x > 4 and string.sub(sLine, x - 4, x - 1) == " " and not string.sub(sLine, 1, x - 1):find("%S") then tLines[y] = string.sub(sLine, 1, x - 5) .. string.sub(sLine, x) setCursor(x - 4, y) else tLines[y] = string.sub(sLine, 1, x - 2) .. string.sub(sLine, x) setCursor(x - 1, y) end elseif y > 1 then -- Remove newline local sPrevLen = #tLines[y - 1] tLines[y - 1] = tLines[y - 1] .. tLines[y] table.remove(tLines, y) setCursor(sPrevLen + 1, y - 1) redrawText() end elseif (key == keys.enter or key == keys.numPadEnter) then -- Newline local sLine = tLines[y] local _, spaces = string.find(sLine, "^[ ]+") if not spaces then spaces = 0 end tLines[y] = string.sub(sLine, 1, x - 1) table.insert(tLines, y + 1, string.rep(' ', spaces) .. string.sub(sLine, x)) setCursor(spaces + 1, y + 1) redrawText() elseif key == keys.leftCtrl or key == keys.rightCtrl then current_menu = menu.create(menu_items) redrawMenu() end end elseif event[1] == "char" then if current_menu then handleMenuEvent(event) else -- Input text local sLine = tLines[y] tLines[y] = string.sub(sLine, 1, x - 1) .. event[2] .. string.sub(sLine, x) setCursor(x + 1, y) end elseif event[1] == "paste" then -- Close menu if open if current_menu then current_menu = nil redrawMenu() end -- Input text local text = event[2] local sLine = tLines[y] tLines[y] = string.sub(sLine, 1, x - 1) .. text .. string.sub(sLine, x) setCursor(x + #text, y) elseif event[1] == "mouse_click" then local button, cx, cy = event[2], event[3], event[4] if current_menu then handleMenuEvent(event) else if button == 1 then -- Left click if cy < h then local newY = math.min(math.max(scrollY + cy, 1), #tLines) local newX = math.min(math.max(scrollX + cx, 1), #tLines[newY] + 1) setCursor(newX, newY) else current_menu = menu.create(menu_items) redrawMenu() end end end elseif event[1] == "mouse_scroll" then if not current_menu then local direction = event[2] if direction == -1 then -- Scroll up if scrollY > 0 then -- Move cursor up scrollY = scrollY - 1 redrawText() end elseif direction == 1 then -- Scroll down local nMaxScroll = #tLines - (h - 1) if scrollY < nMaxScroll then -- Move cursor down scrollY = scrollY + 1 redrawText() end end end elseif event[1] == "term_resize" then w, h = term.getSize() setCursor(x, y) redrawMenu() redrawText() end end -- Cleanup term.clear() term.setCursorBlink(false) term.setCursorPos(1, 1)