package coreimport ()// Tokenizer is a method used by a (line) type to split itself according to// different rules (split between spaces, punctuation, brackets, quotes, etc.).typeTokenizerfunc(cursorPos int) (split []string, index int, newPos int)// Line is an input line buffer.// Contains methods to search and modify its contents,// split itself with tokenizers, and displaying itself.typeLine []rune// Set replaces the line contents altogether with a new slice of characters.// If no characters are passed, the line is thus made empty.func ( *Line) ( ...rune) { * = }// Insert inserts one or more runes at the given position.// If the position is either negative or greater than the// length of the line, nothing is inserted.func ( *Line) ( int, ...rune) {for {// I don't really understand why `0` is creeping in at the // end of the array but it only happens with unicode characters.iflen() > 1 && [len()-1] == 0 { = [:len()-1]continue }break }// Invalid position cancels the insertionif < 0 || > .Len() {return }switch {case .Len() == 0: * = case < .Len(): := string((*)[:]) := string(append((*)[:], ...)) += * = []rune()case == .Len(): * = append(*, ...) }}// InsertBetween inserts one or more runes into the line, between the specified// begin and end position, effectively deleting everything in between those.// If either or these positions is equal to -1, the selection content// is inserted at the other position. If both are -1, nothing is done.func ( *Line) (, int, ...rune) { , , := .checkRange(, )if ! {return }switch {case == -1: .Insert(, ...)case == .Len(): := string((*)[:]) + string() * = []rune()default: := string((*)[:]) := string(append((*)[:], ...)) += * = []rune() }}// Cut deletes a slice of runes between a beginning and end position on the line.// If the begin/end pos is negative/greater than the line, all runes located on// valid indexes in the given range are removed.func ( *Line) (, int) { , , := .checkRange(, )if ! {return }switch {case -1: := string((*)[:]) * = []rune()default: := string((*)[:]) := string((*)[:]) += * = []rune() }}// CutRune deletes a rune at the given position in the line.// If the position is out of bounds, nothing is deleted.func ( *Line) ( int) {if < 0 || > .Len() || .Len() == 0 {return }switch {case == 0: * = (*)[1:]case == .Len(): * = (*)[:-1]default: := string((*)[+1:]) := string((*)[:]) += * = []rune() }}// Len returns the length of the line, as given by ut8.RuneCount.// This should NOT confused with the length of the line in terms of// how many terminal columns its printed representation will take.func ( *Line) () int {returnutf8.RuneCountInString(string(*))}// SelectWord returns the begin and end index positions of a word// (separated by punctuation or spaces) around the specified position.func ( *Line) ( int) (, int) {if .Len() == 0 {return , } = .checkPosRange()if == .Len() { -- } := regexp.MustCompile("[0-9a-zA-Z_]") , = , if := .MatchString(string((*)[])); ! { = regexp.MustCompile(`\s`) }// To first space found backwardfor ; >= 0; -- {if := .MatchString(string((*)[])); ! {break } }// And to first space found forwardfor ; < .Len(); ++ {if := .MatchString(string((*)[])); ! {break } } ++// Ending position must be greater than 0if > 0 { -- }return , }// SelectBlankWord returns the begin and end index positions// of a full bigword (blank word) around the specified position.func ( *Line) ( int) (, int) {if .Len() == 0 {return , } = .checkPosRange()if == .Len() { -- } := regexp.MustCompile(`[^\s]`) , = , if := .MatchString(string((*)[])); ! { = regexp.MustCompile(`\s`) }// To first space found backwardfor ; >= 0; -- { := > 0 && (*)[-1] == '\\'if := .MatchString(string((*)[])); ! && ! {break } }// And to first space found forwardfor ; < .Len(); ++ { := > 0 && (*)[-1] == '\\'if := .MatchString(string((*)[])); ! && ! {break } } ++// Ending position must be greater than 0if > 0 { -- }return , }// Find returns the index position of a target rune, or -1 if not found.func ( *Line) ( rune, int, bool) int {if .Len() == 0 {return -1 } = .checkPosRange()for {if { ++if > .Len()-1 {break } } else { --if < 0 {break } }// Check if character matchesif (*)[] == {return } }// The rune was not found.return -1}// FindSurround returns the beginning and end positions of an enclosing rune (either// matching signs -brackets- or the rune itself -quotes/letters-) and the enclosing chars.func ( *Line) ( rune, int) (, int, , rune) { , = strutil.MatchSurround() = .Find(, +1, false) = .Find(, -1, true)return}// SurroundQuotes returns the index positions of enclosing quotes around the given cursor// position, provided that these quotes are really enclosing the inner selection (that is,// that each of those quotes is not paired with another, outer quote).// bpos or epos can be -1 if no quotes have been forward/backward found.func ( *Line) ( bool, int) (, int) {var , runeif { , = '\'', '\'' } else { , = '"', '"' }// How many occurrences before and after cursor.var , int = .Find(, +1, false) = .Find(, , true) , := , for {if != -1 { ++ }if != -1 { ++ }// If one of the searches failed, we're done.if == -1 || == -1 {break }// Or we use a new forward/backward reference pos. = .Find(, , false) = .Find(, , true) }// If there is an equal number of signs (like quotes) on each side, // that means we are not pointing at a word/phrase within quotes.if %2 == 0 && %2 == 0 {return -1, -1 }// Or we possibly are (but not mandatorily: bpos/epos can be -1)return , }// DisplayLine prints the line to stdout, starting at the current terminal// cursor position, assuming it is at the end of the shell prompt string.// Params:// @indent - Used to align all lines (except the first) together on a single column.func ( *Line, int) { := strings.Split(string(*), "\n")ifstrings.HasSuffix(string(*), "\n") { = append(, "") }for , := range {// Don't let any visual selection go further than length. += color.BgDefault// Clear everything before each line, except the first.if > 0 {term.MoveCursorForwards() = term.ClearLineBefore + }// Clear everything after each line, except the last.if < len()-1 {iflen()+ < term.GetWidth() { += term.ClearLineAfter } += term.NewlineReturn }fmt.Print() }}// CoordinatesLine returns the number of real terminal lines on which the input line spans, considering// any contained newlines, any overflowing line, and the indent passed as parameter. The values also// take into account an eventual suggestion added to the line before printing.// Params:// @indent - Coordinates to align all lines (except the first) together on a single column.// Returns:// @x - The number of columns, starting from the terminal left, to the end of the last line.// @y - The number of actual lines on which the line spans, accounting for line wrap.func ( *Line, int) (, int) { := string(*) := strings.Split(, "\n") , := 0, 0for , := range { , := strutil.LineSpan([]rune(), , ) += = }return , }// Lines returns the number of real lines in the input buffer.// If there are no newlines, the result is 0, otherwise it's// the number of lines - 1.func ( *Line) () int { := string(*) := regexp.MustCompile(string(inputrc.Newline)) := .FindAllStringIndex(, -1)returnlen()}// Forward returns the offset to the beginning of the next// (forward) token determined by the tokenizer function.func ( *Line) ( Tokenizer, int) ( int) { , , := ()switch {caselen() == 0:returncase +1 == len(): = .Len() - default: = len([]) - }return}// ForwardEnd returns the offset to the end of the next// (forward) token determined by the tokenizer function.func ( *Line) ( Tokenizer, int) ( int) { , , := ()iflen() == 0 {return } := strings.TrimRightFunc([], unicode.IsSpace)switch {case == len()-1 && >= len()-1:returncase >= len()-1: = strings.TrimRightFunc([+1], unicode.IsSpace) = len([]) - += len() - 1default: = len() - - 1 }return}// Backward returns the offset to the beginning position of the previous// (backward) token determined by the tokenizer function.func ( *Line) ( Tokenizer, int) ( int) { , , := ()switch {caselen() == 0:returncase == 0 && == 0:returncase == 0: = len([-1])default: = }return * -1}// Tokenize splits the line on each word, that is, split on every punctuation or space.func ( *Line) ( int) ([]string, int, int) { := *if .Len() == 0 {returnnil, 0, 0 } = .checkPosRange()var , intvarbool := make([]string, 1)for , := range {switch {caseunicode.IsPunct():if > 0 && [-1] != { = append(, "") } [len()-1] += string() = truecase == ' ' || == '\t': [len()-1] += string() = truecase == '\n':// Newlines are a word of their own only // when the last rune of the previous word // is one as well.if > 0 && [-1] == { = append(, "") } [len()-1] += string() = truedefault:if { = append(, "") } [len()-1] += string() = false }// Not caught when we are appending to the end // of the line, where rl.pos = linePos + 1, so...if == { = len() - 1 = len([]) - 1 } }// ... so we adjust here for this case.if == len() { = len() - 1 = len([]) }return , , }// TokenizeSpace splits the line on each WORD (blank word), that is, split on every space.func ( *Line) ( int) ([]string, int, int) { := *if .Len() == 0 {returnnil, 0, 0 } = .checkPosRange()var , int := make([]string, 1)varboolfor , := range {switch {case' ', '\t': [len()-1] += string() = falsecase'\n':// Newlines are a word of their own only // when the last rune of the previous word // is one as well.if > 0 && [-1] == { = append(, "") } [len()-1] += string() = truedefault:if ( > 0 && ([-1] == ' ' || [-1] == '\t')) || { = append(, "") } = false [len()-1] += string() }// Not caught when we are appending to the end // of the line, where rl.pos = linePos + 1, so...if == { = len() - 1 = len([]) - 1 } }// ... so we adjust here for this case.if == len() { = len() - 1 = len([]) }return , , }// TokenizeBlock splits the line into arguments delimited either by// brackets, braces and parenthesis, and/or single and double quotes.func ( *Line) ( int) ([]string, int, int) { := *if .Len() == 0 {returnnil, 0, 0 } = .checkPosRange()if == .Len() { -- }var ( , rune []stringint = make(map[int]int)int , bool )switch [] {case'(', ')', '{', '[', '}', ']': , = strutil.MatchSurround([])default:returnnil, 0, 0 }for := range {switch [] {case'\'':if ! { = ! }case'"':if ! { = ! }case :if ! && ! { , , = openToken(, , , , , , ) } elseif == {returnnil, 0, 0 }case :if ! && ! { , = closeToken(, , , , , , )if == {return , 1, 0 } elseif == {return , 1, len([1]) } } elseif == {returnnil, 0, 0 } } }returnnil, 0, 0}// add a new block token to the list of split tokens.func openToken(, , , int, map[int]int, []rune, []string) (int, int, []string) { ++ [] = if != {return , , }// Important: don't index a negative below.if == 0 { ++ } = = []string{string([:-1])}return , , }// close the current block token if any.func closeToken(, , , int, map[int]int, []rune, []string) (int, []string) {if == { = append(, string([[]:]))return , }if == { := []if == 0 { ++ } = []string{string([:-1]),string([[]:]), }return , } --return , }// newlines gives the indexes of all newline characters in the line.func ( *Line) () [][]int { := string(*) += string(inputrc.Newline) := regexp.MustCompile(string(inputrc.Newline))return .FindAllStringIndex(, -1)}// returns bpos, epos ordered and true if either is valid.func ( *Line) (, int) (int, int, bool) {if == -1 && == -1 {return -1, -1, false }// Check positions out of boundif > .Len() { = .Len() }if < 0 { = 0 }// Order begin and end posif > -1 && < { , = , }return , , true}// similar to checkPos, but won't fail: will bring// the position back onto a valid index on the line.func ( *Line) ( int) int {if < 0 {return0 }if > .Len() {return .Len() }return}
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.