Browse Source

Initial commit

master
Levi Olson 5 years ago
commit
ec02dc3c4c
5 changed files with 377 additions and 0 deletions
  1. +8
    -0
      Makefile
  2. BIN
      editor
  3. +16
    -0
      file.txt
  4. +334
    -0
      main.go
  5. +19
    -0
      utils/utils.go

+ 8
- 0
Makefile View File

@ -0,0 +1,8 @@
SHELL := /bin/bash
init:
@echo -n "Building Imports..."; \
cd ~/go/src/editor/utils/; go build .; cd ..; \
echo -e "\nDone"; \
echo "Running Editor"; \
go run main.go file.txt;

BIN
editor View File


+ 16
- 0
file.txt View File

@ -0,0 +1,16 @@
This is the very first line and its awesome
This is line two.
add stff
This is farther down the page.
End of file

+ 334
- 0
main.go View File

@ -0,0 +1,334 @@
package main
import (
"bufio"
"bytes"
"log"
"os"
"editor/utils"
"fmt"
"golang.org/x/crypto/ssh/terminal"
"regexp"
"strings"
)
// Editor is the initializing class
type Editor struct {
}
// Buffer is the temporary view presented to the user
type Buffer struct {
Lines []string
}
// Cursor is the object containing cursor point position
type Cursor struct {
Row int
Col int
}
func clamp(max, cur int) int {
if cur <= max {
return cur
}
return max
}
var filePath string
var editor Editor
var buffer Buffer
var cursor Cursor
var multiplier int
func main() {
if len(os.Args) == 1 {
log.Println("a file is required")
return
}
filePath = os.Args[1]
editor.initialize()
editor.run()
}
func (e *Editor) initialize() {
// Load a file
file, err := os.Open(filePath)
if err != nil {
log.Fatal(err)
}
defer file.Close()
// RAW terminal
oldState, err := terminal.MakeRaw(0)
if err != nil {
log.Fatal(err)
}
defer terminal.Restore(0, oldState)
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
multiplier = 1
buffer = buffer.new(lines)
cursor = cursor.new()
}
func (e *Editor) run() {
for {
e.render()
e.handleInput()
}
}
func (e *Editor) render() {
clearScreen()
buffer.render()
moveCursor(cursor.Row, cursor.Col) // restore cursor
}
func (e *Editor) handleInput() {
c := utils.Getch()
// log.Printf("%#v\t%s\n", c, string(c))
switch {
case bytes.Equal(c, []byte{0x3}), bytes.Equal(c, []byte{0x11}), bytes.Equal(c, []byte{0x18}): // C-c, C-q, C-x
// quit
clearScreen()
moveCursor(1, 1)
os.Exit(0)
return
case bytes.Equal(c, []byte{0x13}): // C-s
// save
buffer.save()
case bytes.Equal(c, []byte{0x5}): // C-e
// end of line
cursorEOL()
case bytes.Equal(c, []byte{0x1}): // C-a
// beginning of line
cursorBOL()
case bytes.Equal(c, []byte{0x15}): // C-u
if multiplier == 1 {
multiplier = 4
} else {
multiplier = multiplier + multiplier
}
case bytes.Equal(c, []byte{0x1b, 0x5b, 0x41}), bytes.Equal(c, []byte{0x10}): // UP, C-p
cursorUp(multiplier)
multiplier = 1
case bytes.Equal(c, []byte{0x1b, 0x5b, 0x42}), bytes.Equal(c, []byte{0xe}): // DOWN, C-n
cursorDown(multiplier)
multiplier = 1
case bytes.Equal(c, []byte{0x1b, 0x5b, 0x43}), bytes.Equal(c, []byte{0x6}): // RIGHT, C-f
cursorForward(multiplier)
multiplier = 1
case bytes.Equal(c, []byte{0x1b, 0x5b, 0x44}), bytes.Equal(c, []byte{0x2}): // LEFT, C-b
cursorBackward(multiplier)
multiplier = 1
case bytes.Equal(c, []byte{0x1b, 0x62}): // M-b
cursorBackwardWord()
multiplier = 1
case bytes.Equal(c, []byte{0x1b, 0x66}): // M-f
cursorForwardWord()
multiplier = 1
case bytes.Equal(c, []byte{0x7f}): // backspace
buffer.deleteChar()
multiplier = 1
case bytes.Equal(c, []byte{0xb}): // C-k
buffer.deleteForward()
multiplier = 1
default:
buffer.insertChar(string(c))
multiplier = 1
}
}
func (b *Buffer) new(lines []string) Buffer {
b.Lines = lines
return *b
}
func (b *Buffer) fetch(line int) string {
// fix the index 1 issue
if line > len(b.Lines) {
return ""
}
return b.Lines[line-1]
}
func (b *Buffer) render() {
moveCursor(1, 1) // reset cursor for printing buffer
for _, str := range buffer.Lines {
// fmt.Printf("%d| %s\r\n", num, str) // with line nums
fmt.Printf("%s\r\n", str)
}
}
func (b *Buffer) save() {
// save file
file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
log.Fatal(err)
}
defer file.Close()
out := strings.Join(b.Lines, "\n")
_, err = file.WriteString(out)
if err != nil {
log.Fatal(err)
}
}
func (b *Buffer) insertChar(inp string) {
if len(b.Lines) < cursor.Row {
b.Lines = append(b.Lines, inp)
} else {
b.Lines[cursor.Row-1] = strings.Join(
[]string{
b.Lines[cursor.Row-1][:cursor.Col-1],
inp,
b.Lines[cursor.Row-1][cursor.Col-1:],
}, "")
}
cursor.Col++
moveCursor(cursor.Row, cursor.Col)
}
func (b *Buffer) deleteChar() {
if cursor.Col == 1 {
return
}
b.Lines[cursor.Row-1] = strings.Join(
[]string{
b.Lines[cursor.Row-1][:cursor.Col-2],
b.Lines[cursor.Row-1][cursor.Col-1:],
}, "")
cursor.Col--
moveCursor(cursor.Row, cursor.Col)
}
func (b *Buffer) deleteForward() {
b.Lines[cursor.Row-1] = b.Lines[cursor.Row-1][:cursor.Col-1]
}
func (c *Cursor) new() Cursor {
return Cursor{
1,
1,
}
}
func (c *Cursor) clampCol() {
line := buffer.fetch(c.Row)
if c.Col > len(line) {
c.Col = len(line) + 1
} else if c.Col < 1 {
c.Col = 1
}
}
func (c *Cursor) clampRow() {
rows := len(buffer.Lines)
if c.Row > rows+1 {
c.Row = rows + 1
} else if c.Row < 1 {
c.Row = 1
}
}
//
//
//
func clearScreen() {
fmt.Print("")
}
func moveCursor(row, col int) {
fmt.Printf("[%d;%dH", row, col)
}
func cursorEOL() {
line := buffer.fetch(cursor.Row)
cursor.Col = len(line) + 1
moveCursor(cursor.Row, cursor.Col)
}
func cursorBOL() {
cursor.Col = 1
moveCursor(cursor.Row, cursor.Col)
}
func cursorUp(multiplier int) {
for k := 0; k < multiplier; k++ {
cursor.Row = cursor.Row - 1
}
cursor.clampRow()
cursor.clampCol()
moveCursor(cursor.Row, cursor.Col)
}
func cursorDown(multiplier int) {
for k := 0; k < multiplier; k++ {
cursor.Row = cursor.Row + 1
}
cursor.clampRow()
cursor.clampCol()
moveCursor(cursor.Row, cursor.Col)
}
func cursorForward(multiplier int) {
for k := 0; k < multiplier; k++ {
cursor.Col = cursor.Col + 1
}
cursor.clampCol()
cursor.clampRow()
moveCursor(cursor.Row, cursor.Col)
}
func cursorForwardWord() {
// split line into array
line := buffer.fetch(cursor.Row)
line = line[cursor.Col-1:]
content := []byte(line)
pattern := regexp.MustCompile(`\W\w`)
loc := pattern.FindIndex(content)
if loc != nil {
cursor.Col = cursor.Col + loc[0] + 1
cursor.clampCol()
cursor.clampRow()
moveCursor(cursor.Row, cursor.Col)
}
}
func cursorBackward(multiplier int) {
for k := 0; k < multiplier; k++ {
cursor.Col = cursor.Col - 1
}
cursor.clampCol()
cursor.clampRow()
moveCursor(cursor.Row, cursor.Col)
}
func cursorBackwardWord() {
line := buffer.fetch(cursor.Row)
line = strings.TrimRight(line[:cursor.Col-1], " ")
content := []byte(line)
pattern := regexp.MustCompile(`\w+.?$`)
loc := pattern.FindIndex(content)
if loc != nil {
cursor.Col = loc[0] + 1
cursor.clampCol()
cursor.clampRow()
moveCursor(cursor.Row, cursor.Col)
}
}

+ 19
- 0
utils/utils.go View File

@ -0,0 +1,19 @@
package utils
import (
"github.com/pkg/term"
)
// Getch simply listens for input from stdin
func Getch() []byte {
t, _ := term.Open("/dev/tty")
term.RawMode(t)
bytes := make([]byte, 3)
numRead, err := t.Read(bytes)
t.Restore()
t.Close()
if err != nil {
return nil
}
return bytes[0:numRead]
}

Loading…
Cancel
Save