├── .editorconfig ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── README.md ├── build └── .gitkeep ├── src ├── editor.nim ├── te.nim ├── term.nim ├── types.nim └── winsize.c └── te.nimble /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [*.nim] 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | build/* 3 | nimsuggest.log 4 | 5 | # Editor 6 | .vscode/* 7 | !.vscode/settings.json 8 | !.vscode/tasks.json 9 | 10 | !.gitkeep 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "nimble", 6 | "isShellCommand": true, 7 | "args": ["build"], 8 | "showOutput": "always" 9 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # te - A tiny editor in Nim 2 | 3 | This editor is (will be?) heavily inspired by antirez' [kilo](https://github.com/antirez/kilo) editor, and the [vis](https://github.com/martanne/vis) modal editor, albeit with my own twist. 4 | 5 | ## Why? 6 | 7 | Very good question. I wanted to take a crack at building my own tooling, just for the sake of it, but there *is* a method to the madness: I've always wanted an editor that is not just language aware, but _framework_ aware. That's the long-term goal here, to have an editor that picks up on the particular use-cases that each framework exposes, to allow for extremely powerful autocorrect. 8 | 9 | ## Building 10 | 11 | 1. `nimble install` 12 | 1. `nimble build` 13 | 1. `./build/te` 14 | 15 | ## License 16 | 17 | Released under the MIT license, Josh Girvin © 2016 18 | -------------------------------------------------------------------------------- /build/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/girvo/te/b3ca7aac2a82956e776d860e7128d62acdf00038/build/.gitkeep -------------------------------------------------------------------------------- /src/editor.nim: -------------------------------------------------------------------------------- 1 | ## Main editor object 2 | 3 | import os, posix, strutils, sequtils 4 | import types 5 | 6 | ## The actual implementation 7 | var editorInst*: EditorConfig = newEditorConfig() 8 | 9 | proc loadFile*(fileInfo: var types.FileInfo) = 10 | fileInfo.data = @[] 11 | try: 12 | var file = readFile(fileInfo.filename) 13 | var lineNo = 0 14 | for line in splitLines(file): 15 | var rowSeq: seq[char] = @[] 16 | for i in 0..len(line): 17 | rowSeq.insert(line[i], i) 18 | fileInfo.data.insert(rowSeq, lineNo) 19 | lineNo = lineNo + 1 20 | except IOError: 21 | echo("IOError...") 22 | discard 23 | except: 24 | echo("Another error...") 25 | raise 26 | -------------------------------------------------------------------------------- /src/te.nim: -------------------------------------------------------------------------------- 1 | ## A tiny editor written in Nim 2 | 3 | import os, posix, posix/termios, strutils 4 | import types 5 | import editor 6 | import term 7 | 8 | var 9 | buff*: array[6, char] 10 | cont: bool = true 11 | 12 | proc handleSigQuit(dummy: cint) {.noconv.} = 13 | cont = false 14 | 15 | proc main() = 16 | # Setup with the initial size 17 | var size: TermSize = term.calcSize() 18 | editorInst.size = size 19 | 20 | # Read the file in 21 | var file = newFileInfo("README.md", ".") 22 | editorInst.file = file 23 | editorInst.file.loadFile() 24 | # term.clear() 25 | term.setup() 26 | 27 | signal(SIGINT, handleSigQuit) 28 | signal(SIGWINCH, termResized) 29 | term.render() 30 | 31 | while cont: 32 | discard read(STDIN_FILENO, addr(buff), 1) 33 | if buff[0] == 'q': 34 | cont = false 35 | break 36 | 37 | when isMainModule: 38 | main() 39 | term.cleanup() 40 | -------------------------------------------------------------------------------- /src/term.nim: -------------------------------------------------------------------------------- 1 | ## ANSI escape-code based terminal handling 2 | ## 3 | ## @see {http://stackoverflow.com/questions/8476332/writing-a-real-interactive-terminal-program-like-vim-htop-in-c-c-witho} 4 | 5 | import os, posix, strutils, posix/termios 6 | import types 7 | import editor 8 | 9 | ## C/POSIX bridges for TTY handling 10 | {.compile: "winsize.c".} 11 | proc getCols(): cushort {.importc.} 12 | proc getRows(): cushort {.importc.} 13 | 14 | var 15 | original*: Termios 16 | raw*: Termios 17 | 18 | ## Load SIGWINCH from signals.h 19 | var SIGWINCH* {.importc, header: "".}: cint 20 | 21 | proc esc*(code: string): string = 22 | ## Generates an escape-code sequence string 23 | return "\x1b[" & code 24 | 25 | proc writeEsc*(code: string) = 26 | ## Writes the escape code out via POSIX layer 27 | var str = esc(code) 28 | discard posix.write(STDOUT_FILENO, cstring(str), len(str)) 29 | 30 | proc moveCursor*(line: Natural, column: Natural) = 31 | write(stdout, esc($line & ';' & $column & 'H')) 32 | 33 | proc clear*() = 34 | ## Clears the terminal to be ready for writing 35 | moveCursor(0, 0) 36 | write(stdout, esc("2J") & esc("1;1H")) 37 | 38 | proc calcSize*(): TermSize = 39 | ## Calculates the size of the terminal using ioctl via C functions 40 | var cols = getCols() 41 | var rows = getRows() 42 | return newTermSize(rows, cols) 43 | 44 | proc setup*() = 45 | ## Sets up the terminal to use raw STDIN, and "fullscreen" mode 46 | discard tcGetAttr(STDIN_FILENO, addr(original)) 47 | cfMakeRaw(addr(raw)) 48 | discard tcSetAttr(STDIN_FILENO, TCSANOW, addr(raw)) 49 | ## Switch to alternate buffer... 50 | writeEsc("?47h") 51 | 52 | proc cleanup*() = 53 | ## Cleans up and re-sets the terminal on close 54 | writeEsc("?91") 55 | writeEsc("?471") 56 | discard tcSetAttr(STDIN_FILENO, TCSANOW, addr(original)) 57 | 58 | proc render*() = 59 | moveCursor(0, 0) 60 | # for y in 1..editorInst.size.rows: 61 | # for x in 1..editorInst.size.cols: 62 | # write(stdout, "a") 63 | # if y != editorInst.size.rows - 1: 64 | # write(stdout, "\r\n") 65 | # else: 66 | # moveCursor(0, 0) 67 | # for row in editorInst.file.data: 68 | # for ch in row: 69 | # write(stdout, ch) 70 | # write(stdout, "\r\n") 71 | 72 | proc termResized*(x: cint) {.noconv.} = 73 | # Triggered by SIGWINCH signal on resize 74 | editorInst.size = calcSize() 75 | term.render() 76 | -------------------------------------------------------------------------------- /src/types.nim: -------------------------------------------------------------------------------- 1 | ## Type definitions (to avoid circular references) 2 | 3 | type 4 | FileInfo* = ref object 5 | ## File information object 6 | filename*: string 7 | path*: string 8 | dirty*: bool 9 | data*: seq[seq[char]] 10 | 11 | type 12 | TermSize* = ref object 13 | ## TermSize definitions for our terminal cols and rows 14 | rows*, cols*: Natural 15 | 16 | type 17 | EditorConfig* = ref object 18 | ## Struct to hold useful data for our current editor/FileInfo 19 | cx*: Natural 20 | cy*: Natural 21 | size*: TermSize 22 | rowOffset*: Natural 23 | colOffset*: Natural 24 | numRows*: Natural 25 | filename*: string 26 | file*: FileInfo 27 | statusMessage*: array[1..80, char] 28 | 29 | ## Constructors 30 | 31 | proc newTermSize*(rows: Natural, cols: Natural): TermSize = 32 | ## Instantiate a TermSize object with the given sizes 33 | TermSize(rows: rows, cols: cols) 34 | 35 | proc newEditorConfig*(): EditorConfig = new(result) 36 | 37 | proc newFileInfo*(filename: string, path: string): FileInfo = 38 | FileInfo( 39 | filename: filename, 40 | path: path, 41 | dirty: false, 42 | data: @[@['\0']]) 43 | -------------------------------------------------------------------------------- /src/winsize.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Tiny bridge to ioctl to grab the TTY size for Nim 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | typedef unsigned short ushort; 9 | 10 | ushort getCols() 11 | { 12 | struct winsize sz; 13 | 14 | if (ioctl(0, TIOCGWINSZ, &sz) == -1) { 15 | return 0; 16 | } 17 | 18 | return sz.ws_col; 19 | } 20 | 21 | ushort getRows() 22 | { 23 | struct winsize sz; 24 | 25 | if (ioctl(0, TIOCGWINSZ, &sz) == -1) { 26 | return 0; 27 | } 28 | 29 | return sz.ws_row; 30 | } 31 | -------------------------------------------------------------------------------- /te.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.0" 4 | author = "Josh Girvin " 5 | description = "A tiny editor in Nim" 6 | license = "MIT" 7 | 8 | # Dependencies 9 | 10 | requires "nim >= 0.14.3" 11 | 12 | srcDir = "src" 13 | binDir = "build" 14 | bin = @["te"] --------------------------------------------------------------------------------