LED is a lightweight text editor written using Go.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

540 lines
11 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. package main
  2. import (
  3. "bufio"
  4. "bytes"
  5. "log"
  6. "os"
  7. "fmt"
  8. "led/utils"
  9. "os/exec"
  10. "regexp"
  11. "strconv"
  12. "strings"
  13. )
  14. // Editor is the initializing class
  15. type Editor struct {
  16. }
  17. // Buffer is the temporary view presented to the user
  18. type Buffer struct {
  19. Lines []string
  20. }
  21. // Screen is the rendering of the entire viewport
  22. type Screen struct {
  23. }
  24. // Modeline is the message line at the bottom of the viewport
  25. type Modeline struct {
  26. message string
  27. }
  28. // Statusline is the file path line at the bottom of the viewport
  29. type Statusline struct {
  30. message string
  31. fileSize string
  32. filePath string
  33. location string
  34. fileFormat string
  35. format string
  36. unformat string
  37. }
  38. // Cursor is the object containing cursor point position
  39. type Cursor struct {
  40. Row int
  41. Col int
  42. }
  43. type messageColor struct {
  44. foreground int
  45. background int
  46. }
  47. var arrow = map[string][]byte{
  48. "up": []byte{0x1b, 0x5b, 0x41},
  49. "down": []byte{0x1b, 0x5b, 0x42},
  50. "right": []byte{0x1b, 0x5b, 0x43},
  51. "left": []byte{0x1b, 0x5b, 0x44},
  52. }
  53. var ctrl = map[string][]byte{
  54. "a": []byte{0x1}, // C-a
  55. "b": []byte{0x2},
  56. "c": []byte{0x3},
  57. "d": []byte{0x4},
  58. "e": []byte{0x5},
  59. "f": []byte{0x6},
  60. "g": []byte{0x7},
  61. "h": []byte{0x8},
  62. "i": []byte{0x9},
  63. "j": []byte{0xa},
  64. "k": []byte{0xb},
  65. "l": []byte{0xc},
  66. "m": []byte{0xd},
  67. "n": []byte{0xe},
  68. "o": []byte{0xf},
  69. "p": []byte{0x10},
  70. "q": []byte{0x11},
  71. "r": []byte{0x12},
  72. "s": []byte{0x13},
  73. "t": []byte{0x14},
  74. "u": []byte{0x15},
  75. "v": []byte{0x16},
  76. "w": []byte{0x17},
  77. "x": []byte{0x18},
  78. "y": []byte{0x19},
  79. "z": []byte{0x1a},
  80. }
  81. var meta = map[string][]byte{
  82. "x": []byte{0x1b, 0x78},
  83. }
  84. var filePath string
  85. var editor Editor
  86. var buffer Buffer
  87. var screen Screen
  88. var modeline Modeline
  89. var statusline Statusline
  90. var cursor Cursor
  91. var multiplier int
  92. var prefix []byte
  93. var fileBytes int
  94. func main() {
  95. if len(os.Args) == 1 {
  96. fmt.Println("You MUST supply a file to edit")
  97. return
  98. }
  99. filePath = os.Args[1]
  100. editor.initialize()
  101. editor.run()
  102. }
  103. func (e *Editor) initialize() {
  104. // Load a file
  105. file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0755)
  106. if err != nil {
  107. log.Fatal(err)
  108. }
  109. defer file.Close()
  110. var lines []string
  111. scanner := bufio.NewScanner(file)
  112. for scanner.Scan() {
  113. lines = append(lines, scanner.Text())
  114. }
  115. if err := scanner.Err(); err != nil {
  116. log.Fatal(err)
  117. }
  118. multiplier = 1
  119. statusline.setFormat()
  120. buffer = buffer.new(lines)
  121. cursor = cursor.new()
  122. }
  123. func (e *Editor) run() {
  124. for {
  125. e.render()
  126. e.handleInput()
  127. }
  128. }
  129. func (e *Editor) render() {
  130. clearScreen()
  131. screen.render()
  132. moveCursor(cursor.Row, cursor.Col) // restore cursor
  133. }
  134. func (e *Editor) handleInput() {
  135. c := utils.Getch()
  136. // log.Printf("%#v\t%s\n", c, string(c))
  137. modeline.setMessage("")
  138. switch {
  139. case bytes.Equal(c, ctrl["x"]):
  140. reset()
  141. prefix = ctrl["x"]
  142. modeline.setMessage("C-x-")
  143. case bytes.Equal(c, ctrl["c"]):
  144. if bytes.Equal(prefix, ctrl["x"]) {
  145. clearScreen()
  146. moveCursor(1, 1)
  147. os.Exit(0)
  148. return
  149. }
  150. modeline.setMessage("Invalid command!")
  151. reset()
  152. case bytes.Equal(c, ctrl["s"]):
  153. if bytes.Equal(prefix, ctrl["x"]) {
  154. buffer.save()
  155. statusline.setFormat()
  156. modeline.setMessage(fmt.Sprintf("Saved \"%s\"", filePath))
  157. return
  158. }
  159. modeline.setMessage("Invalid command, did you mean to save -> C-x C-s")
  160. reset()
  161. case bytes.Equal(c, ctrl["g"]):
  162. modeline.setMessage("Quit")
  163. reset()
  164. case bytes.Equal(c, ctrl["e"]):
  165. cursorEOL()
  166. reset()
  167. case bytes.Equal(c, ctrl["a"]):
  168. cursorBOL()
  169. reset()
  170. case bytes.Equal(c, ctrl["u"]):
  171. setMultiplier()
  172. case bytes.Equal(c, arrow["up"]), bytes.Equal(c, ctrl["p"]): // UP, C-p
  173. cursorUp(multiplier)
  174. reset()
  175. case bytes.Equal(c, []byte{0x1b, 0x5b, 0x42}), bytes.Equal(c, []byte{0xe}): // DOWN, C-n
  176. cursorDown(multiplier)
  177. reset()
  178. case bytes.Equal(c, []byte{0x1b, 0x5b, 0x43}), bytes.Equal(c, []byte{0x6}): // RIGHT, C-f
  179. cursorForward(multiplier)
  180. reset()
  181. case bytes.Equal(c, []byte{0x1b, 0x5b, 0x44}), bytes.Equal(c, []byte{0x2}): // LEFT, C-b
  182. cursorBackward(multiplier)
  183. reset()
  184. case bytes.Equal(c, []byte{0x1b, 0x62}): // M-b
  185. cursorBackwardWord()
  186. reset()
  187. case bytes.Equal(c, []byte{0x1b, 0x66}): // M-f
  188. cursorForwardWord()
  189. reset()
  190. case bytes.Equal(c, []byte{0x7f}): // backspace
  191. buffer.deleteChar()
  192. reset()
  193. case bytes.Equal(c, []byte{0xb}): // C-k
  194. buffer.deleteForward()
  195. statusline.setFilePathColor(messageColor{1, 8})
  196. reset()
  197. default:
  198. buffer.insertChar(string(c))
  199. statusline.setFilePathColor(messageColor{1, 8})
  200. reset()
  201. }
  202. }
  203. func reset() {
  204. prefix = nil
  205. multiplier = 1
  206. }
  207. func setMultiplier() {
  208. if multiplier == 1 {
  209. multiplier = 4
  210. modeline.setMessage("C-u-")
  211. } else {
  212. multiplier = multiplier + multiplier
  213. msg := []string{}
  214. count := 0
  215. start := multiplier
  216. for start > 2 {
  217. start = start / 2
  218. count++
  219. }
  220. for i := 0; i < count; i++ {
  221. msg = append(msg, "C-u-")
  222. }
  223. modeline.setMessage(strings.Join(msg, ""))
  224. }
  225. }
  226. /**
  227. * BUFFER
  228. */
  229. func (b *Buffer) new(lines []string) Buffer {
  230. b.Lines = lines
  231. return *b
  232. }
  233. func (b *Buffer) fetch(line int) string {
  234. // fix the index 1 issue
  235. if line > len(b.Lines) {
  236. return ""
  237. }
  238. return b.Lines[line-1]
  239. }
  240. func (b *Buffer) render() {
  241. moveCursor(1, 1) // reset cursor for printing buffer
  242. for _, str := range buffer.Lines {
  243. // fmt.Printf("%d| %s\r\n", num, str) // with line nums
  244. fmt.Printf("%s\r\n", str)
  245. }
  246. }
  247. func (b *Buffer) save() {
  248. // save file
  249. file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0755)
  250. if err != nil {
  251. log.Fatal(err)
  252. }
  253. defer file.Close()
  254. out := strings.Join(b.Lines, "\n")
  255. fileBytes, err = file.WriteString(out)
  256. if err != nil {
  257. log.Fatal(err)
  258. }
  259. }
  260. func (b *Buffer) size() string {
  261. total := 0
  262. for _, v := range b.Lines {
  263. total += len(v)
  264. }
  265. return humanReadable(total)
  266. }
  267. func (b *Buffer) insertChar(inp string) {
  268. if len(b.Lines) < cursor.Row {
  269. b.Lines = append(b.Lines, inp)
  270. } else {
  271. b.Lines[cursor.Row-1] = strings.Join(
  272. []string{
  273. b.Lines[cursor.Row-1][:cursor.Col-1],
  274. inp,
  275. b.Lines[cursor.Row-1][cursor.Col-1:],
  276. }, "")
  277. }
  278. cursor.Col++
  279. moveCursor(cursor.Row, cursor.Col)
  280. }
  281. func (b *Buffer) deleteChar() {
  282. if cursor.Col == 1 {
  283. return
  284. }
  285. b.Lines[cursor.Row-1] = strings.Join(
  286. []string{
  287. b.Lines[cursor.Row-1][:cursor.Col-2],
  288. b.Lines[cursor.Row-1][cursor.Col-1:],
  289. }, "")
  290. cursor.Col--
  291. moveCursor(cursor.Row, cursor.Col)
  292. }
  293. func (b *Buffer) deleteForward() {
  294. b.Lines[cursor.Row-1] = b.Lines[cursor.Row-1][:cursor.Col-1]
  295. }
  296. /**
  297. * SCREEN
  298. */
  299. func getTermSize() []int {
  300. cmd := exec.Command("stty", "size")
  301. cmd.Stdin = os.Stdin
  302. out, err := cmd.Output()
  303. if err != nil {
  304. log.Fatal(err)
  305. }
  306. size := string(out)
  307. size = strings.TrimSpace(size)
  308. slice := strings.Split(size, " ")
  309. asRowInt, _ := strconv.ParseInt(slice[0], 0, 32)
  310. rows := int(asRowInt)
  311. asColInt, _ := strconv.ParseInt(slice[1], 0, 32)
  312. cols := int(asColInt)
  313. // fmt.Printf("%#v, %#v", size, []int{rows, cols})
  314. return []int{rows, cols}
  315. }
  316. func humanReadable(inp int) string {
  317. switch {
  318. case inp > 1000000:
  319. return fmt.Sprintf("%dM", inp/1000000)
  320. case inp > 1000:
  321. return fmt.Sprintf("%dk", inp/1000)
  322. default:
  323. return fmt.Sprintf("%d", inp)
  324. }
  325. }
  326. func (s *Screen) render() {
  327. termSize := getTermSize()
  328. buffer.render()
  329. statusline.setSize(buffer.size())
  330. statusline.setFilePath(filePath)
  331. statusline.setLocation(cursor.Row, cursor.Col)
  332. statusline.render(termSize)
  333. modeline.render(termSize)
  334. }
  335. /**
  336. * MODELINE
  337. */
  338. func (m *Modeline) setMessage(msg string) {
  339. m.message = msg
  340. }
  341. func (m *Modeline) render(size []int) {
  342. if m.message != "" {
  343. moveCursor(size[0], 0)
  344. fmt.Printf("%s", m.message)
  345. }
  346. }
  347. /**
  348. * STATUSLINE
  349. */
  350. func (s *Statusline) setFormat() {
  351. s.fileFormat = fmt.Sprintf("")
  352. s.format = fmt.Sprintf("")
  353. s.unformat = fmt.Sprintf("")
  354. }
  355. func (s *Statusline) setSize(fileSize string) {
  356. s.fileSize = fileSize
  357. }
  358. func (s *Statusline) setFilePath(filePath string) {
  359. array := []string{s.fileFormat, filePath, s.format}
  360. s.filePath = strings.Join(array, " ")
  361. }
  362. func (s *Statusline) setFilePathColor(color messageColor) {
  363. s.fileFormat = fmt.Sprintf("[48;5;%dm[38;5;%dm", color.background, color.foreground)
  364. // statusline.setFilePath(s.filePath)
  365. }
  366. func (s *Statusline) setLocation(row, col int) {
  367. s.location = fmt.Sprintf("%d:%d", row, col)
  368. }
  369. func (s *Statusline) render(size []int) {
  370. moveCursor(size[0]-1, 0)
  371. statuslineParts := []string{s.fileSize, s.fileFormat, s.filePath, s.format, s.location}
  372. line := strings.Join(statuslineParts, " ")
  373. addLength := len(s.fileFormat) + len(s.format) + len(s.unformat)
  374. fmt.Printf("%s%-*s%s", s.format, size[1]+addLength-1, line, s.unformat)
  375. }
  376. /**
  377. * CURSOR
  378. */
  379. func (c *Cursor) new() Cursor {
  380. return Cursor{
  381. 1,
  382. 1,
  383. }
  384. }
  385. // func clamp(max, cur int) int {
  386. // if cur <= max {
  387. // return cur
  388. // }
  389. // return max
  390. // }
  391. func (c *Cursor) clampCol() {
  392. line := buffer.fetch(c.Row)
  393. if c.Col > len(line) {
  394. c.Col = len(line) + 1
  395. } else if c.Col < 1 {
  396. c.Col = 1
  397. }
  398. }
  399. func (c *Cursor) clampRow() {
  400. rows := len(buffer.Lines)
  401. if c.Row > rows+1 {
  402. c.Row = rows + 1
  403. } else if c.Row < 1 {
  404. c.Row = 1
  405. }
  406. }
  407. //
  408. //
  409. //
  410. func clearScreen() {
  411. fmt.Print("")
  412. }
  413. func moveCursor(row, col int) {
  414. fmt.Printf("[%d;%dH", row, col)
  415. }
  416. func cursorEOL() {
  417. line := buffer.fetch(cursor.Row)
  418. cursor.Col = len(line) + 1
  419. moveCursor(cursor.Row, cursor.Col)
  420. }
  421. func cursorBOL() {
  422. cursor.Col = 1
  423. moveCursor(cursor.Row, cursor.Col)
  424. }
  425. func cursorUp(multiplier int) {
  426. for k := 0; k < multiplier; k++ {
  427. cursor.Row = cursor.Row - 1
  428. }
  429. cursor.clampRow()
  430. cursor.clampCol()
  431. moveCursor(cursor.Row, cursor.Col)
  432. }
  433. func cursorDown(multiplier int) {
  434. for k := 0; k < multiplier; k++ {
  435. cursor.Row = cursor.Row + 1
  436. }
  437. cursor.clampRow()
  438. cursor.clampCol()
  439. moveCursor(cursor.Row, cursor.Col)
  440. }
  441. func cursorForward(multiplier int) {
  442. for k := 0; k < multiplier; k++ {
  443. cursor.Col = cursor.Col + 1
  444. }
  445. cursor.clampCol()
  446. cursor.clampRow()
  447. moveCursor(cursor.Row, cursor.Col)
  448. }
  449. func cursorForwardWord() {
  450. // split line into array
  451. line := buffer.fetch(cursor.Row)
  452. line = line[cursor.Col-1:]
  453. content := []byte(line)
  454. pattern := regexp.MustCompile(`\W\w`)
  455. loc := pattern.FindIndex(content)
  456. if loc != nil {
  457. cursor.Col = cursor.Col + loc[0] + 1
  458. cursor.clampCol()
  459. cursor.clampRow()
  460. moveCursor(cursor.Row, cursor.Col)
  461. }
  462. }
  463. func cursorBackward(multiplier int) {
  464. for k := 0; k < multiplier; k++ {
  465. cursor.Col = cursor.Col - 1
  466. }
  467. cursor.clampCol()
  468. cursor.clampRow()
  469. moveCursor(cursor.Row, cursor.Col)
  470. }
  471. func cursorBackwardWord() {
  472. line := buffer.fetch(cursor.Row)
  473. line = strings.TrimRight(line[:cursor.Col-1], " ")
  474. content := []byte(line)
  475. pattern := regexp.MustCompile(`\w+.?$`)
  476. loc := pattern.FindIndex(content)
  477. if loc != nil {
  478. cursor.Col = loc[0] + 1
  479. cursor.clampCol()
  480. cursor.clampRow()
  481. moveCursor(cursor.Row, cursor.Col)
  482. }
  483. }