package editor import ( "timshome.page/gilo/char" "timshome.page/gilo/editor/document" "timshome.page/gilo/internal/gilo" "timshome.page/gilo/terminal" ) // ---------------------------------------------------------------------------- // !Constants representing input keys // ---------------------------------------------------------------------------- const ( keyUp = "ARROW_UP" keyDown = "ARROW_DOWN" keyLeft = "ARROW_LEFT" keyRight = "ARROW_RIGHT" keyPageUp = "PAGE_UP" keyPageDown = "PAGE_DOWN" keyHome = "HOME" keyEnd = "END" keyDelete = "DELETE" ) /** * Determine what to do with an individual character of input */ func (e *Editor) processKeypressChar(ch rune) bool { switch ch { case char.Ctrl('q'): if e.doc.IsDirty() && e.quitTimes > 0 { e.SetStatusMessage("WARNING!!! File has unsaved changes. Press Ctrl-Q %d more times to quit.", e.quitTimes) e.quitTimes -= 1 return true } // Clean up on exit terminal.Write(terminal.ClearScreen + terminal.ResetCursor) return false case char.Ctrl('s'): e.save() case char.Ctrl('f'): e.find() case char.Enter: e.doc.InsertNewline(e.cursor) e.cursor.Y += 1 e.cursor.X = 0 case char.Backspace, char.Ctrl('h'): e.delChar() case char.Esc, char.Ctrl('l'): // Modifier keys that return ANSI escape sequences str := parseEscapeSequence() // Don't swallow a character after ESC if it doesn't // start an ANSI escape sequence if len(str) == 1 { return e.processKeypressChar(rune(str[0])) } e.processKeypressStr(str) default: e.insertChar(ch) } // Clear the quit message and restart the // confirmation count if confirmation is not // completed if e.quitTimes != gilo.QuitTimes { e.quitTimes = gilo.QuitTimes e.SetStatusMessage("") } return true } /** * Determine what do do with a parsed ANSI escape sequence */ func (e *Editor) processKeypressStr(key string) { switch key { case keyUp, keyDown, keyLeft, keyRight, keyPageUp, keyPageDown, keyHome, keyEnd: e.moveCursor(key) case keyDelete: e.moveCursor(keyRight) e.delChar() } } func (e *Editor) moveCursor(key string) { var row *document.Row if e.cursor.Y >= e.doc.RowCount() { row = nil } else { row = e.doc.GetRow(e.cursor.Y) } switch key { case keyLeft: if e.cursor.X != 0 { e.cursor.X -= 1 } // Move from beginning of current row to end of previous row if e.cursor.Y > 0 { e.cursor.Y -= 1 e.cursor.X = e.doc.GetRow(e.cursor.Y).Size() } case keyRight: if row != nil && e.cursor.X < row.Size() { e.cursor.X += 1 } // Move from end of current line to beginning of next line if row != nil && e.cursor.X == row.Size() && e.cursor.Y < e.doc.RowCount()-1 { e.cursor.Y += 1 e.cursor.X = 0 } case keyUp: if e.cursor.Y != 0 { e.cursor.Y -= 1 } case keyDown: if e.cursor.Y < e.doc.RowCount() { e.cursor.Y += 1 } case keyPageUp: if e.cursor.Y > e.screen.Rows { e.cursor.Y -= e.screen.Rows } else { e.cursor.Y = 0 } case keyPageDown: if e.cursor.Y+e.screen.Rows > e.doc.RowCount() { e.cursor.Y += e.screen.Rows } else { e.cursor.Y = e.doc.RowCount() - 1 } case keyHome: e.cursor.X = 0 case keyEnd: if row != nil { e.cursor.X = row.Size() } } if e.cursor.Y < e.doc.RowCount() { row = e.doc.GetRow(e.cursor.Y) rowLen := row.Size() // Snap to the end of a shorter line from a longer one if e.cursor.X > rowLen { e.cursor.X = rowLen } } } // Convert the raw ANSI escape sequences to the type of key input func parseEscapeSequence() string { // If we aren't starting an escape sequence, // return the character startChar, _ := terminal.ReadKey() if startChar != '[' && startChar != 'O' { return string(startChar) } // Read one or two characters after // \e[ or \eO, which is the end of the // handled escape sequences runes := [2]rune{'\000', '\000'} for i := 0; i < 2; i++ { ch, size := terminal.ReadKey() if size == 0 { return string(char.Esc) } runes[i] = ch if i == 0 && runes[0] >= 'A' && runes[0] <= 'Z' { return escSeqToKey([]rune{startChar, runes[0]}) } // \e[*~ if i == 1 && startChar == '[' && runes[1] == '~' { return escSeqToKey([]rune{startChar, runes[0], runes[1]}) } } return string(char.Esc) } func escSeqToKey(seq []rune) string { // \eO* // \e[* if len(seq) == 2 { startChar, cmd := seq[0], seq[1] if startChar == 'O' { switch cmd { case 'H': return keyHome case 'F': return keyEnd } } else if startChar == '[' { switch cmd { case 'A': return keyUp case 'B': return keyDown case 'C': return keyRight case 'D': return keyLeft case 'H': return keyHome case 'F': return keyEnd } } } else if len(seq) == 3 { // \e[*~ cmd := seq[1] switch cmd { case '1': return keyHome case '3': return keyDelete case '4': return keyEnd case '5': return keyPageUp case '6': return keyPageDown case '7': return keyHome case '8': return keyEnd } } return string(char.Esc) }