SergOmarov

Untitled

Dec 31st, 2015
74
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. local syntax = require("syntax")
  2. local component = require("component")
  3. local event = require("event")
  4. local fs = require("filesystem")
  5. local keyboard = require("keyboard")
  6. local shell = require("shell")
  7. local term = require("term")
  8. local text = require("text")
  9. local unicode = require("unicode")
  10.  
  11. if not term.isAvailable() then
  12. return
  13. end
  14.  
  15. local args, options = shell.parse(...)
  16. if #args == 0 then
  17. io.write("Usage: edit <filename>")
  18. return
  19. end
  20.  
  21. local filename = shell.resolve(args[1])
  22.  
  23. local readonly = options.r or fs.get(filename) == nil or fs.get(filename).isReadOnly()
  24.  
  25. if not fs.exists(filename) then
  26. if fs.isDirectory(filename) then
  27. io.stderr:write("file is a directory")
  28. return
  29. elseif readonly then
  30. io.stderr:write("file system is read only")
  31. return
  32. end
  33. end
  34.  
  35. local function loadConfig()
  36. -- Try to load user settings.
  37. local env = {}
  38. local config = loadfile("/etc/edit.cfg", nil, env)
  39. if config then
  40. pcall(config)
  41. end
  42. -- Fill in defaults.
  43. env.keybinds = env.keybinds or {
  44. left = {{"left"}},
  45. right = {{"right"}},
  46. up = {{"up"}},
  47. down = {{"down"}},
  48. home = {{"home"}},
  49. eol = {{"end"}},
  50. pageUp = {{"pageUp"}},
  51. pageDown = {{"pageDown"}},
  52.  
  53. backspace = {{"back"}},
  54. delete = {{"delete"}},
  55. deleteLine = {{"control", "delete"}, {"shift", "delete"}},
  56. newline = {{"enter"}},
  57.  
  58. save = {{"control", "s"}},
  59. close = {{"control", "w"}},
  60. find = {{"control", "f"}},
  61. findnext = {{"control", "g"}, {"control", "n"}, {"f3"}}
  62. }
  63. -- Generate config file if it didn't exist.
  64. if not config then
  65. local root = fs.get("/")
  66. if root and not root.isReadOnly() then
  67. fs.makeDirectory("/etc")
  68. local f = io.open("/etc/edit.cfg", "w")
  69. if f then
  70. local serialization = require("serialization")
  71. for k, v in pairs(env) do
  72. f:write(k.."="..tostring(serialization.serialize(v, math.huge)).."\n")
  73. end
  74. f:close()
  75. end
  76. end
  77. end
  78. return env
  79. end
  80.  
  81. term.clear()
  82. term.setCursorBlink(true)
  83.  
  84. local running = true
  85. local buffer = {}
  86. local scrollX, scrollY = 0, 0
  87. local config = loadConfig()
  88.  
  89. local getKeyBindHandler -- forward declaration for refind()
  90.  
  91. local function helpStatusText()
  92. local function prettifyKeybind(label, command)
  93. local keybind = type(config.keybinds) == "table" and config.keybinds[command]
  94. if type(keybind) ~= "table" or type(keybind[1]) ~= "table" then return "" end
  95. local alt, control, shift, key
  96. for _, value in ipairs(keybind[1]) do
  97. if value == "alt" then alt = true
  98. elseif value == "control" then control = true
  99. elseif value == "shift" then shift = true
  100. else key = value end
  101. end
  102. if not key then return "" end
  103. return label .. ": [" ..
  104. (control and "Ctrl+" or "") ..
  105. (alt and "Alt+" or "") ..
  106. (shift and "Shift+" or "") ..
  107. unicode.upper(key) ..
  108. "] "
  109. end
  110. return prettifyKeybind("Save", "save") ..
  111. prettifyKeybind("Close", "close") ..
  112. prettifyKeybind("Find", "find")
  113. end
  114.  
  115. -------------------------------------------------------------------------------
  116.  
  117. local function setStatus(value)
  118. local w, h = component.gpu.getResolution()
  119. component.gpu.set(1, h, text.padRight(unicode.sub(value, 1, w - 10), w - 10))
  120. end
  121.  
  122. local function getSize()
  123. local w, h = component.gpu.getResolution()
  124. return w, h - 1
  125. end
  126.  
  127. local function getCursor()
  128. local cx, cy = term.getCursor()
  129. return cx + scrollX, cy + scrollY
  130. end
  131.  
  132. local function line()
  133. local cbx, cby = getCursor()
  134. return buffer[cby]
  135. end
  136.  
  137. local function setCursor(nbx, nby)
  138. local w, h = getSize()
  139. nby = math.max(1, math.min(#buffer, nby))
  140.  
  141. local ncy = nby - scrollY
  142. if ncy > h then
  143. term.setCursorBlink(false)
  144. local sy = nby - h
  145. local dy = math.abs(scrollY - sy)
  146. scrollY = sy
  147. component.gpu.copy(1, 1 + dy, w, h - dy, 0, -dy)
  148. for by = nby - (dy - 1), nby do
  149. local str = text.padRight(unicode.sub(buffer[by], 1 + scrollX), w)
  150. component.gpu.set(1, by - scrollY, str)
  151. end
  152. elseif ncy < 1 then
  153. term.setCursorBlink(false)
  154. local sy = nby - 1
  155. local dy = math.abs(scrollY - sy)
  156. scrollY = sy
  157. component.gpu.copy(1, 1, w, h - dy, 0, dy)
  158. for by = nby, nby + (dy - 1) do
  159. local str = text.padRight(unicode.sub(buffer[by], 1 + scrollX), w)
  160. component.gpu.set(1, by - scrollY, str)
  161. end
  162. end
  163. term.setCursor(term.getCursor(), nby - scrollY)
  164.  
  165. nbx = math.max(1, math.min(unicode.len(line()) + 1, nbx))
  166. local ncx = nbx - scrollX
  167. if ncx > w then
  168. term.setCursorBlink(false)
  169. local sx = nbx - w
  170. local dx = math.abs(scrollX - sx)
  171. scrollX = sx
  172. component.gpu.copy(1 + dx, 1, w - dx, h, -dx, 0)
  173. for by = 1 + scrollY, math.min(h + scrollY, #buffer) do
  174. local str = unicode.sub(buffer[by], nbx - (dx - 1), nbx)
  175. str = text.padRight(str, dx)
  176. component.gpu.set(1 + (w - dx), by - scrollY, str)
  177. end
  178. elseif ncx < 1 then
  179. term.setCursorBlink(false)
  180. local sx = nbx - 1
  181. local dx = math.abs(scrollX - sx)
  182. scrollX = sx
  183. component.gpu.copy(1, 1, w - dx, h, dx, 0)
  184. for by = 1 + scrollY, math.min(h + scrollY, #buffer) do
  185. local str = unicode.sub(buffer[by], nbx, nbx + dx)
  186. --str = text.padRight(str, dx)
  187. component.gpu.set(1, by - scrollY, str)
  188. end
  189. end
  190. term.setCursor(nbx - scrollX, nby - scrollY)
  191.  
  192. component.gpu.set(w - 9, h + 1, text.padLeft(string.format("%d,%d", nby, nbx), 10))
  193. end
  194.  
  195. local function highlight(bx, by, length, enabled)
  196. local w, h = getSize()
  197. local cx, cy = bx - scrollX, by - scrollY
  198. cx = math.max(1, math.min(w, cx))
  199. cy = math.max(1, math.min(h, cy))
  200. length = math.max(1, math.min(w - cx, length))
  201.  
  202. local fg, fgp = component.gpu.getForeground()
  203. local bg, bgp = component.gpu.getBackground()
  204. if enabled then
  205. component.gpu.setForeground(bg, bgp)
  206. component.gpu.setBackground(fg, fgp)
  207. end
  208. local value = ""
  209. for x = cx, cx + length - 1 do
  210. value = value .. component.gpu.get(x, cy)
  211. end
  212. component.gpu.set(cx, cy, value)
  213. if enabled then
  214. component.gpu.setForeground(fg, fgp)
  215. component.gpu.setBackground(bg, bgp)
  216. end
  217. end
  218.  
  219. local function home()
  220. local cbx, cby = getCursor()
  221. setCursor(1, cby)
  222. end
  223.  
  224. local function ende()
  225. local cbx, cby = getCursor()
  226. setCursor(unicode.len(line()) + 1, cby)
  227. end
  228.  
  229. local function left()
  230. local cbx, cby = getCursor()
  231. if cbx > 1 then
  232. setCursor(cbx - 1, cby)
  233. return true -- for backspace
  234. elseif cby > 1 then
  235. setCursor(cbx, cby - 1)
  236. ende()
  237. return true -- again, for backspace
  238. end
  239. end
  240.  
  241. local function right(n)
  242. n = n or 1
  243. local cbx, cby = getCursor()
  244. local be = unicode.len(line()) + 1
  245. if cbx < be then
  246. setCursor(cbx + n, cby)
  247. elseif cby < #buffer then
  248. setCursor(1, cby + 1)
  249. end
  250. end
  251.  
  252. local function up(n)
  253. n = n or 1
  254. local cbx, cby = getCursor()
  255. if cby > 1 then
  256. setCursor(cbx, cby - n)
  257. if getCursor() > unicode.len(line()) then
  258. ende()
  259. end
  260. end
  261. end
  262.  
  263. local function down(n)
  264. n = n or 1
  265. local cbx, cby = getCursor()
  266. if cby < #buffer then
  267. setCursor(cbx, cby + n)
  268. if getCursor() > unicode.len(line()) then
  269. ende()
  270. end
  271. end
  272. end
  273.  
  274. local function delete(fullRow)
  275. local cx, cy = term.getCursor()
  276. local cbx, cby = getCursor()
  277. local w, h = getSize()
  278. local function deleteRow(row)
  279. local content = table.remove(buffer, row)
  280. local rcy = cy + (row - cby)
  281. if rcy <= h then
  282. component.gpu.copy(1, rcy + 1, w, h - rcy, 0, -1)
  283. component.gpu.set(1, h, text.padRight(buffer[row + (h - rcy)], w))
  284. end
  285. return content
  286. end
  287. if fullRow then
  288. term.setCursorBlink(false)
  289. if #buffer > 1 then
  290. deleteRow(cby)
  291. else
  292. buffer[cby] = ""
  293. component.gpu.fill(1, cy, w, 1, " ")
  294. end
  295. setCursor(1, cby)
  296. elseif cbx <= unicode.len(line()) then
  297. term.setCursorBlink(false)
  298. buffer[cby] = unicode.sub(line(), 1, cbx - 1) ..
  299. unicode.sub(line(), cbx + 1)
  300. component.gpu.copy(cx + 1, cy, w - cx, 1, -1, 0)
  301. local br = cbx + (w - cx)
  302. local char = unicode.sub(line(), br, br)
  303. if not char or unicode.len(char) == 0 then
  304. char = " "
  305. end
  306. component.gpu.set(w, cy, char)
  307. elseif cby < #buffer then
  308. term.setCursorBlink(false)
  309. local append = deleteRow(cby + 1)
  310. buffer[cby] = buffer[cby] .. append
  311. component.gpu.set(cx, cy, append)
  312. else
  313. return
  314. end
  315. setStatus(helpStatusText())
  316. end
  317.  
  318. local function insert(value)
  319. if not value or unicode.len(value) < 1 then
  320. return
  321. end
  322. term.setCursorBlink(false)
  323. local cx, cy = term.getCursor()
  324. local cbx, cby = getCursor()
  325. local w, h = getSize()
  326. buffer[cby] = unicode.sub(line(), 1, cbx - 1) ..
  327. value ..
  328. unicode.sub(line(), cbx)
  329. local len = unicode.len(value)
  330. local n = w - (cx - 1) - len
  331. if n > 0 then
  332. component.gpu.copy(cx, cy, n, 1, len, 0)
  333. end
  334. syntax.highlightAndDraw(1, cy, w, buffer[cby])
  335. --component.gpu.set(cx, cy, value)
  336. right(len)
  337. setStatus(helpStatusText())
  338. end
  339.  
  340. local function enter()
  341. term.setCursorBlink(false)
  342. local cx, cy = term.getCursor()
  343. local cbx, cby = getCursor()
  344. local w, h = getSize()
  345. table.insert(buffer, cby + 1, unicode.sub(buffer[cby], cbx))
  346. buffer[cby] = unicode.sub(buffer[cby], 1, cbx - 1)
  347. component.gpu.fill(cx, cy, w - (cx - 1), 1, " ")
  348. if cy < h then
  349. if cy < h - 1 then
  350. component.gpu.copy(1, cy + 1, w, h - (cy + 1), 0, 1)
  351. end
  352. component.gpu.set(1, cy + 1, text.padRight(buffer[cby + 1], w))
  353. end
  354. setCursor(1, cby + 1)
  355. setStatus(helpStatusText())
  356. end
  357.  
  358. local findText = ""
  359.  
  360. local function find()
  361. local w, h = getSize()
  362. local cx, cy = term.getCursor()
  363. local cbx, cby = getCursor()
  364. local ibx, iby = cbx, cby
  365. while running do
  366. if unicode.len(findText) > 0 then
  367. local sx, sy
  368. for syo = 1, #buffer do -- iterate lines with wraparound
  369. sy = (iby + syo - 1 + #buffer - 1) % #buffer + 1
  370. sx = string.find(buffer[sy], findText, syo == 1 and ibx or 1, true)
  371. if sx and (sx >= ibx or syo > 1) then
  372. break
  373. end
  374. end
  375. if not sx then -- special case for single matches
  376. sy = iby
  377. sx = string.find(buffer[sy], findText, nil, true)
  378. end
  379. if sx then
  380. cbx, cby = sx, sy
  381. setCursor(cbx, cby)
  382. highlight(cbx, cby, unicode.len(findText), true)
  383. end
  384. end
  385. term.setCursor(7 + unicode.len(findText), h + 1)
  386. setStatus("Find: " .. findText)
  387.  
  388. local _, _, char, code = event.pull("key_down")
  389. local handler, name = getKeyBindHandler(code)
  390. highlight(cbx, cby, unicode.len(findText), false)
  391. if name == "newline" then
  392. break
  393. elseif name == "close" then
  394. handler()
  395. elseif name == "backspace" then
  396. findText = unicode.sub(findText, 1, -2)
  397. elseif name == "find" or name == "findnext" then
  398. ibx = cbx + 1
  399. iby = cby
  400. elseif not keyboard.isControl(char) then
  401. findText = findText .. unicode.char(char)
  402. end
  403. end
  404. setCursor(cbx, cby)
  405. setStatus(helpStatusText())
  406. end
  407.  
  408. -------------------------------------------------------------------------------
  409.  
  410. local keyBindHandlers = {
  411. left = left,
  412. right = right,
  413. up = up,
  414. down = down,
  415. home = home,
  416. eol = ende,
  417. pageUp = function()
  418. local w, h = getSize()
  419. up(h - 1)
  420. end,
  421. pageDown = function()
  422. local w, h = getSize()
  423. down(h - 1)
  424. end,
  425.  
  426. backspace = function()
  427. if not readonly and left() then
  428. delete()
  429. end
  430. end,
  431. delete = function()
  432. if not readonly then
  433. delete()
  434. end
  435. end,
  436. deleteLine = function()
  437. if not readonly then
  438. delete(true)
  439. end
  440. end,
  441. newline = function()
  442. if not readonly then
  443. enter()
  444. end
  445. end,
  446.  
  447. save = function()
  448. if readonly then return end
  449. local new = not fs.exists(filename)
  450. local backup
  451. if not new then
  452. backup = filename .. "~"
  453. for i = 1, math.huge do
  454. if not fs.exists(backup) then
  455. break
  456. end
  457. backup = filename .. "~" .. i
  458. end
  459. fs.copy(filename, backup)
  460. end
  461. local f, reason = io.open(filename, "w")
  462. if f then
  463. local chars, firstLine = 0, true
  464. for _, line in ipairs(buffer) do
  465. if not firstLine then
  466. line = "\n" .. line
  467. end
  468. firstLine = false
  469. f:write(line)
  470. chars = chars + unicode.len(line)
  471. end
  472. f:close()
  473. local format
  474. if new then
  475. format = [["%s" [New] %dL,%dC written]]
  476. else
  477. format = [["%s" %dL,%dC written]]
  478. end
  479. setStatus(string.format(format, fs.name(filename), #buffer, chars))
  480. else
  481. setStatus(reason)
  482. end
  483. if not new then
  484. fs.remove(backup)
  485. end
  486. end,
  487. close = function()
  488. -- TODO ask to save if changed
  489. running = false
  490. end,
  491. find = function()
  492. findText = ""
  493. find()
  494. end,
  495. findnext = find
  496. }
  497.  
  498. getKeyBindHandler = function(code)
  499. if type(config.keybinds) ~= "table" then return end
  500. -- Look for matches, prefer more 'precise' keybinds, e.g. prefer
  501. -- ctrl+del over del.
  502. local result, resultName, resultWeight = nil, nil, 0
  503. for command, keybinds in pairs(config.keybinds) do
  504. if type(keybinds) == "table" and keyBindHandlers[command] then
  505. for _, keybind in ipairs(keybinds) do
  506. if type(keybind) == "table" then
  507. local alt, control, shift, key
  508. for _, value in ipairs(keybind) do
  509. if value == "alt" then alt = true
  510. elseif value == "control" then control = true
  511. elseif value == "shift" then shift = true
  512. else key = value end
  513. end
  514. if (not alt or keyboard.isAltDown()) and
  515. (not control or keyboard.isControlDown()) and
  516. (not shift or keyboard.isShiftDown()) and
  517. code == keyboard.keys[key] and
  518. #keybind > resultWeight
  519. then
  520. resultWeight = #keybind
  521. resultName = command
  522. result = keyBindHandlers[command]
  523. end
  524. end
  525. end
  526. end
  527. end
  528. return result, resultName
  529. end
  530.  
  531. -------------------------------------------------------------------------------
  532.  
  533. local function onKeyDown(char, code)
  534. local handler = getKeyBindHandler(code)
  535. if handler then
  536. handler()
  537. elseif readonly and code == keyboard.keys.q then
  538. running = false
  539. elseif not readonly then
  540. if not keyboard.isControl(char) then
  541. insert(unicode.char(char))
  542. elseif unicode.char(char) == "\t" then
  543. insert(" ")
  544. end
  545. end
  546. end
  547.  
  548. local function onClipboard(value)
  549. value = value:gsub("\r\n", "\n")
  550. local cbx, cby = getCursor()
  551. local start = 1
  552. local l = value:find("\n", 1, true)
  553. if l then
  554. repeat
  555. local line = string.sub(value, start, l - 1)
  556. line = text.detab(line, 2)
  557. insert(line)
  558. enter()
  559. start = l + 1
  560. l = value:find("\n", start, true)
  561. until not l
  562. end
  563. insert(string.sub(value, start))
  564. end
  565.  
  566. local function onClick(x, y)
  567. setCursor(x + scrollX, y + scrollY)
  568. end
  569.  
  570. local function onScroll(direction)
  571. local cbx, cby = getCursor()
  572. setCursor(cbx, cby - direction * 12)
  573. end
  574.  
  575. -------------------------------------------------------------------------------
  576.  
  577. do
  578. local f = io.open(filename)
  579. if f then
  580. local w, h = getSize()
  581. local chars = 0
  582. for line in f:lines() do
  583. if line:sub(-1) == "\r" then
  584. line = line:sub(1, -2)
  585. end
  586. table.insert(buffer, line)
  587. chars = chars + unicode.len(line)
  588. if #buffer <= h then
  589. component.gpu.set(1, #buffer, line)
  590. end
  591. end
  592. f:close()
  593. if #buffer == 0 then
  594. table.insert(buffer, "")
  595. end
  596. local format
  597. if readonly then
  598. format = [["%s" [readonly] %dL,%dC]]
  599. else
  600. format = [["%s" %dL,%dC]]
  601. end
  602. setStatus(string.format(format, fs.name(filename), #buffer, chars))
  603. else
  604. table.insert(buffer, "")
  605. setStatus(string.format([["%s" [New File] ]], fs.name(filename)))
  606. end
  607. setCursor(1, 1)
  608. end
  609.  
  610. while running do
  611. local event, address, arg1, arg2, arg3 = event.pull()
  612. if type(address) == "string" and component.isPrimary(address) then
  613. local blink = true
  614. if event == "key_down" then
  615. onKeyDown(arg1, arg2)
  616. elseif event == "clipboard" and not readonly then
  617. onClipboard(arg1)
  618. elseif event == "touch" or event == "drag" then
  619. onClick(arg1, arg2)
  620. elseif event == "scroll" then
  621. onScroll(arg3)
  622. else
  623. blink = false
  624. end
  625. if blink then
  626. term.setCursorBlink(true)
  627. term.setCursorBlink(true) -- force toggle to caret
  628. end
  629. end
  630. end
  631.  
  632. term.clear()
  633. term.setCursorBlink(false)
RAW Paste Data