// The main interface/implementation of the editor object package editor import ( "fmt" "time" doc "timshome.page/gilo/editor/document" "timshome.page/gilo/gilo" "timshome.page/gilo/key" "timshome.page/gilo/terminal" ) // ---------------------------------------------------------------------------- // !Editor // ---------------------------------------------------------------------------- type statusMsg struct { message string created time.Time } type editor struct { screen *terminal.Screen cursor *gilo.Point offset *gilo.Point document *doc.Document status *statusMsg quitTimes uint8 renderX int } func NewEditor() *editor { screen := terminal.Size() // Subtract rows for status bar and message bar/prompt screen.Rows -= 2 cursor := gilo.DefaultPoint() offset := gilo.DefaultPoint() document := doc.NewDocument() status := &statusMsg{ "", time.Now(), } return &editor{ screen, cursor, offset, document, status, gilo.QuitTimes, 0, } } func (e *editor) Open(filename string) { e.document.Open(filename) } func (e *editor) SetStatusMessage(template string, a ...interface{}) { e.status = &statusMsg{ fmt.Sprintf(template, a...), time.Now(), } } func (e *editor) ProcessKeypress() bool { ch, _ := terminal.ReadKey() return e.processKeypressChar(ch) } func (e *editor) save() { if e.document.Filename == "" { e.document.Filename = e.prompt("Save as: %s (ESC to cancel)", nil) if e.document.Filename == "" { e.SetStatusMessage("Save aborted") } } size := e.document.Save() if size > 0 { e.SetStatusMessage("%d bytes written to disk", size) } else { e.SetStatusMessage("Failed to save file") } } func (e *editor) prompt(prompt string, callback func(string, string)) string { buf := gilo.NewBuffer() // Show the prompt message e.SetStatusMessage(prompt, "") e.RefreshScreen() for { if buf.Len() > 0 { e.SetStatusMessage(prompt, buf.ToString()) e.RefreshScreen() } ch, _ := terminal.ReadKey() if ch == key.Enter { if buf.Len() != 0 { e.SetStatusMessage("") if callback != nil { callback(buf.ToString(), string(ch)) } return buf.ToString() } } else if key.IsAscii(ch) && !key.IsCtrl(ch) { buf.AppendRune(ch) } else if ch == key.Backspace || ch == key.Ctrl('h') { buf.Truncate(buf.Len() - 1) } else if ch == key.Esc { k := parseEscapeSequence() if k == keyDelete { buf.Truncate(buf.Len() - 1) } else if k == string(key.Esc) { e.SetStatusMessage("") if callback != nil { callback(buf.ToString(), k) } return "" } else { if callback != nil { callback(buf.ToString(), k) } } } if callback != nil { callback(buf.ToString(), string(ch)) } } } func (e *editor) find() { savedCursor := e.cursor.Clone() savedOffset := e.cursor.Clone() lastMatch := -1 direction := 1 query := e.prompt("Search: %s (Use ESC/Arrows/Enter)", func(query string, ch string) { if ch == string(key.Enter) || ch == string(key.Esc) { lastMatch = -1 direction = 1 return } else if ch == keyRight || ch == keyDown { direction = 1 } else if ch == keyLeft || ch == keyUp { direction = -1 } else { lastMatch = -1 direction = 1 } if lastMatch == -1 { direction = 1 } current := lastMatch for i := 0; i < e.document.RowCount(); i++ { current += direction if current == -1 { current = e.document.RowCount() } else if current == e.document.RowCount() { current = 0 } row := e.document.GetRow(current) matchIndex := row.Search(query) if matchIndex != -1 { lastMatch = current e.cursor.Y = current e.cursor.X = row.RenderXtoCursorX(matchIndex) e.offset.Y = e.document.RowCount() break } } }) if query == "" { e.cursor = savedCursor.Clone() e.offset = savedOffset.Clone() return } } func (e *editor) insertChar(ch rune) { if e.cursor.Y == e.document.RowCount() { e.document.AppendRow("") } e.document.InsertChar(e.cursor, ch) e.cursor.X += 1 } func (e *editor) delChar() { if e.cursor.Y == e.document.RowCount() { return } if e.cursor.X == 0 && e.cursor.Y == 0 { return } if e.cursor.X > 0 { at := e.cursor at.X -= 1 e.document.DelChar(at) } else { // Move cursor to the current end of the previous line e.cursor.X = e.document.GetRow(e.cursor.Y - 1).Size() // Move the contents of the current row to the previous e.document.MergeRows(e.cursor.Y-1, e.cursor.Y) e.cursor.Y -= 1 } }