Belzebub

Nano.lua

Jan 22nd, 2018
224
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.99 KB | None | 0 0
  1. --[[
  2. nano clone based off GNU nano for OpenComputers
  3. by Gamax92
  4. --]]
  5. local component = require("component")
  6. local computer = require("computer")
  7. local kbd = require("keyboard")
  8. local keys = kbd.keys
  9. local fs = require("filesystem")
  10. local shell = require("shell")
  11. local event = require("event")
  12. local term = require("term")
  13. local unicode = require("unicode")
  14.  
  15. local nanoVERSION = "1.0.0"
  16.  
  17. local function printf(...) print(string.format(...)) end
  18. local function eprintf(...) io.stderr:write(string.format(...) .. "\n") end
  19. local event_pull = term.pull or event.pull
  20. -- Fix keys.none
  21. keys["none"] = 0
  22. keys[0] = "none"
  23.  
  24. -- TODO: Replace with more gnuopt style parser
  25. local args, opts = shell.parse(...)
  26.  
  27. -- TODO: Once above has been done, check for invalid arguments
  28. -- Default options
  29. local options = {
  30. fgcolor = 0xFFFFFF,
  31. bgcolor = 0x000000,
  32. tabsize = 8,
  33. }
  34. local flags={"autoindent","backup","backwards","brighttext","casesensitive","const","cut","morespace","mouse","multibuffer","noconvert","nohelp","nonewlines","nowrap","quiet","quickblank","patterns","smarthome","smooth","softwrap","tabstospaces","tempfile","undo","view"}
  35. for i = 1,#flags do
  36. options[flags[i]] = false
  37. end
  38. -- TODO: HACK, REMOVE WHEN HELP AREA IS IMPLEMENTED
  39. options.nohelp = true
  40. flags = nil
  41. -- Default bindings
  42. -- TODO: Bindings for things other than main
  43. local bind = {
  44. main = {
  45. {"help","^G","F1"},
  46. {"exit","^X","F2"},
  47. {"writeout","^O","F3"},
  48. {"insert","^R","F5","insert"},
  49. {"whereis","^W","F6"},
  50. {"replace","^\\","M-R","F14"},
  51. {"cut","^K","F9"},
  52. {"uncut","^U","F10"},
  53. {"justify","^J","F4"},
  54. {"speller","^T","F12"},
  55. {"curpos","^C","F11"},
  56. {"gotoline","^_","M-G","F13"},
  57. {"prevpage","^Y","F7","pageUp"},
  58. {"nextpage","^V","F8","pageDown"},
  59. {"firstline","M-\\","M-|"},
  60. {"lastline","M-/","M-?"},
  61. {"searchagain","M-W","F16"},
  62. {"findbracket","M-]"},
  63. {"mark","^^","M-A","F15"},
  64. {"copytext","M-^","M-6"},
  65. {"indent","M-}"},
  66. {"unindent","M-{"},
  67. {"undo","M-U"},
  68. {"redo","M-E"},
  69. {"left","^B","left"},
  70. {"right","^F","right"},
  71. {"prevword","M-Space"},
  72. {"nextword","^Space"},
  73. {"home","^A","home"},
  74. {"end","^E","end"},
  75. {"prevline","^P","up"},
  76. {"nextline","^N","down"},
  77. {"beginpara","M-(","M-9"},
  78. {"endpara","M-)","M-0"},
  79. {"scrollup","M--","M-_"},
  80. {"scrolldown","M-+","M-="},
  81. {"prevbuf","M-<","M-,"},
  82. {"nextbuf","M->","M-."},
  83. {"verbatim","M-V"},
  84. {"tab","^I"},
  85. {"enter","^M","enter"},
  86. {"delete","^D","delete"},
  87. {"backspace","^H","back"},
  88. {"cutrestoffile","M-T"},
  89. {"fulljustify","M-J"},
  90. {"wordcount","M-D"},
  91. {"refresh","^L"},
  92. {"suspend","^Z"},
  93. {"nohelp","M-X"},
  94. {"constupdate","M-C"},
  95. {"morespace","M-O"},
  96. {"smoothscroll","M-S"},
  97. {"softwrap","M-$"},
  98. {"whitespacedisplay","M-P"},
  99. {"nosyntax","M-Y"},
  100. {"smarthome","M-H"},
  101. {"autoindent","M-I"},
  102. {"cuttoend","M-K"},
  103. {"nowrap","M-L"},
  104. {"tabstospaces","M-Q"},
  105. {"backupfile","M-B"},
  106. {"multibuffer","M-F"},
  107. {"mouse","M-M"},
  108. {"noconvert","M-N"},
  109. {"suspendenable","M-Z"},
  110. }
  111. }
  112. local vfn = {}
  113. for k,v in pairs(bind) do
  114. for i = 1,#v do
  115. local b = v[i]
  116. for j = 2,#b do
  117. v[unicode.upper(b[j])]=b[1]
  118. end
  119. vfn[b[1]]=true
  120. v[i] = nil
  121. end
  122. end
  123.  
  124. local function parseRC(filename)
  125. local problem = false
  126. local i = 1
  127. local function bad(msg, ...)
  128. problem = true
  129. printf("Error in %s on line %d: " .. msg, filename, i, ...)
  130. end
  131. local file, err = io.open(filename,"rb")
  132. if not file then
  133. problem = true; printf("Error reading %s: %s", filename, err)
  134. else
  135. for line in file:lines() do
  136. local cmd = {}
  137. for part in line:gmatch("%S+") do
  138. cmd[#cmd+1] = part
  139. end
  140. if cmd[1] == "set" or cmd[1] == "unset" then
  141. if #cmd < 2 then
  142. bad("Missing flag")
  143. elseif options[cmd[2]] ~= nil then
  144. if type(options[cmd[2]]) == "boolean" then
  145. options[cmd[2]] = cmd[1] == "set"
  146. elseif cmd[1] == "unset" then
  147. bad("Cannot unset option \"%s\"", cmd[2])
  148. elseif type(options[cmd[2]]) == "number" then
  149. if tonumber(cmd[3]) == nil then
  150. bad("Parameter \"%s\" is invalid", cmd[3])
  151. else
  152. options[cmd[2]] = tonumber(cmd[3])
  153. end
  154. end
  155. else
  156. bad("Unknown flag \"%s\"", cmd[2])
  157. end
  158. elseif cmd[1] == "bind" or cmd[1] == "unbind" then
  159. if cmd[2] == nil then
  160. bad("Missing key name")
  161. else
  162. local first = unicode.upper(cmd[2]:sub(1,1))
  163. local last = cmd[1] == "bind" and cmd[4] or cmd[3]
  164. if first ~= "^" and first ~= "M" and first ~= "F" then
  165. bad("Key name must begin with \"^\", \"M\", or \"F\"")
  166. elseif cmd[1] == "bind" and cmd[3] == nil then
  167. bad("Must specify a function to bind the key to")
  168. elseif last == nil then
  169. bad("Must specify a menu (or \"all\") in which to bind/unbind the key")
  170. elseif cmd[1] == "bind" and not vfn[cmd[3]] then
  171. bad("Cannot map name \"%s\" to a function", cmd[3])
  172. elseif bind[last] == nil then
  173. bad("Cannot map name \"%s\" to a menu", last)
  174. else
  175. if cmd[1] == "bind" then
  176. bind[last][cmd[2]] = cmd[3]
  177. elseif cmd[1] == "unbind" then
  178. bind[last][cmd[2]] = nil
  179. end
  180. end
  181. end
  182. else
  183. bad("Command \"%s\" not understood", cmd[1])
  184. end
  185. i = i + 1
  186. end
  187. end
  188. if problem then
  189. print("Press Enter to continue starting nano.")
  190. while true do
  191. local name,_,_,code = event_pull()
  192. if name == "key_down" and code == keys.enter then break end
  193. end
  194. end
  195. end
  196. if fs.exists("/etc/nanorc") then
  197. parseRC("/etc/nanorc")
  198. end
  199. -- TODO: Parse arguments
  200.  
  201. local buffers, buffer = {}
  202. local gpu = component.gpu
  203. gpu.setForeground(options.fgcolor)
  204. gpu.setBackground(options.bgcolor)
  205. local gpuW, gpuH = gpu.getResolution()
  206. gpu.fill(1,1,gpuW,gpuH," ")
  207. term.setCursorBlink(true)
  208.  
  209. -- TODO: more modes
  210. local mode = "main"
  211. -- Calculate positions for screen elements
  212. local linesY = (options.morespace and 2 or 3)
  213. local linesH = gpuH - linesY
  214. local statusbarY = gpuH
  215. if not options.nohelp then
  216. linesH = linesH - 2
  217. statusbarY = statusbarY - 2
  218. end
  219. -- TODO: Help lines
  220.  
  221.  
  222. local utf8char = "[%z\1-\127\194-\244][\128-\191]*"
  223. local function formatLine(text,map)
  224. local newstr, pos = {}, 0
  225. local cmap
  226. if map then
  227. cmap = {}
  228. end
  229. for char in text:gmatch(utf8char) do
  230. if map then
  231. cmap[#cmap+1] = pos+1
  232. end
  233. if char == "\t" then
  234. local amt = options.tabsize - pos%options.tabsize
  235. for i = 1,amt do
  236. newstr[#newstr+1] = " "
  237. end
  238. pos = pos + amt
  239. else
  240. newstr[#newstr+1] = char
  241. pos = pos + unicode.charWidth(char)
  242. end
  243. end
  244. if map then
  245. cmap[#cmap+1] = pos+1
  246. end
  247. return table.concat(newstr), cmap
  248. end
  249.  
  250. local function setTitleBar(buffer)
  251. local title = " New Buffer "
  252. if buffer.filename ~= nil then
  253. title = " File: " .. buffer.filename:match(".*/(.+)") .. " "
  254. end
  255. gpu.setForeground(options.bgcolor)
  256. gpu.setBackground(options.fgcolor)
  257. gpu.fill(1,1,gpuW,1," ")
  258. local startX = (gpuW - unicode.wlen(title))/2
  259. gpu.set(3,1,"Lua nano " .. nanoVERSION)
  260. if buffer.modified then
  261. local str = "Modified"
  262. gpu.set(gpuW-unicode.wlen(str)-2,1,str)
  263. end
  264. gpu.set(startX,1,title)
  265. gpu.setForeground(options.fgcolor)
  266. gpu.setBackground(options.bgcolor)
  267. end
  268.  
  269. local function setModified(buffer,mod)
  270. if mod == nil then mod = true end
  271. if buffer.modified ~= mod then
  272. buffer.modified = mod
  273. setTitleBar(buffer)
  274. end
  275. end
  276.  
  277. local timeout = math.huge
  278. local function setStatusBar(text)
  279. timeout = 0
  280. text = "[ " .. text .. " ]"
  281. gpu.fill(1,statusbarY,gpuW,1," ")
  282. gpu.setForeground(options.bgcolor)
  283. gpu.setBackground(options.fgcolor)
  284. local startX = (gpuW - unicode.wlen(text))/2
  285. gpu.set(startX,statusbarY,text)
  286. gpu.setForeground(options.fgcolor)
  287. gpu.setBackground(options.bgcolor)
  288. end
  289.  
  290. local function createNewBuffer()
  291. return {
  292. filename=nil,
  293. modified=false,
  294. lines = {
  295. },
  296. x = 1,
  297. tx = 1,
  298. y = 1,
  299. startLine = 1,
  300. }
  301. end
  302.  
  303. local function createBuffer(filename)
  304. local buffer = createNewBuffer()
  305. if fs.exists(filename) then
  306. local file, err = io.open(filename,"rb")
  307. if file then
  308. buffer.filename = filename
  309. for line in file:lines() do
  310. buffer.lines[#buffer.lines+1] = line
  311. end
  312. file:close()
  313. setStatusBar("Read " .. #buffer.lines .. " line" .. (#buffer.lines ~= 1 and "s" or ""))
  314. else
  315. setStatusBar("Couldn't open " .. filename .. ": " .. err)
  316. end
  317. else
  318. buffer.filename = filename
  319. end
  320. if #buffer.lines == 0 then
  321. buffer.lines[1] = ""
  322. end
  323. buffers[#buffers+1] = buffer
  324. end
  325.  
  326. local function drawLine(line,y)
  327. local fline = formatLine(line)
  328. if unicode.wlen(fline) < gpuW then
  329. fline = fline .. string.rep(" ",gpuW - unicode.wlen(fline))
  330. elseif unicode.wlen(fline) > gpuW then
  331. fline = unicode.wtrunc(fline,gpuW)
  332. fline = fline .. string.rep(" ",gpuW - unicode.wlen(fline) - 1) .. "$"
  333. end
  334. gpu.set(1,y,fline)
  335. end
  336.  
  337. local function updateActiveLine(simple)
  338. local line = buffer.lines[buffer.y]
  339. term.setCursorBlink(false)
  340. if not simple then
  341. local fline,map = formatLine(line,true)
  342. -- TODO: This is not nano style line clamping, plus it messes up when tabs have things behind them
  343. local xwant = map[math.min(buffer.x,#map)]
  344. local gwant = math.floor(gpuW*3/4)
  345. if unicode.wlen(fline) >= gpuW and xwant >= gwant then
  346. for i = 2,#map do
  347. if xwant-map[i]+1 < gwant then
  348. line = unicode.sub(line,i)
  349. xwant = xwant-map[i]+1
  350. break
  351. end
  352. end
  353. end
  354. term.setCursor(xwant,buffer.y-buffer.startLine+linesY)
  355. end
  356. drawLine(line,buffer.y-buffer.startLine+linesY)
  357. for i=1,2 do term.setCursorBlink(true)end
  358. end
  359.  
  360. local function updateTX()
  361. local _,map = formatLine(buffer.lines[buffer.y],true)
  362. buffer.tx = map[math.min(buffer.x,#map)]
  363. end
  364.  
  365. local function scrollBuffer()
  366. local amt
  367. local startLine = buffer.startLine
  368. if buffer.y-startLine < 0 then
  369. amt = buffer.y-startLine
  370. elseif buffer.y-startLine > (linesH-1) then
  371. amt = buffer.y-startLine-(linesH-1)
  372. end
  373. if not amt then return end
  374. term.setCursorBlink(false)
  375. startLine = startLine + amt
  376. buffer.startLine = startLine
  377. if amt > 0 then
  378. gpu.copy(1,linesY+amt,gpuW,linesH-amt,0,-amt)
  379. for i = startLine+linesH-1-amt, math.min(startLine+linesH-1,#buffer.lines) do
  380. drawLine(buffer.lines[i],i-startLine+linesY)
  381. end
  382. else
  383. gpu.copy(1,linesY,gpuW,linesH+amt,0,-amt)
  384. for i = startLine, math.min(startLine-amt-1,#buffer.lines) do
  385. drawLine(buffer.lines[i],i-startLine+linesY)
  386. end
  387. end
  388. term.setCursorBlink(true)
  389. end
  390.  
  391. local function redraw()
  392. setTitleBar(buffer)
  393. -- TODO: Only clear the area lacking lines
  394. gpu.fill(1,linesY,gpuW,linesH," ")
  395. local startLine, amt = buffer.startLine
  396. if buffer.y-startLine < 0 then
  397. amt = buffer.y-startLine
  398. elseif buffer.y-startLine > (linesH-1) then
  399. amt = buffer.y-startLine-(linesH-1)
  400. end
  401. if amt then
  402. startLine = startLine + amt
  403. buffer.startLine = startLine
  404. end
  405. for i = startLine, math.min(startLine+linesH-1,#buffer.lines) do
  406. drawLine(buffer.lines[i],i-startLine+linesY)
  407. end
  408. updateActiveLine()
  409. end
  410.  
  411. local function switchBuffers(index)
  412. buffer = buffers[index]
  413. if buffers.cur ~= nil then
  414. local name = "New Buffer"
  415. if buffer.filename ~= nil then
  416. name = buffer.filename:match(".*/(.+)")
  417. end
  418. setStatusBar("Switched to " .. name)
  419. end
  420. buffers.cur = index
  421. redraw()
  422. end
  423.  
  424. local function statusPrompt(text)
  425. gpu.fill(1,statusbarY,gpuW,1," ")
  426. gpu.set(1,statusbarY,text)
  427. term.setCursor(unicode.wlen(text)+1,statusbarY)
  428. end
  429.  
  430. local function resetColor()
  431. gpu.setForeground(options.fgcolor)
  432. gpu.setBackground(options.bgcolor)
  433. end
  434.  
  435. local running = true
  436. local function clul() return unicode.len(buffer.lines[buffer.y]) end
  437. local binding = {}
  438. function binding.exit()
  439. -- TODO: Get a MYESNO mode
  440. if buffer.modified then
  441. gpu.setForeground(options.bgcolor)
  442. gpu.setBackground(options.fgcolor)
  443. statusPrompt("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? ")
  444. while true do
  445. local e,_,c = event_pull()
  446. if e == "key_down" then
  447. c = unicode.lower(unicode.char(c))
  448. local ctrl = kbd.isControlDown()
  449. if not ctrl and c == "y" then
  450. -- TODO: Get a MWRITEFILE mode
  451. -- TODO: Needs more options
  452. local oldname = ""
  453. if buffer.filename ~= nil then
  454. oldname = buffer.filename:match(".*/(.+)")
  455. end
  456. local filename = oldname
  457. ::tryagain::
  458. statusPrompt("File Name to Write: ")
  459. -- TODO: HACK HACK HACK
  460. computer.pushSignal("key_down",computer.address(),0,keys.up)
  461. filename = term.read({filename},false)
  462. if filename then
  463. filename = filename:gsub("[\r\n]","")
  464. end
  465. if filename == "" or filename == nil then
  466. resetColor()
  467. setStatusBar("Cancelled")
  468. return
  469. end
  470. if filename ~= oldname and fs.exists(shell.resolve(filename)) then
  471. -- TODO: Again, MYESNO mode
  472. statusPrompt("File exists, OVERWRITE ? ")
  473. while true do
  474. local e,_,c = event_pull()
  475. c = unicode.lower(unicode.char(c))
  476. if e == "key_down" then
  477. local ctrl = kbd.isControlDown()
  478. if not ctrl and c == "y" then
  479. break
  480. elseif (not ctrl and c == "n") or (ctrl and c == "\3") then
  481. goto tryagain
  482. end
  483. end
  484. end
  485. end
  486. local file, err = io.open(shell.resolve(filename),"wb")
  487. if not file then
  488. resetColor()
  489. setStatusBar("Error writing " .. filename .. ": " .. err)
  490. return
  491. end
  492. for i = 1,#buffer.lines do
  493. file:write(buffer.lines[i] .. "\n")
  494. end
  495. file:close()
  496. break
  497. elseif not ctrl and c == "n" then
  498. break
  499. elseif ctrl and c == "\3" then
  500. resetColor()
  501. setStatusBar("Cancelled")
  502. return
  503. end
  504. end
  505. end
  506. end
  507. gpu.setForeground(options.fgcolor)
  508. gpu.setBackground(options.bgcolor)
  509. table.remove(buffers,buffers.cur)
  510. buffers.cur = math.min(buffers.cur,#buffers)
  511. if buffers.cur < 1 then
  512. running = false
  513. else
  514. switchBuffers(buffers.cur)
  515. end
  516. end
  517. function binding.left()
  518. if buffer.x > 1 or buffer.y > 1 then
  519. buffer.x = buffer.x - 1
  520. if buffer.x < 1 then
  521. updateActiveLine(true)
  522. buffer.y = buffer.y - 1
  523. buffer.x = unicode.len(buffer.lines[buffer.y])+1
  524. scrollBuffer()
  525. end
  526. updateTX()
  527. updateActiveLine()
  528. end
  529. end
  530. function binding.right()
  531. if buffer.x < clul()+1 or buffer.y < #buffer.lines then
  532. buffer.x = buffer.x + 1
  533. if buffer.x > clul()+1 then
  534. updateActiveLine(true)
  535. buffer.y = buffer.y + 1
  536. buffer.x = 1
  537. scrollBuffer()
  538. end
  539. updateTX()
  540. updateActiveLine()
  541. end
  542. end
  543. function binding.prevline()
  544. if buffer.y > 1 then
  545. updateActiveLine(true)
  546. buffer.y = buffer.y - 1
  547. local _,map = formatLine(buffer.lines[buffer.y],true)
  548. for i = 1,#map do
  549. if map[i] > buffer.tx then break end
  550. buffer.x = i
  551. end
  552. scrollBuffer()
  553. updateActiveLine()
  554. end
  555. end
  556. function binding.nextline()
  557. if buffer.y < #buffer.lines then
  558. updateActiveLine(true)
  559. buffer.y = buffer.y + 1
  560. local _,map = formatLine(buffer.lines[buffer.y],true)
  561. for i = 1,#map do
  562. if map[i] > buffer.tx then break end
  563. buffer.x = i
  564. end
  565. scrollBuffer()
  566. updateActiveLine()
  567. end
  568. end
  569. function binding.prevpage()
  570. if buffer.y > 1 then
  571. local diff = math.max(buffer.y - linesH, 1) - buffer.y
  572. buffer.y = buffer.y + diff
  573. buffer.startLine = math.max(buffer.startLine + diff, 1)
  574. local _,map = formatLine(buffer.lines[buffer.y],true)
  575. for i = 1,#map do
  576. if map[i] > buffer.tx then break end
  577. buffer.x = i
  578. end
  579. redraw()
  580. end
  581. end
  582. function binding.nextpage()
  583. if buffer.y < #buffer.lines then
  584. local diff = math.min(buffer.y + linesH, #buffer.lines) - buffer.y
  585. buffer.y = buffer.y + diff
  586. buffer.startLine = math.min(buffer.startLine + diff, #buffer.lines-linesH+1)
  587. local _,map = formatLine(buffer.lines[buffer.y],true)
  588. for i = 1,#map do
  589. if map[i] > buffer.tx then break end
  590. buffer.x = i
  591. end
  592. redraw()
  593. end
  594. end
  595. function binding.home()
  596. if buffer.x > 1 then
  597. buffer.x = 1
  598. updateTX()
  599. updateActiveLine()
  600. end
  601. end
  602. binding["end"] = function()
  603. if buffer.x < clul()+1 then
  604. buffer.x = clul()+1
  605. updateTX()
  606. updateActiveLine()
  607. end
  608. end
  609. function binding.backspace()
  610. if buffer.x == 1 then
  611. if buffer.y > 1 then
  612. setModified(buffer)
  613. buffer.x = unicode.len(buffer.lines[buffer.y-1])+1
  614. buffer.lines[buffer.y-1] = buffer.lines[buffer.y-1] .. buffer.lines[buffer.y]
  615. table.remove(buffer.lines,buffer.y)
  616. buffer.y = buffer.y - 1
  617. -- TODO: Don't redraw everything
  618. redraw()
  619. end
  620. else
  621. setModified(buffer)
  622. local line = buffer.lines[buffer.y]
  623. buffer.lines[buffer.y] = unicode.sub(line,1,buffer.x-2) .. unicode.sub(line,buffer.x)
  624. buffer.x = buffer.x - 1
  625. updateActiveLine()
  626. end
  627. updateTX()
  628. end
  629. function binding.delete()
  630. if buffer.x >= clul()+1 then
  631. setModified(buffer)
  632. if buffer.y < #buffer.lines then
  633. buffer.lines[buffer.y] = buffer.lines[buffer.y] .. buffer.lines[buffer.y+1]
  634. table.remove(buffer.lines,buffer.y+1)
  635. -- TODO: Don't redraw everything
  636. redraw()
  637. end
  638. else
  639. setModified(buffer)
  640. local line = buffer.lines[buffer.y]
  641. buffer.lines[buffer.y] = unicode.sub(line,1,buffer.x-1) .. unicode.sub(line,buffer.x+1)
  642. updateActiveLine()
  643. end
  644. end
  645. function binding.enter()
  646. setModified(buffer)
  647. local line = buffer.lines[buffer.y]
  648. table.insert(buffer.lines,buffer.y+1,unicode.sub(line,buffer.x))
  649. buffer.lines[buffer.y] = unicode.sub(line,1,buffer.x-1)
  650. buffer.x = 1
  651. buffer.tx = 1
  652. buffer.y = buffer.y + 1
  653. -- TODO: Don't redraw everything
  654. redraw()
  655. end
  656. function binding.prevbuf()
  657. buffers.cur = ((buffers.cur-2)%#buffers)+1
  658. switchBuffers(buffers.cur)
  659. end
  660. function binding.nextbuf()
  661. buffers.cur = (buffers.cur%#buffers)+1
  662. switchBuffers(buffers.cur)
  663. end
  664. function binding.curpos()
  665. -- TODO: Character count
  666. local la,lb = buffer.y,#buffer.lines
  667. local oa,ob = buffer.x,#buffer.lines[buffer.y]+1
  668. local cb = 0
  669. for i = 1,buffer.y-1 do
  670. cb = cb + #buffer.lines[i]+1
  671. end
  672. local ca = cb + buffer.x
  673. for i = buffer.y,#buffer.lines do
  674. cb = cb + #buffer.lines[i]+1
  675. end
  676. setStatusBar(string.format("line %d/%d (%d%%), col %d/%d (%d%%), char %d/%d (%d%%)",la,lb,la/lb*100+0.5, oa,ob,oa/ob*100+0.5, ca,cb,ca/cb*100+0.5))
  677. end
  678.  
  679. -- TODO: Line and column arguments
  680. -- Load files
  681. if #args > 0 then
  682. for i = 1,#args do
  683. createBuffer(shell.resolve(args[i]))
  684. end
  685. if #buffers > 1 then
  686. setStatusBar("Read " .. #buffers[1].lines .. " line" .. (#buffers[1].lines ~= 1 and "s" or ""))
  687. end
  688. else
  689. local buffer = createNewBuffer()
  690. buffer.lines[1] = ""
  691. buffers[#buffers+1] = buffer
  692. end
  693.  
  694. -- Show the first file
  695. switchBuffers(1)
  696.  
  697. while running do
  698. local e = { event_pull() }
  699. if e[1] == "key_down" then
  700. local char, code = e[3], e[4]
  701. local ctrl = kbd.isControlDown()
  702. local alt = kbd.isAltDown()
  703. local scp,sc = keys[code]:sub(1,1), keys[code]:sub(2)
  704. local schar
  705. if not kbd.isControl(char) and char ~= 32 then
  706. schar = unicode.upper(unicode.char(char))
  707. else
  708. schar = unicode.upper(keys[code])
  709. end
  710. if ctrl then
  711. -- TODO: This doesn't cover everything
  712. if unicode.len(keys[code]) == 1 then
  713. schar = unicode.upper(keys[code])
  714. end
  715. end
  716. if ctrl then
  717. schar = "^" .. schar
  718. elseif alt then
  719. schar = "M-" .. schar
  720. end
  721. if (scp == "l" or scp == "r") and (sc == "control" or sc == "menu" or sc == "shift") then
  722. elseif bind[mode][schar] ~= nil then
  723. local cmd = bind[mode][schar]
  724. if binding[cmd] ~= nil then
  725. binding[cmd]()
  726. else
  727. setStatusBar("Binding \"" .. cmd .. "\" Unimplemented")
  728. end
  729. elseif ctrl or alt then
  730. setStatusBar("Unknown Command")
  731. elseif not kbd.isControl(char) or char == 9 then
  732. timeout = timeout + 1
  733. if timeout >= (options.quickblank and 1 or 25) then
  734. gpu.fill(1,statusbarY,gpuW,1," ")
  735. end
  736. setModified(buffer)
  737. local line = buffer.lines[buffer.y]
  738. buffer.lines[buffer.y] = unicode.sub(line,1,buffer.x-1) .. unicode.char(char) .. unicode.sub(line,buffer.x)
  739. buffer.x = buffer.x + 1
  740. updateTX()
  741. updateActiveLine()
  742. end
  743. end
  744. end
  745.  
  746. term.clear()
  747. term.setCursorBlink(false)
  748.  
  749. -- Unfix keys.none
  750. keys["none"] = nil
  751. keys[0] = nil
Add Comment
Please, Sign In to add comment