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.

244 lines
4.8 KiB

package main
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"os"
"runtime"
"syscall"
"unicode/utf8"
"unsafe"
)
const (
CLEAR = "\x1b[K"
TAB_SIZE = 8
EMPTY_LINE_GUTTER = " ~ "
LINE_GUTTER = " "
STATUSLINEHEIGHT = 2
WHITESPACE = "\xB8"
NEWLINE = '\xAC'
)
func uiDrawRows() {
buf := state.Buffer
currentRow := ""
if len(state.rows) > 0 {
currentRow = state.rows[state.Cy]
}
_, highlightedRowLen := renderLine(currentRow)
rx := editorCxoRx(currentRow, state.Cx)
gutterWidth, gutterFmtStr := calcMaxGutterWidth()
file := state.file
if file == "" {
file = "NO FILE"
}
state.StatusLines[0] = fmt.Sprintf(
"%s\x1b[1;7m '%s' \x1b[m L:%d/%d C:%d/%d | H:%d W: %d",
CLEAR,
file,
state.Cy+1,
len(state.rows),
rx+1,
highlightedRowLen+1,
state.Rows-1,
state.Cols-gutterWidth-1)
for y := 0; y < state.Rows; y++ {
if y >= state.Rows-STATUSLINEHEIGHT {
buf.WriteString(drawStatusLine(y - (state.Rows - STATUSLINEHEIGHT)))
} else if state.Cy < len(state.rows) && y + state.rowOffset < len(state.rows) {
rowIdx := y + state.rowOffset
if rowIdx < len(state.rows) {
// wrie the gutter w/ line numbers
fmt.Fprintf(buf, gutterFmtStr, rowIdx+1)
// calc how much room left for text
editorCols := (state.Cols - gutterWidth) - 1
// get length and content for the current row
row, rowLen := renderLine(state.rows[rowIdx])
// calculate offset from the left
renderLen := rowLen
rowEnd := editorCols + state.colOffset
if renderLen > editorCols && rowEnd <= rowLen {
renderLen = rowEnd
}
if state.colOffset <= renderLen {
buf.WriteString(fmt.Sprintf("%s%c", row[state.colOffset:renderLen], NEWLINE))
}
if rowLen > rowEnd {
buf.WriteByte('>')
}
}
} else {
buf.WriteString(EMPTY_LINE_GUTTER)
}
buf.WriteString(CLEAR)
if y < state.Rows-1 {
buf.WriteString("\r\n")
}
}
}
func drawStatusLine(row int) string {
statusLine := state.StatusLines[row]
statusLen := len(statusLine)
if statusLen > state.Cols {
statusLen = state.Cols
}
return statusLine[:statusLen]
}
func uiRefresh() {
buf := state.Buffer
fmt.Fprintf(buf, "\x1b[?251")
fmt.Fprintf(buf, "\x1b[H")
var err error
if state.Rows, state.Cols, err = getWindowSize(); err != nil {
log.Fatal(err)
}
uiDrawRows()
gutterWidth, _ := calcMaxGutterWidth()
y := state.Cy - state.rowOffset
currentRow := ""
if len(state.rows) > 0 {
currentRow = state.rows[y]
}
rx := gutterWidth + editorCxoRx(currentRow, state.Cx) - state.colOffset
setCursorPosition(rx, y)
fmt.Fprintf(buf, "\x1b[?25h")
os.Stdout.Write(buf.Bytes())
buf.Reset()
}
func uiClearScreen() {
fmt.Fprintf(os.Stdout, "\x1b[2J")
fmt.Fprintf(os.Stdout, "\x1b[H")
}
func setCursorPosition(x, y int) {
fmt.Fprintf(state.Buffer, "\x1b[%d;%dH", y+1, x+1)
}
func getCursorPosition() (int, int, error) {
if _, err := os.Stdout.Write([]byte("\x1b[6n")); err != nil && err != io.EOF {
return -1, -1, err
}
buf := make([]byte, 32)
temp := make([]byte, 1)
for i := 0; i < len(buf)-1; i++ {
if _, err := os.Stdin.Read(temp); err != nil && err != io.EOF {
return -1, -1, err
}
buf[i] = temp[0]
if bytes.ContainsRune(temp, 'R') {
break
}
}
if buf[0] != byte(KEY_ESC) || buf[1] != '[' {
return 0, 0, errors.New("can't read pos")
}
var rows, cols int
if _, err := fmt.Fscanf(bytes.NewBuffer(buf[2:]), "%d;%d", &rows, &cols); err != nil && err != io.EOF {
return 0, 0, errors.New("can't read pos")
}
return rows, cols, nil
}
type window struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}
func getWindowSize() (int, int, error) {
w := new(window)
_, _, err := syscall.Syscall(syscall.SYS_IOCTL,
os.Stdout.Fd(),
syscall.TIOCGWINSZ,
uintptr(unsafe.Pointer(w)),
)
runtime.GC()
if int(err) == -1 || w.Col == 0 {
if _, err := os.Stdout.Write([]byte("\x1b[999C\x1b[999B")); err != nil {
return -1, -1, errors.New("ioctl FALLBACK")
}
return getCursorPosition()
}
return int(w.Row), int(w.Col), nil
}
func calcMaxGutterWidth() (int, string) {
lineCount := len(state.rows)
maxDigits := len([]rune(fmt.Sprintf("%d", lineCount)))
gutterStr := fmt.Sprintf("%s%s%%%dd ", CLEAR, LINE_GUTTER, maxDigits)
// 3 accounts for ' ~ '
return len([]rune(LINE_GUTTER)) + maxDigits + 1, gutterStr
}
func editorCxoRx(row string, cx int) int {
rx := 0
for idx, c := range row {
if idx < cx {
if c == '\t' {
// rx += (TAB_SIZE - 1) - (rx % TAB_SIZE)
rx += (TAB_SIZE - 1)
}
rx++
continue
}
break
}
return rx
}
func renderLine(line string) (string, int) {
rl := ""
tabCount := 0
for _, char := range line {
if char == '\t' {
tabCount++
for i := 0; i < TAB_SIZE; i++ {
rl += WHITESPACE
}
continue
}
rl = rl + string(char)
}
return rl, utf8.RuneCountInString(rl) //+ (TAB_SIZE * tabCount) - tabCount
}