daily pastebin goal
48%
SHARE
TWEET

ShEdit

LoganDark Dec 5th, 2017 (edited) 984 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. local fs = require("filesystem")
  2. local keyboard = require("keyboard")
  3. local shell = require("shell")
  4. local term = require("term") -- TODO use tty and cursor position instead of global area and gpu
  5. local text = require("text")
  6. local unicode = require("unicode")
  7.  
  8. -- MODIFICATION: added color scheme and syntax highlighting info
  9.  
  10. -- Monokai color scheme
  11. local bgColor = 0x272822
  12. local lineColor = 0x39392F
  13. local textColor = 0xF8F8F2
  14. local keywordColor = 0xF92672
  15. local commentColor = 0x75715E
  16. local stringColor = 0xE6DB74
  17. local valueColor = 0xAE81FF
  18. local builtinColor = 0x66D9EF
  19. local lineNrColor = 0x90908A
  20.  
  21. --[[local keywords = {
  22.     ["and"] = true,
  23.     ["break"] = true,
  24.     ["do"] = true,
  25.     ["else"] = true,
  26.     ["elseif"] = true,
  27.     ["end"] = true,
  28.     ["false"] = true,
  29.     ["for"] = true,
  30.     ["function"] = true,
  31.     ["if"] = true,
  32.     ["in"] = true,
  33.     ["local"] = true,
  34.     ["nil"] = true,
  35.     ["not"] = true,
  36.     ["or"] = true,
  37.     ["repeat"] = true,
  38.     ["return"] = true,
  39.     ["then"] = true,
  40.     ["true"] = true,
  41.     ["until"] = true,
  42.     ["while"] = true,
  43.  
  44.     ["+"] = true,
  45.     ["-"] = true,
  46.     ["*"] = true,
  47.     ["/"] = true,
  48.     ["="] = true
  49. }]]
  50.  
  51. local keywords = {
  52.     ['break'] = true,
  53.     ['do'] = true,
  54.     ['else'] = true,
  55.     ['for'] = true,
  56.     ['if'] = true,
  57.     ['elseif'] = true,
  58.     ['return'] = true,
  59.     ['then'] = true,
  60.     ['repeat'] = true,
  61.     ['while'] = true,
  62.     ['until'] = true,
  63.     ['end'] = true,
  64.     ['function'] = true,
  65.     ['local'] = true,
  66.     ['in'] = true,
  67.     ['and'] = true,
  68.     ['or'] = true,
  69.     ['not'] = true,
  70.  
  71.     ['+'] = true,
  72.     ['-'] = true,
  73.     ['%'] = true,
  74.     ['#'] = true,
  75.     ['*'] = true,
  76.     ['/'] = true,
  77.     ['^'] = true,
  78.     ['='] = true,
  79.     ['=='] = true,
  80.     ['~='] = true,
  81.     ['<'] = true,
  82.     ['<='] = true,
  83.     ['>'] = true,
  84.     ['>='] = true,
  85.     ['..'] = true
  86. }
  87.  
  88. local builtins = {
  89.     ['assert'] = true,
  90.     ['collectgarbage'] = true,
  91.     ['dofile'] = true,
  92.     ['error'] = true,
  93.     ['getfenv'] = true,
  94.     ['getmetatable'] = true,
  95.     ['ipairs'] = true,
  96.     ['loadfile'] = true,
  97.     ['loadstring'] = true,
  98.     ['module'] = true,
  99.     ['next'] = true,
  100.     ['pairs'] = true,
  101.     ['pcall'] = true,
  102.     ['print'] = true,
  103.     ['rawequal'] = true,
  104.     ['rawget'] = true,
  105.     ['rawset'] = true,
  106.     ['require'] = true,
  107.     ['select'] = true,
  108.     ['setfenv'] = true,
  109.     ['setmetatable'] = true,
  110.     ['tonumber'] = true,
  111.     ['tostring'] = true,
  112.     ['type'] = true,
  113.     ['unpack'] = true,
  114.     ['xpcall'] = true
  115. }
  116.  
  117. local values = {
  118.     ['false'] = true,
  119.     ['nil'] = true,
  120.     ['true'] = true,
  121.     ['_G'] = true,
  122.     ['_VERSION'] = true
  123. }
  124.  
  125. local patterns = {
  126.     {"^%-%-%[%[.-%]%]", commentColor},
  127.     {"^%-%-.*", commentColor},
  128.     {"^\"\"", stringColor},
  129.     {"^\".-[^\\]\"", stringColor},
  130.     {"^\'\'", stringColor},
  131.     {"^\'.-[^\\]\'", stringColor},
  132.     {"^%[%[.-%]%]", stringColor},
  133.     {"^[%w_%+%-%%%#%*%/%^%=%~%<%>%.]+", function(text)
  134.         if values[text] or tonumber(text) then
  135.             local match = text:find('^0x%x%x%x%x%x%x$')
  136.  
  137.             if match then
  138.                 local luminosity = 0.2126 * tonumber('0x' .. text:sub(3, 4)) + 0.7152 * tonumber('0x' .. text:sub(5, 6)) + 0.0722 * tonumber('0x' .. text:sub(7, 8))
  139.  
  140.                 if luminosity > 20 then
  141.                     return 0x000000, tonumber(text)
  142.                 else
  143.                     return 0xffffff, tonumber(text)
  144.                 end
  145.             else
  146.                 return valueColor
  147.             end
  148.         elseif keywords[text] then
  149.             return keywordColor
  150.         elseif builtins[text] then
  151.             return builtinColor
  152.         end
  153.  
  154.         return textColor
  155.     end}
  156. }
  157.  
  158. local cache = {}
  159. local currentMargin = 7
  160.  
  161. -- END OF MODIFICATION
  162.  
  163. if not term.isAvailable() then
  164.     return
  165. end
  166. local gpu = term.gpu()
  167.  
  168. -- MODIFICATION: set the foreground and background correctly
  169.  
  170. local originalFg = gpu.setForeground(textColor)
  171. local originalBg = gpu.setBackground(bgColor)
  172.  
  173. function resetColors()
  174.     gpu.setForeground(originalFg)
  175.     gpu.setBackground(originalBg)
  176. end
  177.  
  178. -- END OF MODIFICATION
  179.  
  180. local args, options = shell.parse(...)
  181. if #args == 0 then
  182.     resetColors() -- MODIFICATION
  183.     io.write("Usage: edit <filename>")
  184.     return
  185. end
  186.  
  187. local filename = shell.resolve(args[1])
  188. local file_parentpath = fs.path(filename)
  189.  
  190. if fs.exists(file_parentpath) and not fs.isDirectory(file_parentpath) then
  191.     resetColors() -- MODIFICATION
  192.     io.stderr:write(string.format("Not a directory: %s\n", file_parentpath))
  193.     return 1
  194. end
  195.  
  196. local readonly = options.r or fs.get(filename) == nil or fs.get(filename).isReadOnly()
  197.  
  198. if fs.isDirectory(filename) then
  199.     resetColors() -- MODIFICATION
  200.     io.stderr:write("file is a directory\n")
  201.     return 1
  202. elseif not fs.exists(filename) and readonly then
  203.     resetColors() -- MODIFICATION
  204.     io.stderr:write("file system is read only\n")
  205.     return 1
  206. end
  207.  
  208. local function loadConfig()
  209.     -- Try to load user settings.
  210.     local env = {}
  211.     local config = loadfile("/etc/edit.cfg", nil, env)
  212.     if config then
  213.         pcall(config)
  214.     end
  215.     -- Fill in defaults.
  216.     env.keybinds = env.keybinds or {
  217.         left = {{"left"}},
  218.         right = {{"right"}},
  219.         up = {{"up"}},
  220.         down = {{"down"}},
  221.         home = {{"home"}},
  222.         eol = {{"end"}},
  223.         pageUp = {{"pageUp"}},
  224.         pageDown = {{"pageDown"}},
  225.  
  226.         backspace = {{"back"}},
  227.         delete = {{"delete"}},
  228.         deleteLine = {{"control", "delete"}, {"shift", "delete"}},
  229.         newline = {{"enter"}},
  230.  
  231.         save = {{"control", "s"}},
  232.         close = {{"control", "w"}},
  233.         find = {{"control", "f"}},
  234.         findnext = {{"control", "g"}, {"control", "n"}, {"f3"}},
  235.  
  236.         jump = {{'control', 'j'}} -- MODIFICATION
  237.     }
  238.  
  239.     -- Generate config file if it didn't exist.
  240.     if not config then
  241.         local root = fs.get("/")
  242.         if root and not root.isReadOnly() then
  243.             fs.makeDirectory("/etc")
  244.             local f = io.open("/etc/edit.cfg", "w")
  245.             if f then
  246.                 local serialization = require("serialization")
  247.                 for k, v in pairs(env) do
  248.                     f:write(k.."="..tostring(serialization.serialize(v, math.huge)).."\n")
  249.                 end
  250.                 f:close()
  251.             end
  252.         end
  253.     end
  254.     return env
  255. end
  256.  
  257. term.clear()
  258. term.setCursorBlink(true)
  259.  
  260. local running = true
  261. local buffer = {}
  262. local scrollX, scrollY = 0, 0
  263. local config = loadConfig()
  264.  
  265. local getKeyBindHandler -- forward declaration for refind()
  266.  
  267. local function helpStatusText()
  268.     local function prettifyKeybind(label, command)
  269.         local keybind = type(config.keybinds) == "table" and config.keybinds[command]
  270.         if type(keybind) ~= "table" or type(keybind[1]) ~= "table" then return "" end
  271.         local alt, control, shift, key
  272.         for _, value in ipairs(keybind[1]) do
  273.             if value == "alt" then alt = true
  274.             elseif value == "control" then control = true
  275.             elseif value == "shift" then shift = true
  276.             else key = value end
  277.         end
  278.         if not key then return "" end
  279.         return label .. ": [" ..
  280.                      (control and "Ctrl+" or "") ..
  281.                      (alt and "Alt+" or "") ..
  282.                      (shift and "Shift+" or "") ..
  283.                      unicode.upper(key) ..
  284.                      "] "
  285.     end
  286.     return prettifyKeybind("Save", "save") ..
  287.                  prettifyKeybind("Close", "close") ..
  288.                  prettifyKeybind("Find", "find") ..
  289.                  prettifyKeybind('Jump to line', 'jump') -- MODIFICATION
  290. end
  291.  
  292. -------------------------------------------------------------------------------
  293.  
  294. local currentStatus = '' -- MODIFICATION
  295.  
  296. local function setStatus(value)
  297.     local x, y, w, h = term.getGlobalArea()
  298.     value = unicode.wlen(value) > w - 10 and unicode.wtrunc(value, w - 9) or value
  299.     value = text.padRight(value, w - 10)
  300.     gpu.set(x, y + h - 1, value)
  301.  
  302.     currentStatus = value -- MODIFICATION
  303. end
  304.  
  305. local function getArea()
  306.     local x, y, w, h = term.getGlobalArea()
  307.     --return x, y, w, h - 1
  308.     return x + currentMargin, y, w - currentMargin, h - 1 -- MODIFICATION
  309. end
  310.  
  311. local function removePrefix(line, length)
  312.     if length >= unicode.wlen(line) then
  313.         return ""
  314.     else
  315.         local prefix = unicode.wtrunc(line, length + 1)
  316.         local suffix = unicode.sub(line, unicode.len(prefix) + 1)
  317.         length = length - unicode.wlen(prefix)
  318.         if length > 0 then
  319.             suffix = (" "):rep(unicode.charWidth(suffix) - length) .. unicode.sub(suffix, 2)
  320.         end
  321.         return suffix
  322.     end
  323. end
  324.  
  325. local function lengthToChars(line, length)
  326.     if length > unicode.wlen(line) then
  327.         return unicode.len(line) + 1
  328.     else
  329.         local prefix = unicode.wtrunc(line, length)
  330.         return unicode.len(prefix) + 1
  331.     end
  332. end
  333.  
  334.  
  335. local function isWideAtPosition(line, x)
  336.     local index = lengthToChars(line, x)
  337.     if index > unicode.len(line) then
  338.         return false, false
  339.     end
  340.     local prefix = unicode.sub(line, 1, index)
  341.     local char = unicode.sub(line, index, index)
  342.     --isWide, isRight
  343.     return unicode.isWide(char), unicode.wlen(prefix) == x
  344. end
  345.  
  346. -- MODIFICATION: completely rewrote drawLine function
  347.  
  348. --[[
  349. local function drawLine(x, y, w, h, lineNr)
  350.     local yLocal = lineNr - scrollY
  351.     if yLocal > 0 and yLocal <= h then
  352.         local str = removePrefix(buffer[lineNr] or "", scrollX)
  353.         str = unicode.wlen(str) > w and unicode.wtrunc(str, w + 1) or str
  354.         str = text.padRight(str, w)
  355.         gpu.set(x, y - 1 + lineNr - scrollY, str)
  356.     end
  357. end
  358. ]]
  359.  
  360. local function drawLine(x, y, w, h, lineNr)
  361.     local yLocal = lineNr - scrollY
  362.     local drawY = y - 1 + lineNr - scrollY
  363.  
  364.     if yLocal > 0 and yLocal <= h then
  365.         --[[local str = removePrefix(buffer[lineNr] or "", scrollX)
  366.         str = unicode.wlen(str) > w and unicode.wtrunc(str, w + 1) or str
  367.         str = text.padRight(str, w)
  368.         gpu.set(x, y - 1 + lineNr - scrollY, str)]]
  369.  
  370.         local colors = {}
  371.         local line = buffer[lineNr] or ""
  372.  
  373.         if cache[lineNr] and cache[lineNr][1] == line then
  374.             colors = cache[lineNr][2]
  375.         else
  376.             local function appendTextInColor(text, color, bgcolor)
  377.                 local data = colors[#colors]
  378.  
  379.                 if data ~= nil then
  380.                     if data[2] == color and data[3] == bgcolor then
  381.                         data[1] = data[1] .. text
  382.                     else
  383.                         colors[#colors + 1] = {text, color, bgcolor}
  384.                     end
  385.                 else
  386.                     colors[#colors + 1] = {text, color, bgcolor}
  387.                 end
  388.             end
  389.  
  390.             local len = 0
  391.  
  392.             for char = 1, line:len() do
  393.                 if char > len then
  394.                     local patternFound = false
  395.  
  396.                     for pat = 1, #patterns do
  397.                         local data = patterns[pat]
  398.                         local foundb, founde = line:find(data[1], char)
  399.  
  400.                         if foundb ~= nil then
  401.                             local text = line:sub(foundb, founde)
  402.                             local color = data[2]
  403.                             local bgcolor = data[3]
  404.  
  405.                             if type(color) == 'function' then
  406.                                 color, bgcolor = color(text)
  407.                             end
  408.  
  409.                             appendTextInColor(text, color, bgcolor)
  410.                             len = len + (founde - foundb + 1)
  411.  
  412.                             patternFound = true
  413.  
  414.                             break
  415.                         end
  416.                     end
  417.  
  418.                     if not patternFound then
  419.                         appendTextInColor(line:sub(char, char), textColor)
  420.                         len = len + 1
  421.                     end
  422.                 end
  423.             end
  424.  
  425.             cache[lineNr] = {line, colors}
  426.         end
  427.  
  428.         local i = 0
  429.         local cx, cy = term.getCursor()
  430.         local lineBg = bgColor
  431.  
  432.         if cy + scrollY == lineNr then
  433.             lineBg = lineColor
  434.         end
  435.  
  436.         gpu.setBackground(lineBg)
  437.         gpu.fill(1, y - 1 + lineNr - scrollY, 7, 1, ' ')
  438.         gpu.fill(x, drawY, w + currentMargin, 1, ' ')
  439.  
  440.         if lineNr <= #buffer then
  441.             for l = 1, #colors do
  442.                 local data = colors[l]
  443.                 local text = data[1]
  444.                 local color = data[2]
  445.                 local bg = data[3]
  446.                 local drawAt = i - scrollX + x
  447.  
  448.                 if drawAt + text:len() > 0 then
  449.                     local currentColor = gpu.setForeground(color)
  450.                     local currentBg = gpu.setBackground(bg or lineBg)
  451.                     gpu.set(drawAt, drawY, text)
  452.                     gpu.setForeground(currentColor)
  453.                     gpu.setBackground(currentBg)
  454.                 end
  455.  
  456.                 i = i + text:len()
  457.             end
  458.  
  459.             local currentColor = gpu.setForeground(lineNrColor)
  460.             local number = tostring(math.floor(lineNr))
  461.  
  462.             gpu.fill(1, y - 1 + lineNr - scrollY, 7, 1, ' ') -- again
  463.             gpu.set(2 + (5 - number:len()), y - 1 + lineNr - scrollY, number)
  464.             gpu.setForeground(currentColor)
  465.             gpu.setBackground(bgColor)
  466.         end
  467.     end
  468. end
  469.  
  470. -- END OF MODIFICATION --
  471.  
  472. local function getCursor()
  473.     local cx, cy = term.getCursor()
  474.     --return cx + scrollX, cy + scrollY
  475.     return cx + scrollX - currentMargin, cy + scrollY -- MODIFICATION
  476. end
  477.  
  478. local function line()
  479.     local cbx, cby = getCursor()
  480.     return buffer[cby]
  481. end
  482.  
  483. local function getNormalizedCursor()
  484.     local cbx, cby = getCursor()
  485.     local wide, right = isWideAtPosition(buffer[cby], cbx)
  486.     if wide and right then
  487.         cbx = cbx - 1
  488.     end
  489.     return cbx, cby
  490. end
  491.  
  492. local function setCursor(nbx, nby)
  493.     local x, y, w, h = getArea()
  494.     nby = math.max(1, math.min(#buffer, nby))
  495.  
  496.     local ncy = nby - scrollY
  497.     if ncy > h then
  498.         term.setCursorBlink(false)
  499.         local sy = nby - h
  500.         local dy = math.abs(scrollY - sy)
  501.         scrollY = sy
  502.         if h > dy then
  503.             gpu.copy(x - currentMargin, y + dy, w + currentMargin, h - dy, 0, -dy)
  504.         end
  505.         for lineNr = nby - (math.min(dy, h) - 1), nby do
  506.             drawLine(x, y, w, h, lineNr)
  507.         end
  508.     elseif ncy < 1 then
  509.         term.setCursorBlink(false)
  510.         local sy = nby - 1
  511.         local dy = math.abs(scrollY - sy)
  512.         scrollY = sy
  513.         if h > dy then
  514.             gpu.copy(x - currentMargin, y, w + currentMargin, h - dy, 0, dy)
  515.         end
  516.         for lineNr = nby, nby + (math.min(dy, h) - 1) do
  517.             drawLine(x, y, w, h, lineNr)
  518.         end
  519.     end
  520.     term.setCursor(term.getCursor(), nby - scrollY)
  521.  
  522.     nbx = math.max(1, math.min(unicode.wlen(line()) + 1, nbx))
  523.     local wide, right = isWideAtPosition(line(), nbx)
  524.     local ncx = nbx - scrollX
  525.     if ncx > w or (ncx + 1 > w and wide and not right) then
  526.         term.setCursorBlink(false)
  527.         scrollX = nbx - w + ((wide and not right) and 1 or 0)
  528.         for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do
  529.             drawLine(x, y, w, h, lineNr)
  530.         end
  531.     elseif ncx < 1 or (ncx - 1 < 1 and wide and right) then
  532.         term.setCursorBlink(false)
  533.         scrollX = nbx - 1 - ((wide and right) and 1 or 0)
  534.         for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do
  535.             drawLine(x, y, w, h, lineNr)
  536.         end
  537.     end
  538.     --term.setCursor(nbx - scrollX, nby - scrollY)
  539.     term.setCursor(nbx - scrollX + currentMargin, nby - scrollY) -- MODIFICATION
  540.     --update with term lib
  541.     nbx, nby = getCursor()
  542.     gpu.set(x + w - 10, y + h, text.padLeft(string.format("%d,%d", nby, nbx), 10))
  543. end
  544.  
  545. local function highlight(bx, by, length, enabled)
  546.     local x, y, w, h = getArea()
  547.     local cx, cy = bx - scrollX, by - scrollY
  548.     cx = math.max(1, math.min(w, cx))
  549.     cy = math.max(1, math.min(h, cy))
  550.     length = math.max(1, math.min(w - cx, length))
  551.  
  552.     local fg, fgp = gpu.getForeground()
  553.     local bg, bgp = gpu.getBackground()
  554.     if enabled then
  555.         gpu.setForeground(bg, bgp)
  556.         gpu.setBackground(fg, fgp)
  557.     end
  558.     local indexFrom = lengthToChars(buffer[by], bx)
  559.     local value = unicode.sub(buffer[by], indexFrom)
  560.     if unicode.wlen(value) > length then
  561.         value = unicode.wtrunc(value, length + 1)
  562.     end
  563.     gpu.set(x - 1 + cx, y - 1 + cy, value)
  564.     if enabled then
  565.         gpu.setForeground(fg, fgp)
  566.         gpu.setBackground(bg, bgp)
  567.     end
  568. end
  569.  
  570. local function home()
  571.     local cbx, cby = getCursor()
  572.     setCursor(1, cby)
  573. end
  574.  
  575. local function ende()
  576.     local cbx, cby = getCursor()
  577.     setCursor(unicode.wlen(line()) + 1, cby)
  578. end
  579.  
  580. local function left()
  581.     local cbx, cby = getNormalizedCursor()
  582.     local _, _cby = getCursor()
  583.     if cbx > 1 then
  584.         local wideTarget, rightTarget = isWideAtPosition(line(), cbx - 1)
  585.         if wideTarget and rightTarget then
  586.             setCursor(cbx - 2, cby)
  587.         else
  588.             setCursor(cbx - 1, cby)
  589.         end
  590.         return true -- for backspace
  591.     elseif cby > 1 then
  592.         setCursor(cbx, cby - 1)
  593.         ende()
  594.         return true -- again, for backspace
  595.     end
  596. end
  597.  
  598. local function right(n)
  599.     n = n or 1
  600.     local cbx, cby = getNormalizedCursor()
  601.     local be = unicode.wlen(line()) + 1
  602.     local wide, right = isWideAtPosition(line(), cbx + n)
  603.     if wide and right then
  604.         n = n + 1
  605.     end
  606.     if cbx + n <= be then
  607.         setCursor(cbx + n, cby)
  608.     elseif cby < #buffer then
  609.         setCursor(1, cby + 1)
  610.     end
  611. end
  612.  
  613. local function up(n)
  614.     n = n or 1
  615.     local cbx, cby = getCursor()
  616.     if cby > 1 then
  617.         setCursor(cbx, cby - n)
  618.     end
  619. end
  620.  
  621. local function down(n)
  622.     n = n or 1
  623.     local cbx, cby = getCursor()
  624.     if cby < #buffer then
  625.         setCursor(cbx, cby + n)
  626.     end
  627. end
  628.  
  629. local function delete(fullRow)
  630.     local cx, cy = term.getCursor()
  631.     local cbx, cby = getCursor()
  632.     local x, y, w, h = getArea()
  633.     local function deleteRow(row)
  634.         local content = table.remove(buffer, row)
  635.         local rcy = cy + (row - cby)
  636.         if rcy <= h then
  637.             gpu.copy(x, y + rcy, w, h - rcy, 0, -1)
  638.             drawLine(x, y, w, h, row + (h - rcy))
  639.         end
  640.         return content
  641.     end
  642.     if fullRow then
  643.         term.setCursorBlink(false)
  644.         if #buffer > 1 then
  645.             deleteRow(cby)
  646.         else
  647.             buffer[cby] = ""
  648.             gpu.fill(x, y - 1 + cy, w, 1, " ")
  649.         end
  650.         setCursor(1, cby)
  651.     elseif cbx <= unicode.wlen(line()) then
  652.         term.setCursorBlink(false)
  653.         local index = lengthToChars(line(), cbx)
  654.         buffer[cby] = unicode.sub(line(), 1, index - 1) ..
  655.                                     unicode.sub(line(), index + 1)
  656.         drawLine(x, y, w, h, cby)
  657.     elseif cby < #buffer then
  658.         term.setCursorBlink(false)
  659.         local append = deleteRow(cby + 1)
  660.         buffer[cby] = buffer[cby] .. append
  661.         drawLine(x, y, w, h, cby)
  662.     else
  663.         return
  664.     end
  665.     setStatus(helpStatusText())
  666. end
  667.  
  668. local function insert(value)
  669.     if not value or unicode.len(value) < 1 then
  670.         return
  671.     end
  672.     term.setCursorBlink(false)
  673.     local cx, cy = term.getCursor()
  674.     local cbx, cby = getCursor()
  675.     local x, y, w, h = getArea()
  676.     local index = lengthToChars(line(), cbx)
  677.     buffer[cby] = unicode.sub(line(), 1, index - 1) ..
  678.                                 value ..
  679.                                 unicode.sub(line(), index)
  680.     drawLine(x, y, w, h, cby)
  681.     right(unicode.wlen(value))
  682.     setStatus(helpStatusText())
  683. end
  684.  
  685. local function enter()
  686.     term.setCursorBlink(false)
  687.     local cx, cy = term.getCursor()
  688.     local cbx, cby = getCursor()
  689.     local x, y, w, h = getArea()
  690.     local index = lengthToChars(line(), cbx)
  691.     table.insert(buffer, cby + 1, unicode.sub(buffer[cby], index))
  692.     buffer[cby] = unicode.sub(buffer[cby], 1, index - 1)
  693.     drawLine(x, y, w, h, cby)
  694.     if cy < h then
  695.         if cy < h - 1 then
  696.             gpu.copy(x, y + cy, w, h - (cy + 1), 0, 1)
  697.         end
  698.         drawLine(x, y, w, h, cby + 1)
  699.     end
  700.     --setCursor(1, cby + 1)
  701.  
  702.     -- MODIFICATION: keep indent
  703.  
  704.     local whitespace = buffer[cby]:match('^[%s]+') or ""
  705.     buffer[cby + 1] = whitespace .. buffer[cby + 1]
  706.  
  707.     setCursor(1 + whitespace:len(), cby + 1)
  708.  
  709.     -- END OF MODIFICATION
  710.  
  711.     setStatus(helpStatusText())
  712. end
  713.  
  714. local findText = ""
  715.  
  716. local function find()
  717.     local x, y, w, h = getArea()
  718.     local cx, cy = term.getCursor()
  719.     local cbx, cby = getCursor()
  720.     local ibx, iby = cbx, cby
  721.     while running do
  722.         if unicode.len(findText) > 0 then
  723.             local sx, sy
  724.             for syo = 1, #buffer do -- iterate lines with wraparound
  725.                 sy = (iby + syo - 1 + #buffer - 1) % #buffer + 1
  726.                 sx = string.find(buffer[sy], findText, syo == 1 and ibx or 1, true)
  727.                 if sx and (sx >= ibx or syo > 1) then
  728.                     break
  729.                 end
  730.             end
  731.             if not sx then -- special case for single matches
  732.                 sy = iby
  733.                 sx = string.find(buffer[sy], findText, nil, true)
  734.             end
  735.             if sx then
  736.                 sx = unicode.wlen(string.sub(buffer[sy], 1, sx - 1)) + 1
  737.                 cbx, cby = sx, sy
  738.                 setCursor(cbx, cby)
  739.                 highlight(cbx, cby, unicode.wlen(findText), true)
  740.             end
  741.         end
  742.         term.setCursor(7 + unicode.wlen(findText), h + 1)
  743.         setStatus("Find: " .. findText)
  744.  
  745.         local _, address, char, code = term.pull("key_down")
  746.         if address == term.keyboard() then
  747.             local handler, name = getKeyBindHandler(code)
  748.             highlight(cbx, cby, unicode.wlen(findText), false)
  749.             if name == "newline" then
  750.                 break
  751.             elseif name == "close" then
  752.                 handler()
  753.             elseif name == "backspace" then
  754.                 findText = unicode.sub(findText, 1, -2)
  755.             elseif name == "find" or name == "findnext" then
  756.                 ibx = cbx + 1
  757.                 iby = cby
  758.             elseif not keyboard.isControl(char) then
  759.                 findText = findText .. unicode.char(char)
  760.             end
  761.         end
  762.     end
  763.     setCursor(cbx, cby)
  764.     setStatus(helpStatusText())
  765. end
  766.  
  767. -- MODIFICATION: more functions
  768.  
  769. local function fix()
  770.     local x, y, w, h = getArea()
  771.  
  772.     gpu.fill(x, y, w, h, ' ')
  773.  
  774.     for i = 1, #buffer do
  775.         if i > scrollY and i <= (scrollY + h) then
  776.             drawLine(x, y, w, h, i)
  777.         end
  778.     end
  779. end
  780.  
  781. local function jump()
  782.     local x, y, w, h = getArea()
  783.     local cx, cy = term.getCursor()
  784.     local currentStatus = currentStatus
  785.  
  786.     setStatus('Jump to line #')
  787.  
  788.     local current = ''
  789.  
  790.     while true do
  791.         local _, address, char, code = term.pull('key_down')
  792.  
  793.         char = math.floor(char)
  794.  
  795.         if address == term.keyboard() then
  796.             if char == 13 then -- enter
  797.                 break
  798.             elseif char >= string.byte('0') and char <= string.byte('9') then -- number
  799.                 if current:len() < 5 then
  800.                     current = current .. string.char(char)
  801.                 end
  802.             elseif char == 127 then -- backspace
  803.                 if current:len() > 0 then
  804.                     current = current:sub(1, -1)
  805.                 end
  806.             end
  807.  
  808.             term.setCursor(15, h + 1)
  809.             gpu.set(15, h + 1, '     ')
  810.             term.write(current)
  811.         end
  812.     end
  813.  
  814.     term.setCursor(cx, cy)
  815.  
  816.     current = tonumber(current)
  817.  
  818.     if current then
  819.         if current <= #buffer then
  820.             setCursor(1, current)
  821.             setStatus('Jumped to line ' .. tostring(current))
  822.         else
  823.             setStatus('Line ' .. tostring(current) .. ' does not exist')
  824.         end
  825.     else
  826.         setStatus(currentStatus)
  827.     end
  828. end
  829.  
  830. -- END OF MODIFICATION
  831.  
  832. -------------------------------------------------------------------------------
  833.  
  834. local keyBindHandlers = {
  835.     left = left,
  836.     right = right,
  837.     up = up,
  838.     down = down,
  839.     home = home,
  840.     eol = ende,
  841.     pageUp = function()
  842.         local x, y, w, h = getArea()
  843.         up(h - 1)
  844.     end,
  845.     pageDown = function()
  846.         local x, y, w, h = getArea()
  847.         down(h - 1)
  848.     end,
  849.  
  850.     backspace = function()
  851.         if not readonly and left() then
  852.             delete()
  853.         end
  854.     end,
  855.     delete = function()
  856.         if not readonly then
  857.             delete()
  858.         end
  859.     end,
  860.     deleteLine = function()
  861.         if not readonly then
  862.             delete(true)
  863.         end
  864.     end,
  865.     newline = function()
  866.         if not readonly then
  867.             enter()
  868.         end
  869.     end,
  870.  
  871.     save = function()
  872.         if readonly then return end
  873.         local new = not fs.exists(filename)
  874.         local backup
  875.         if not new then
  876.             backup = filename .. "~"
  877.             for i = 1, math.huge do
  878.                 if not fs.exists(backup) then
  879.                     break
  880.                 end
  881.                 backup = filename .. "~" .. i
  882.             end
  883.             fs.copy(filename, backup)
  884.         end
  885.         if not fs.exists(file_parentpath) then
  886.             fs.makeDirectory(file_parentpath)
  887.         end
  888.         local f, reason = io.open(filename, "w")
  889.         if f then
  890.             local chars, firstLine = 0, true
  891.             for _, line in ipairs(buffer) do
  892.                 if not firstLine then
  893.                     line = "\n" .. line
  894.                 end
  895.                 firstLine = false
  896.                 f:write(line)
  897.                 chars = chars + unicode.len(line)
  898.             end
  899.             f:close()
  900.             local format
  901.             if new then
  902.                 format = [["%s" [New] %dL,%dC written]]
  903.             else
  904.                 format = [["%s" %dL,%dC written]]
  905.             end
  906.             setStatus(string.format(format, fs.name(filename), #buffer, chars))
  907.         else
  908.             setStatus(reason)
  909.         end
  910.         if not new then
  911.             fs.remove(backup)
  912.         end
  913.     end,
  914.     close = function()
  915.         -- TODO ask to save if changed
  916.         running = false
  917.     end,
  918.     find = function()
  919.         findText = ""
  920.         find()
  921.     end,
  922.     findnext = find,
  923.  
  924.     jump = jump -- MODIFICATION
  925. }
  926.  
  927. getKeyBindHandler = function(code)
  928.     if type(config.keybinds) ~= "table" then return end
  929.     -- Look for matches, prefer more 'precise' keybinds, e.g. prefer
  930.     -- ctrl+del over del.
  931.     local result, resultName, resultWeight = nil, nil, 0
  932.     for command, keybinds in pairs(config.keybinds) do
  933.         if type(keybinds) == "table" and keyBindHandlers[command] then
  934.             for _, keybind in ipairs(keybinds) do
  935.                 if type(keybind) == "table" then
  936.                     local alt, control, shift, key
  937.                     for _, value in ipairs(keybind) do
  938.                         if value == "alt" then alt = true
  939.                         elseif value == "control" then control = true
  940.                         elseif value == "shift" then shift = true
  941.                         else key = value end
  942.                     end
  943.                     local keyboardAddress = term.keyboard()
  944.                     if (not alt or keyboard.isAltDown(keyboardAddress)) and
  945.                          (not control or keyboard.isControlDown(keyboardAddress)) and
  946.                          (not shift or keyboard.isShiftDown(keyboardAddress)) and
  947.                          code == keyboard.keys[key] and
  948.                          #keybind > resultWeight
  949.                     then
  950.                         resultWeight = #keybind
  951.                         resultName = command
  952.                         result = keyBindHandlers[command]
  953.                     end
  954.                 end
  955.             end
  956.         end
  957.     end
  958.     return result, resultName
  959. end
  960.  
  961. -------------------------------------------------------------------------------
  962.  
  963. local function onKeyDown(char, code)
  964.     local handler = getKeyBindHandler(code)
  965.     if handler then
  966.         handler()
  967.     elseif readonly and code == keyboard.keys.q then
  968.         running = false
  969.     elseif not readonly then
  970.         if not keyboard.isControl(char) then
  971.             insert(unicode.char(char))
  972.         elseif unicode.char(char) == "\t" then
  973.             insert("  ")
  974.         end
  975.     end
  976. end
  977.  
  978. local function onClipboard(value)
  979.     value = value:gsub("\r\n", "\n")
  980.     local cbx, cby = getCursor()
  981.     local start = 1
  982.     local l = value:find("\n", 1, true)
  983.     if l then
  984.         repeat
  985.             local line = string.sub(value, start, l - 1)
  986.             line = text.detab(line, 2)
  987.             insert(line)
  988.             enter()
  989.             start = l + 1
  990.             l = value:find("\n", start, true)
  991.         until not l
  992.     end
  993.     insert(string.sub(value, start))
  994. end
  995.  
  996. local function onClick(x, y)
  997.     setCursor(x + scrollX, y + scrollY)
  998. end
  999.  
  1000. local function onScroll(direction)
  1001.     local cbx, cby = getCursor()
  1002.     setCursor(cbx, cby - direction * 12)
  1003. end
  1004.  
  1005. -------------------------------------------------------------------------------
  1006.  
  1007. do
  1008.     local f = io.open(filename)
  1009.     if f then
  1010.         local x, y, w, h = getArea()
  1011.         local chars = 0
  1012.         for line in f:lines() do
  1013.             table.insert(buffer, line)
  1014.             chars = chars + unicode.len(line)
  1015.             if #buffer <= h then
  1016.                 drawLine(x, y, w, h, #buffer)
  1017.             end
  1018.         end
  1019.         f:close()
  1020.         if #buffer == 0 then
  1021.             table.insert(buffer, "")
  1022.         end
  1023.         local format
  1024.         if readonly then
  1025.             format = [["%s" [readonly] %dL,%dC]]
  1026.         else
  1027.             format = [["%s" %dL,%dC]]
  1028.         end
  1029.         setStatus(string.format(format, fs.name(filename), #buffer, chars))
  1030.     else
  1031.         table.insert(buffer, "")
  1032.         setStatus(string.format([["%s" [New File] ]], fs.name(filename)))
  1033.     end
  1034.     setCursor(1, 1)
  1035. end
  1036.  
  1037. while running do
  1038.     local startX = scrollX -- MODIFICATION
  1039.     local startY = scrollY -- MODIFICATION
  1040.     local _, startC = getCursor() -- MODIFICATION
  1041.  
  1042.     local event, address, arg1, arg2, arg3 = term.pull()
  1043.     if address == term.keyboard() or address == term.screen() then
  1044.         local blink = true
  1045.         if event == "key_down" then
  1046.             onKeyDown(arg1, arg2)
  1047.         elseif event == "clipboard" and not readonly then
  1048.             onClipboard(arg1)
  1049.         elseif event == "touch" or event == "drag" then
  1050.             local x, y, w, h = getArea()
  1051.             arg1 = arg1 - x + 1
  1052.             arg2 = arg2 - y + 1
  1053.             if arg1 >= 1 and arg2 >= 1 and arg1 <= w and arg2 <= h then
  1054.                 onClick(arg1, arg2)
  1055.             end
  1056.         elseif event == "scroll" then
  1057.             onScroll(arg3)
  1058.         else
  1059.             blink = false
  1060.         end
  1061.         if blink then
  1062.             term.setCursorBlink(true)
  1063.         end
  1064.  
  1065.         -- MODIFICATION: redraw lines if needed
  1066.  
  1067.         if startX ~= scrollX or startY ~= scrollY then
  1068.             --fix()
  1069.         end
  1070.  
  1071.         local _, endC = getCursor()
  1072.         local x, y, w, h = getArea()
  1073.  
  1074.         if startC ~= endC then
  1075.             drawLine(x, y, w, h, startC)
  1076.         end
  1077.  
  1078.         drawLine(x, y, w, h, endC)
  1079.  
  1080.         -- END OF MODIFICATION
  1081.     end
  1082. end
  1083.  
  1084. resetColors()
  1085.  
  1086. term.clear()
  1087. term.setCursorBlink(true)
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top