Browse Source

Slightly better

master
Levi Olson 5 years ago
parent
commit
420ac87bce
5 changed files with 283 additions and 69 deletions
  1. +1
    -0
      .gitignore
  2. +29
    -6
      Makefile
  3. BIN
      editor
  4. +0
    -16
      file.txt
  5. +253
    -47
      main.go

+ 1
- 0
.gitignore View File

@ -0,0 +1 @@
editor

+ 29
- 6
Makefile View File

@ -1,8 +1,31 @@
SHELL := /bin/bash SHELL := /bin/bash
FILE := tmp.txt
CAT := bat
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;
ifeq (, $(shell which bat))
$(error "No bat in $(PATH), consider installing ti")
CAT = cat
endif
build_and_run: clean build run
.PHONY : build_and_run build run clean test
build :
@echo "-> Building"
@cd ~/go/src/editor/utils/; go build .; cd ..
@go build .
@echo "-> Done"
run :
@echo "-> Running"
@./editor $(FILE)
clean :
@echo "-> Cleaning up"
@-rm editor
test :
@echo -e "-> Generating test file"
@echo -e "This is a line.\nThis is another line.\n\n\nThis is the end." > tmp.txt
@$(CAT) tmp.txt

BIN
editor View File


+ 0
- 16
file.txt View File

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

+ 253
- 47
main.go View File

@ -8,8 +8,9 @@ import (
"editor/utils" "editor/utils"
"fmt" "fmt"
"golang.org/x/crypto/ssh/terminal"
"os/exec"
"regexp" "regexp"
"strconv"
"strings" "strings"
) )
@ -22,28 +23,89 @@ type Buffer struct {
Lines []string Lines []string
} }
// Screen is the rendering of the entire viewport
type Screen struct {
}
// Modeline is the message line at the bottom of the viewport
type Modeline struct {
message string
}
// Statusline is the file path line at the bottom of the viewport
type Statusline struct {
message string
fileSize string
filePath string
location string
fileFormat string
format string
unformat string
}
// Cursor is the object containing cursor point position // Cursor is the object containing cursor point position
type Cursor struct { type Cursor struct {
Row int Row int
Col int Col int
} }
func clamp(max, cur int) int {
if cur <= max {
return cur
}
return max
type messageColor struct {
foreground int
background int
}
var arrow = map[string][]byte{
"up": []byte{0x1b, 0x5b, 0x41},
"down": []byte{0x1b, 0x5b, 0x42},
"right": []byte{0x1b, 0x5b, 0x43},
"left": []byte{0x1b, 0x5b, 0x44},
}
var ctrl = map[string][]byte{
"a": []byte{0x1}, // C-a
"b": []byte{0x2},
"c": []byte{0x3},
"d": []byte{0x4},
"e": []byte{0x5},
"f": []byte{0x6},
"g": []byte{0x7},
"h": []byte{0x8},
"i": []byte{0x9},
"j": []byte{0xa},
"k": []byte{0xb},
"l": []byte{0xc},
"m": []byte{0xd},
"n": []byte{0xe},
"o": []byte{0xf},
"p": []byte{0x10},
"q": []byte{0x11},
"r": []byte{0x12},
"s": []byte{0x13},
"t": []byte{0x14},
"u": []byte{0x15},
"v": []byte{0x16},
"w": []byte{0x17},
"x": []byte{0x18},
"y": []byte{0x19},
"z": []byte{0x1a},
}
var meta = map[string][]byte{
"x": []byte{0x1b, 0x78},
} }
var filePath string var filePath string
var editor Editor var editor Editor
var buffer Buffer var buffer Buffer
var screen Screen
var modeline Modeline
var statusline Statusline
var cursor Cursor var cursor Cursor
var multiplier int var multiplier int
var prefix []byte
var fileBytes int
func main() { func main() {
if len(os.Args) == 1 { if len(os.Args) == 1 {
log.Println("a file is required")
fmt.Println("You MUST supply a file to edit")
return return
} }
filePath = os.Args[1] filePath = os.Args[1]
@ -53,19 +115,12 @@ func main() {
func (e *Editor) initialize() { func (e *Editor) initialize() {
// Load a file // Load a file
file, err := os.Open(filePath)
file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0755)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer file.Close() defer file.Close()
// RAW terminal
oldState, err := terminal.MakeRaw(0)
if err != nil {
log.Fatal(err)
}
defer terminal.Restore(0, oldState)
var lines []string var lines []string
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
@ -76,6 +131,7 @@ func (e *Editor) initialize() {
} }
multiplier = 1 multiplier = 1
statusline.setFormat()
buffer = buffer.new(lines) buffer = buffer.new(lines)
cursor = cursor.new() cursor = cursor.new()
} }
@ -89,66 +145,109 @@ func (e *Editor) run() {
func (e *Editor) render() { func (e *Editor) render() {
clearScreen() clearScreen()
buffer.render()
screen.render()
moveCursor(cursor.Row, cursor.Col) // restore cursor moveCursor(cursor.Row, cursor.Col) // restore cursor
} }
func (e *Editor) handleInput() { func (e *Editor) handleInput() {
c := utils.Getch() c := utils.Getch()
// log.Printf("%#v\t%s\n", c, string(c)) // log.Printf("%#v\t%s\n", c, string(c))
modeline.setMessage("")
switch { 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
case bytes.Equal(c, ctrl["x"]):
reset()
prefix = ctrl["x"]
modeline.setMessage("C-x-")
case bytes.Equal(c, ctrl["c"]):
if bytes.Equal(prefix, ctrl["x"]) {
clearScreen()
moveCursor(1, 1)
os.Exit(0)
return
}
modeline.setMessage("Invalid command!")
reset()
case bytes.Equal(c, ctrl["s"]):
if bytes.Equal(prefix, ctrl["x"]) {
buffer.save()
statusline.setFormat()
modeline.setMessage(fmt.Sprintf("Saved \"%s\"", filePath))
return
}
modeline.setMessage("Invalid command, did you mean to save -> C-x C-s")
reset()
case bytes.Equal(c, ctrl["g"]):
modeline.setMessage("Quit")
reset()
case bytes.Equal(c, ctrl["e"]):
cursorEOL() cursorEOL()
case bytes.Equal(c, []byte{0x1}): // C-a
// beginning of line
reset()
case bytes.Equal(c, ctrl["a"]):
cursorBOL() 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
reset()
case bytes.Equal(c, ctrl["u"]):
setMultiplier()
case bytes.Equal(c, arrow["up"]), bytes.Equal(c, ctrl["p"]): // UP, C-p
cursorUp(multiplier) cursorUp(multiplier)
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x1b, 0x5b, 0x42}), bytes.Equal(c, []byte{0xe}): // DOWN, C-n case bytes.Equal(c, []byte{0x1b, 0x5b, 0x42}), bytes.Equal(c, []byte{0xe}): // DOWN, C-n
cursorDown(multiplier) cursorDown(multiplier)
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x1b, 0x5b, 0x43}), bytes.Equal(c, []byte{0x6}): // RIGHT, C-f case bytes.Equal(c, []byte{0x1b, 0x5b, 0x43}), bytes.Equal(c, []byte{0x6}): // RIGHT, C-f
cursorForward(multiplier) cursorForward(multiplier)
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x1b, 0x5b, 0x44}), bytes.Equal(c, []byte{0x2}): // LEFT, C-b case bytes.Equal(c, []byte{0x1b, 0x5b, 0x44}), bytes.Equal(c, []byte{0x2}): // LEFT, C-b
cursorBackward(multiplier) cursorBackward(multiplier)
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x1b, 0x62}): // M-b case bytes.Equal(c, []byte{0x1b, 0x62}): // M-b
cursorBackwardWord() cursorBackwardWord()
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x1b, 0x66}): // M-f case bytes.Equal(c, []byte{0x1b, 0x66}): // M-f
cursorForwardWord() cursorForwardWord()
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x7f}): // backspace case bytes.Equal(c, []byte{0x7f}): // backspace
buffer.deleteChar() buffer.deleteChar()
multiplier = 1
reset()
case bytes.Equal(c, []byte{0xb}): // C-k case bytes.Equal(c, []byte{0xb}): // C-k
buffer.deleteForward() buffer.deleteForward()
multiplier = 1
statusline.setFilePathColor(messageColor{1, 8})
reset()
default: default:
buffer.insertChar(string(c)) buffer.insertChar(string(c))
multiplier = 1
statusline.setFilePathColor(messageColor{1, 8})
reset()
}
}
func reset() {
prefix = nil
multiplier = 1
}
func setMultiplier() {
if multiplier == 1 {
multiplier = 4
modeline.setMessage("C-u-")
} else {
multiplier = multiplier + multiplier
msg := []string{}
count := 0
start := multiplier
for start > 2 {
start = start / 2
count++
}
for i := 0; i < count; i++ {
msg = append(msg, "C-u-")
}
modeline.setMessage(strings.Join(msg, ""))
} }
} }
/**
* BUFFER
*/
func (b *Buffer) new(lines []string) Buffer { func (b *Buffer) new(lines []string) Buffer {
b.Lines = lines b.Lines = lines
@ -179,12 +278,20 @@ func (b *Buffer) save() {
} }
defer file.Close() defer file.Close()
out := strings.Join(b.Lines, "\n") out := strings.Join(b.Lines, "\n")
_, err = file.WriteString(out)
fileBytes, err = file.WriteString(out)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
func (b *Buffer) size() string {
total := 0
for _, v := range b.Lines {
total += len(v)
}
return humanReadable(total)
}
func (b *Buffer) insertChar(inp string) { func (b *Buffer) insertChar(inp string) {
if len(b.Lines) < cursor.Row { if len(b.Lines) < cursor.Row {
b.Lines = append(b.Lines, inp) b.Lines = append(b.Lines, inp)
@ -217,6 +324,98 @@ func (b *Buffer) deleteForward() {
b.Lines[cursor.Row-1] = b.Lines[cursor.Row-1][:cursor.Col-1] b.Lines[cursor.Row-1] = b.Lines[cursor.Row-1][:cursor.Col-1]
} }
/**
* SCREEN
*/
func getTermSize() []int {
cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
out, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
size := string(out)
size = strings.TrimSpace(size)
slice := strings.Split(size, " ")
asRowInt, _ := strconv.ParseInt(slice[0], 0, 32)
rows := int(asRowInt)
asColInt, _ := strconv.ParseInt(slice[1], 0, 32)
cols := int(asColInt)
// fmt.Printf("%#v, %#v", size, []int{rows, cols})
return []int{rows, cols}
}
func humanReadable(inp int) string {
switch {
case inp > 1000000:
return fmt.Sprintf("%dM", inp/1000000)
case inp > 1000:
return fmt.Sprintf("%dk", inp/1000)
default:
return fmt.Sprintf("%d", inp)
}
}
func (s *Screen) render() {
termSize := getTermSize()
buffer.render()
statusline.setSize(buffer.size())
statusline.setFilePath(filePath)
statusline.setLocation(cursor.Row, cursor.Col)
statusline.render(termSize)
modeline.render(termSize)
}
/**
* MODELINE
*/
func (m *Modeline) setMessage(msg string) {
m.message = msg
}
func (m *Modeline) render(size []int) {
if m.message != "" {
moveCursor(size[0], 0)
fmt.Printf("%s", m.message)
}
}
/**
* STATUSLINE
*/
func (s *Statusline) setFormat() {
s.fileFormat = fmt.Sprintf("")
s.format = fmt.Sprintf("")
s.unformat = fmt.Sprintf("")
}
func (s *Statusline) setSize(fileSize string) {
s.fileSize = fileSize
}
func (s *Statusline) setFilePath(filePath string) {
array := []string{s.fileFormat, filePath, s.format}
s.filePath = strings.Join(array, " ")
}
func (s *Statusline) setFilePathColor(color messageColor) {
s.fileFormat = fmt.Sprintf("[48;5;%dm[38;5;%dm", color.background, color.foreground)
// statusline.setFilePath(s.filePath)
}
func (s *Statusline) setLocation(row, col int) {
s.location = fmt.Sprintf("%d:%d", row, col)
}
func (s *Statusline) render(size []int) {
moveCursor(size[0]-1, 0)
statuslineParts := []string{s.fileSize, s.fileFormat, s.filePath, s.format, s.location}
line := strings.Join(statuslineParts, " ")
addLength := len(s.fileFormat) + len(s.format) + len(s.unformat)
fmt.Printf("%s%-*s%s", s.format, size[1]+addLength-1, line, s.unformat)
}
/**
* CURSOR
*/
func (c *Cursor) new() Cursor { func (c *Cursor) new() Cursor {
return Cursor{ return Cursor{
1, 1,
@ -224,6 +423,13 @@ func (c *Cursor) new() Cursor {
} }
} }
// func clamp(max, cur int) int {
// if cur <= max {
// return cur
// }
// return max
// }
func (c *Cursor) clampCol() { func (c *Cursor) clampCol() {
line := buffer.fetch(c.Row) line := buffer.fetch(c.Row)
if c.Col > len(line) { if c.Col > len(line) {

Loading…
Cancel
Save