package coreimport ()// Cursor is the cursor position in the current line buffer.// Contains methods to set, move, describe and check itself.typeCursorstruct { pos int mark int line *Line}// NewCursor is a required constructor for the line cursor,// because some default numeric values must be negative.func ( *Line) *Cursor {return &Cursor{pos: 0,mark: -1,line: , }}// Set sets the position of the cursor to an absolute value.// If either negative or greater than the length of the line,// the cursor will be set to either 0, or the length of the line.func ( *Cursor) ( int) {defer .CheckAppend()switch {case < 0: .pos = 0case > .line.Len(): .pos = .line.Len()default: .pos = }}// Pos returns the current cursor position.// This function cannot return an invalid cursor position: it cannot be negative, nor it// can be greater than the length of the line (note that it still can be out of line by 1).func ( *Cursor) () int { .CheckAppend()return .pos}// Inc increments the cursor position by 1,// if it's not at the end of the line.func ( *Cursor) () {if .pos < .line.Len() { .pos++ }}// Dec decrements the cursor position by 1,// if it's not at the beginning of the line.func ( *Cursor) () {if .pos > 0 { .pos-- }}// Move moves the cursor position by a relative value. If the end result is negative,// the cursor is set to 0. If longer than the line, the cursor is set to length of line.func ( *Cursor) ( int) {defer .CheckAppend() .pos += }// Char returns the rune (unicode point) under the cursor.// If the line is empty, or if the cursor is appending to// the line, the returned rune is 0 (rune(0)).func ( *Cursor) () rune { .CheckAppend()if .line.Len() == 0 {returnrune(0) }if .pos >= .line.Len() {returnrune(0) }return (*.line)[.pos]}// ReplaceWith replaces the rune (unicode point) under the cursor with the provided one.// If the cursor is appending to the line, the character is simply added at the end of it.func ( *Cursor) ( rune) { .CheckAppend()switch {case .pos == .line.Len(): .line.Insert(.line.Len(), )default: (*.line)[.pos] = }}// InsertAt inserts the given runes into the line at the current cursor position.func ( *Cursor) ( ...rune) { .CheckAppend() .line.Insert(.pos, ...) .pos += len()}// ToFirstNonSpace moves the cursor either backward or forward to// the first character in the line that is not a space, a tab or// a newline. If the current is not one, the cursor doesn't move.// If the cursor is at the end of the line, the move is performed// backward, regardless of the forward parameter value.func ( *Cursor) ( bool) {if .line.Len() == 0 {return }defer .CheckAppend()if .pos >= .line.Len() { = false .pos = .line.Len() - 1 }// At line boundsif ! && .pos == 0 {return }for {if !.onSpace() {return }if { .pos++ } else { .pos-- }if .pos <= 0 {return } }}// BeginningOfLine moves the cursor to the beginning of the current line,// (marked by a newline) or if no newline found, to the beginning of the buffer.func ( *Cursor) () {defer .CheckCommand() := .line.Find(inputrc.Newline, .pos, false)if != -1 { .pos = + 1 } else { .pos = 0 }}// EndOfLine moves the cursor to the end of the current line,// (marked by a newline) or if no newline found, to the position// of the last character in the buffer.func ( *Cursor) () {defer .CheckCommand()if .OnEmptyLine() {return } := .line.Find(inputrc.Newline, .pos, true)if != -1 { .pos = - 1 } else { .pos = .line.Len() - 1 }}// EndOfLineAppend moves the cursor to the end of either current line// (if buffer is multiline), or the whole buffer, in append-mode.func ( *Cursor) () {defer .CheckAppend()if .OnEmptyLine() {return } := .line.Find(inputrc.Newline, .pos-1, true)if != -1 { .pos = } else { .pos = .line.Len() }}// SetMark sets the current cursor position as the mark.func ( *Cursor) () { .CheckAppend() .mark = .pos}// Mark returns the current mark value of the cursor, or -1 if not set.func ( *Cursor) () int {return .mark}// ResetMark resets the insertion point mark (-1).func ( *Cursor) () { .mark = -1}// LinePos returns the index of the current line on which the cursor is.// A line is defined as a sequence of runes between one or two newline// characters, between end and/or beginning of buffer, or a mix of both.func ( *Cursor) () int { .CheckAppend() := .line.newlines()// Either match between two newlinesfor , := range {if [0] < .pos {continue }return }// Or return the number of linesreturnlen()}// LineMove moves the cursor by n lines either up (if the value is negative),// or down (if positive). If greater than the length of possible lines above/below,// the cursor will be set to either the first, or the last line of the buffer.func ( *Cursor) ( int) { .CheckAppend()defer .CheckAppend() := .line.newlines()iflen() == 1 || == 0 {return }if < 0 {for := 0; < -1*; ++ { .moveLineUp() .CheckCommand() } } else {for := 0; < ; ++ { .moveLineDown() .CheckCommand() } }}// OnEmptyLine returns true if the rune under the current cursor position is a newline// and that the preceding rune in the line is also a newline, or returns false.func ( *Cursor) () bool {if .line.Len() == 0 {returntrue }if .pos == 0 {return (*.line)[.pos] == inputrc.Newline } elseif .pos == .line.Len() {return (*.line)[.pos-1] == inputrc.Newline } := (*.line)[.pos] == inputrc.Newline := (*.line)[.pos-1] == inputrc.Newlinereturn && }// AtBeginningOfLine returns true if the cursor is either at the beginning// of the line buffer, or on the first character after the previous newline.func ( *Cursor) () bool {if .pos == 0 {returntrue } := .line.newlines()for := 0; < len(); ++ { := [][0]if == .pos-1 {returntrue } }returnfalse}// AtEndOfLine returns true if the cursor is either at the end of the// buffer, or if the character immediately following it is a newline.func ( *Cursor) () bool {if .pos >= .line.Len()-1 {returntrue } := .line.newlines()for := 0; < len(); ++ { := [][0]if == .pos+1 {returntrue } }returnfalse}// CheckAppend verifies that the current cursor position is neither negative,// nor greater than the length of the input line. If either is true, the// cursor will set its value as either 0, or the length of the line.func ( *Cursor) () {// Positionif .pos < 0 { .pos = 0 }if .pos > .line.Len() { .pos = .line.Len() }// Mark, invalid position deactivates it.if .mark < -1 { .mark = -1 }if .mark > .line.Len()-1 { .mark = -1 }}// CheckCommand is like CheckAppend, but ensures the cursor position is never greater// than the length of the line minus 1, since in Vim command mode, the cursor is on a char.func ( *Cursor) () { .CheckAppend()if .pos == .line.Len() && !.OnEmptyLine() { .pos-- }// The cursor can also not be on a newline sign, // as it will induce the line rendering into an error.if .line.Len() > 0 && .pos < .line.Len() && .Char() == '\n' && !.OnEmptyLine() { .Dec() }}// CoordinatesCursor returns the number of real terminal lines above the cursor position// (y value), and the number of columns since the beginning of the current line (x value).// @indent - Used to align all lines (except the first) together on a single column.func ( *Cursor, int) (, int) { .CheckAppend() := .line.newlines() := 0 := 0for , := range {switch {case [0] < .pos:// Until we didn't reach the cursor line, // simply care about the line count. := (*.line)[:[0]] = [0] + 1 , := strutil.LineSpan(, , ) += default:// On the cursor line, use both line and column count. := (*.line)[:.pos] , := strutil.LineSpan(, , ) += return , } }return}func ( *Cursor) () {var , int = -1 := .line.newlines()for := 0; < len(); ++ { := [][0]if < .LinePos() { = continue }// If we are on the current line, // go at the end of itif == .LinePos() { = .pos - = continue }// And either go at the end of the line // or to the previous cursor X coordinate.if - > { .pos = + } else { .pos = }break }}func ( *Cursor) () {var , int := .line.newlines()for := len() - 1; >= 0; -- { := [][0]if > .LinePos() {continue }// Get the beginning of the previous line.if > 0 { = [-1][0] } else { = -1 -- }// If we are on the current line, // go at the beginning of the previous one.if == .LinePos() { = .pos - continue }// And either go at the end of the line // or to the previous cursor X coordinate.if - > { .pos = + } else { .pos = }break }}func ( *Cursor) () bool {switch .Char() {caseinputrc.Space, inputrc.Newline, inputrc.Tab:returntruedefault:returnfalse }}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.