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
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"
"fmt"
"golang.org/x/crypto/ssh/terminal"
"os/exec"
"regexp"
"strconv"
"strings"
)
@ -22,28 +23,89 @@ type Buffer struct {
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
type Cursor struct {
Row 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 editor Editor
var buffer Buffer
var screen Screen
var modeline Modeline
var statusline Statusline
var cursor Cursor
var multiplier int
var prefix []byte
var fileBytes int
func main() {
if len(os.Args) == 1 {
log.Println("a file is required")
fmt.Println("You MUST supply a file to edit")
return
}
filePath = os.Args[1]
@ -53,19 +115,12 @@ func main() {
func (e *Editor) initialize() {
// Load a file
file, err := os.Open(filePath)
file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0755)
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() {
@ -76,6 +131,7 @@ func (e *Editor) initialize() {
}
multiplier = 1
statusline.setFormat()
buffer = buffer.new(lines)
cursor = cursor.new()
}
@ -89,66 +145,109 @@ func (e *Editor) run() {
func (e *Editor) render() {
clearScreen()
buffer.render()
screen.render()
moveCursor(cursor.Row, cursor.Col) // restore cursor
}
func (e *Editor) handleInput() {
c := utils.Getch()
// log.Printf("%#v\t%s\n", c, string(c))
modeline.setMessage("")
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()
case bytes.Equal(c, []byte{0x1}): // C-a
// beginning of line
reset()
case bytes.Equal(c, ctrl["a"]):
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)
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x1b, 0x5b, 0x42}), bytes.Equal(c, []byte{0xe}): // DOWN, C-n
cursorDown(multiplier)
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x1b, 0x5b, 0x43}), bytes.Equal(c, []byte{0x6}): // RIGHT, C-f
cursorForward(multiplier)
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x1b, 0x5b, 0x44}), bytes.Equal(c, []byte{0x2}): // LEFT, C-b
cursorBackward(multiplier)
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x1b, 0x62}): // M-b
cursorBackwardWord()
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x1b, 0x66}): // M-f
cursorForwardWord()
multiplier = 1
reset()
case bytes.Equal(c, []byte{0x7f}): // backspace
buffer.deleteChar()
multiplier = 1
reset()
case bytes.Equal(c, []byte{0xb}): // C-k
buffer.deleteForward()
multiplier = 1
statusline.setFilePathColor(messageColor{1, 8})
reset()
default:
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 {
b.Lines = lines
@ -179,12 +278,20 @@ func (b *Buffer) save() {
}
defer file.Close()
out := strings.Join(b.Lines, "\n")
_, err = file.WriteString(out)
fileBytes, err = file.WriteString(out)
if err != nil {
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) {
if len(b.Lines) < cursor.Row {
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]
}
/**
* 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 {
return Cursor{
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() {
line := buffer.fetch(c.Row)
if c.Col > len(line) {

Loading…
Cancel
Save