Advertisement
slipers

Minecraft. OpenComputers. Cedit (/bin/cedit.lua).

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