package coreimport ()// Selection contains all regions of an input line that are currently selected/marked// with either a begin and/or end position. The main selection is the visual one, used// with the default cursor mark and position, and contains a list of additional surround// selections used to change/select multiple parts of the line at once.typeSelectionstruct { Type string// Can be a normal one, surrounding (pairs), (cursor) matchers, etc. active bool// The selection is running. visual bool// The selection is highlighted. visualLine bool// The selection should span entire lines. bpos int// Beginning index position epos int// End index position (can be +1 in visual mode, to encompass cursor pos) kpos int// Keyword regexp matchers cycling counter. kmpos int// Keyword regexp matcher subgroups counter.// Display fg string// Foreground color of the highlighted selection. bg string// Background color. surrounds []Selection// Surrounds are usually pairs of characters matching each other (quotes/brackets, etc.)// Core line *Line cursor *Cursor}// NewSelection is a required constructor to use for initializing// a selection, as some numeric values must be negative by default.func ( *Line, *Cursor) *Selection {return &Selection{bpos: -1,epos: -1,line: ,cursor: , }}// Mark starts a pending selection at the specified position in the line.// If the position is out of the line bounds, no selection is started.// If this function is called on a surround selection, nothing happens.func ( *Selection) ( int) {if < 0 || > .line.Len() {return } .MarkRange(, -1)}// MarkRange starts a selection as a range in the input line. If either of// begin/end pos are negative, it is replaced with the current cursor position.// Any out of range positive value is replaced by the length of the line.func ( *Selection) (, int) { , , := .checkRange(, )if ! {return } .Type = "visual" .active = true .bpos = .epos = .bg = color.BgBlue}// MarkSurround creates two distinct selections each containing one rune.// The first area starts at bpos, and the second one at epos. If either bpos// is negative or epos is > line.Len()-1, no selection is created.func ( *Selection) (, int) {if < 0 || > .line.Len()-1 {return } .active = truefor , := range []int{, } { .surrounds = append(.surrounds, Selection{Type: "surround",active: true,visual: true,bpos: ,epos: ,bg: color.BgRed,line: .line,cursor: .cursor, }) }}// Active return true if the selection is active.// When created, all selections are marked active,// so that visual modes in Vim can work properly.func ( *Selection) () bool {return .active}// Visual sets the selection as a visual one (highlighted),// which is commonly seen in Vim edition mode.// If line is true, the visual is extended to entire lines.func ( *Selection) ( bool) { .visual = true .visualLine = }// IsVisual indicates whether the selection should be highlighted.func ( *Selection) () bool {return .visual}// Pos returns the begin and end positions of the selection.// If any of these is not set, it is set to the cursor position.// This is generally the case with "pending" visual selections.func ( *Selection) () (, int) {if .line.Len() == 0 || !.active {return -1, -1 } , , := .checkRange(.bpos, .epos)if ! {return , }// Use currently set values, or update if one is pending. .bpos, .epos = , if == -1 { , = .selectToCursor() }if .visual { ++ }// Always check that neither the initial values nor the ones // that we might have updated are wrong. It's very rare that // the adjusted values would be invalid as a result of this // call (unfixable values), but better being too safe than not. , , = .checkRange(, )if ! {return -1, -1 }return , }// Cursor returns what should be the cursor position if the active// selection is to be deleted, but also works for yank operations.func ( *Selection) () int { , := .Pos()if == -1 && == -1 {return .cursor.Pos() } := if !.visual || !.visualLine {return }varint := .cursor.Pos()// Get the indent of the cursor line.for = - 1; >= 0; -- {if (*.line)[] == '\n' {break } } = - - 1// If the selection includes the last line, // the cursor will move up the above line.var , intif < .line.Len() { = + 1 = } else {for = - 2; >= 0; -- {if (*.line)[] == '\n' {break } }if < -1 { = -1 } ++ = }// Now calculate the cursor position, the indent // must be less than the line characters.for = ; < .line.Len(); ++ {if (*.line)[] == '\n' {break }if + <= {break } }// That cursor position might be bigger than the line itself: // it should be controlled when the line is redisplayed. = + - return}// Len returns the length of the current selection. If any// of begin/end pos is not set, the cursor position is used.func ( *Selection) () int {if .line.Len() == 0 || (.bpos == .epos) {return0 } , := .Pos() := (*.line)[:]returnlen()}// Text returns the current selection as a string, but does not reset it.func ( *Selection) () string {if .line.Len() == 0 {return"" } , := .Pos()if == -1 || == -1 {return"" }returnstring((*.line)[:])}// Pop returns the contents of the current selection as a string,// as well as its begin and end position in the line, and the cursor// position as given by the Cursor() method. Then, the selection is reset.func ( *Selection) () ( string, , , int) {if .line.Len() == 0 {return"", -1, -1, 0 }defer .Reset() , = .Pos()if == -1 || == -1 {return"", -1, -1, 0 } = .Cursor() = string((*.line)[:])return , , , }// InsertAt insert the contents of the selection into the line, between the// 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 negative, nothing is done.// This is equivalent to selection.Pop(), and line.InsertAt() combined.//// After insertion, the selection is reset.func ( *Selection) (, int) { , , := .checkRange(, )if ! {return }// Get and reset the selection.defer .Reset() := .Text()switch {case == -1, == : .line.Insert(, []rune()...)default: .line.InsertBetween(, , []rune()...) }}// Surround surrounds the selection with a begin and end character,// effectively inserting those characters into the current input line.// If epos is greater than the line length, the line length is used.// After insertion, the selection is reset.func ( *Selection) (, rune) {if .line.Len() == 0 || .Len() == 0 {return }defer .Reset()var []rune = append(, ) = append(, []rune(.Text())...) = append(, )// The begin and end positions of the selection // is where we insert our new buffer. , := .Pos()if == -1 || == -1 {return } .line.InsertBetween(, , ...)}// SelectAWord selects a word around the current cursor position,// selecting leading or trailing spaces depending on where the cursor// is: if on a blank space, in a word, or at the end of the line.func ( *Selection) () (, int) {if .line.Len() == 0 {return } = .cursor.Pos() := , := .spacesAroundWord() , = .line.SelectWord() .cursor.Set() = .cursor.Pos() := < .line.Len()-1 && isSpace((*.line)[+1])// And only select spaces after it if the word selected is not preceded // by spaces as well, or if we started the selection within this word. , _ = .adjustWordSelection(, , , )if !.Active() || < { .Mark() }return , }// SelectABlankWord selects a bigword around the current cursor position,// selecting leading or trailing spaces depending on where the cursor is:// if on a blank space, in a word, or at the end of the line.func ( *Selection) () (, int) {if .line.Len() == 0 {return } = .cursor.Pos() , := .spacesAroundWord()// If we are out of a word or in the middle of one, find its beginning.if ! && ! { .cursor.Inc() .cursor.Move(.line.Backward(.line.TokenizeSpace, .cursor.Pos())) = .cursor.Pos() } else { .cursor.ToFirstNonSpace(true) }// Then go to the end of the blank word .cursor.Move(.line.ForwardEnd(.line.TokenizeSpace, .cursor.Pos())) := .cursor.Pos() < .line.Len()-1 && isSpace((*.line)[.cursor.Pos()+1])// And only select spaces after it if the word selected is not preceded // by spaces as well, or if we started the selection within this word. , _ = .adjustWordSelection(, , , )if !.Active() || < .cursor.Pos() { .Mark() }return , .cursor.Pos()}// SelectAShellWord selects a shell word around the cursor position,// selecting leading or trailing spaces depending on where the cursor// is: if on a blank space, in a word, or at the end of the line.func ( *Selection) () (, int) {if .line.Len() == 0 {return , } .cursor.CheckCommand() .cursor.ToFirstNonSpace(true) , := .line.SurroundQuotes(true, .cursor.Pos()) , := .line.SurroundQuotes(false, .cursor.Pos()) , := strutil.AdjustSurroundQuotes(, , , )// If none of the quotes matched, use blank wordif == -1 && == -1 { , = .line.SelectBlankWord(.cursor.Pos()) } .cursor.Set()// The quotes might be followed by non-blank characters, // in which case we must keep expanding our selection.for { := > 0 && isSpace((*.line)[-1])if { .cursor.Dec() .cursor.ToFirstNonSpace(false) .cursor.Inc()break } elseif == 0 {break } .cursor.Move(.line.Backward(.line.TokenizeSpace, .cursor.Pos())) = .cursor.Pos() } = .cursor.Pos() .cursor.Set()// Adjust if no spaces after.for { := < .line.Len()-1 && isSpace((*.line)[+1])if || == .line.Len()-1 {break } .cursor.Move(.line.ForwardEnd(.line.TokenizeSpace, )) = .cursor.Pos() }// Else set the region inside those quotesif !.Active() || < .cursor.Pos() { .Mark() }return , }// SelectKeyword attempts to find a pattern in the current blank word// around the current cursor position, using various regular expressions.// Repeatedly calling this function will cycle through all regex matches,// or if a matcher captured multiple subgroups, through each of those groups.//// Those are, in the order in which they are tried:// URI / URL / Domain|IPv4|IPv6 / URL path component / URL parameters.//// The returned positions are the beginning and end positions of the match// on the line (absolute position, not relative to cursor), or if no matcher// succeeds, the bpos and epos parameters are returned unchanged.// If found is true, it means a match occurred, otherwise false is returned.func ( *Selection) (, int, bool) (, int, bool) {if .line.Len() == 0 {return , , false } := (*.line)[:] _, , , = .matchKeyword(, , )if ! {return , , false }// Always check the validity of the selection , , = .checkRange(, )if ! {return , , false }// Mark the selection at its beginning // and move the cursor to its end. .Mark()return , , true}// ReplaceWith replaces all characters of the line within the current// selection range by applying to each rune the provided replacer function.// After replacement, the selection is reset.func ( *Selection) ( func( rune) rune) {if .line.Len() == 0 || .Len() == 0 {return }defer .Reset() , := .Pos()if == -1 || == -1 {return }for := ; < ; ++ { := (*.line)[] = () (*.line)[] = }}// Cut deletes the current selection from the line, updates the cursor position// and returns the deleted content, which can then be passed to the shell registers.// After deletion, the selection is reset.func ( *Selection) () ( string) {if .line.Len() == 0 {return }defer .Reset()switch {caselen(.surrounds) > 0: := 0for , := range .surrounds { .line.CutRune(.bpos - ) ++ }default: , := .Pos()if == -1 || == -1 {return } = .Text() .line.Cut(, ) }return}// Surrounds returns all surround-selected regions contained by the selection.func ( *Selection) () []Selection {return .surrounds}// Highlights returns the highlighting sequences for the selection.func ( *Selection) () (, string) {return .fg, .bg}// HighlightMatchers adds highlighting to matching// parens when the cursor is on one of them.func ( *Selection) { := .cursor.Pos()if .line.Len() == 0 || == .line.Len() {return }ifstrutil.IsBracket(.cursor.Char()) {var , int , , := .line.TokenizeBlock()switch {caselen() == 0:returncase == 0 && len() > : = len([])default: = * -1 } = + .surrounds = append(.surrounds, Selection{Type: "matcher",active: true,visual: true,bpos: ,epos: ,bg: color.Fmt("240"),line: .line,cursor: .cursor, }) }}// ResetMatchers is used by the display engine// to reset matching parens highlighting regions.func ( *Selection) {var []Selectionfor , := range .surrounds {if .Type == "matcher" {continue } = append(, ) } .surrounds = }// Reset makes the current selection inactive, resetting all of its values.func ( *Selection) () { .Type = "" .active = false .visual = false .visualLine = false .bpos = -1 .epos = -1 .kpos = 0 .fg = "" .bg = ""// Get rid of all surround regions but matcher ones. := make([]Selection, 0)for , := range .surrounds {if .Type != "matcher" {continue } = append(, ) } .surrounds = }func ( *Selection) (, int) (int, int, bool) {// Return on some on unfixable cases.switch {case .line.Len() == 0:return -1, -1, falsecase < 0 && < 0:return -1, -1, falsecase > .line.Len() && > .line.Len():return -1, -1, false }// Adjust positive out-of-range valuesif > .line.Len() { = .line.Len() }if > .line.Len() { = .line.Len() }// Adjust negative values when pending.if < 0 { , = , -1 } elseif < 0 { = -1 }// And reorder if inversed.if > && != -1 { , = , }return , , true}func ( *Selection) ( int) (int, int) {varint// The cursor might be now before its original mark, // in which case we invert before doing any move.if .cursor.Pos() < { , = .cursor.Pos(), } else { = .cursor.Pos() }if .visualLine {// To beginning of linefor --; >= 0; -- {if (*.line)[] == '\n' { ++break } }if == -1 { = 0 }// To end of linefor ; < .line.Len(); ++ {if == -1 { = 0 }if (*.line)[] == '\n' {break } } }// Check again in case the visual line inverted both.if > { , = , }return , }func ( *Selection) ( int) (, bool) { = isSpace(.cursor.Char()) = > 0 && isSpace((*.line)[-1])return}func isSpace( rune) bool {returnunicode.IsSpace() && != inputrc.Newline}// adjustWordSelection adjust the beginning and end of a word (blank or not) selection, depending// on whether it's surrounded by spaces, and if selection started from a whitespace or within word.func ( *Selection) (, , bool, int) (int, int) {varintif && ! { .cursor.Inc() .cursor.ToFirstNonSpace(true) .cursor.Dec() } elseif ! { = .cursor.Pos() .cursor.Set( - 1) .cursor.ToFirstNonSpace(false) .cursor.Inc() = .cursor.Pos() .cursor.Set() } = .cursor.Pos()return , }func ( *Selection) ( []rune, int, bool) ( string, bool, , int) { := []string{"URI", } := map[string]*regexp.Regexp{"URI": regexp.MustCompile(`https?:\/\/(?:www\.)?([-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b)*(\/[\/\d\w\.-]*)*(?:[\?])*(.+)*`), }// The matcher name and all the capturing subgroups it found.varstringvar []int// Always preload the current/last target matcher used.if .kpos > 0 && .kpos <= len() { = [.kpos-1] := [] = .runMatcher(, ) }// Either increment/decrement the counter (switch the matcher altogether), // or cycle through the match groups for the current matcher.if , , := .cycleSubgroup(, , ); {return , true, , }// We did not cycle through subgroups, so cycle the matchers. := .kpos .kmpos = 1if { .kpos++ } else { .kpos-- }if .kpos > len() { .kpos = 1 }if .kpos < 1 { .kpos = len() }// Prepare the cycling functions, increments and counters.var ( = .kposfunc( int) boolfunc( int) int )if { = func( int) bool { return <= len() } = func( int) int { return + 1 } } else { = func( int) bool { return > 0 } = func( int) int { return - 1 } }// Try the different matchers until one succeeds, and select the first/last capturing group.for () { = [-1] := [] = .runMatcher(, )iflen() <= 1 { = ()continue }if ! && == 1 { .kmpos = len()return , true, + [len()-2], + [len()-1] }return , true, + [0], + [1] }return"", false, -1, -1}func ( *Selection) ( []int, int, bool) (, int, bool) { := ( && .kmpos < len()-1) || (! && .kmpos > 1)if ! {return }if { .kmpos++ } else { .kmpos-- }return + [.kmpos-1], + [.kmpos], true}func ( *Selection) ( []rune, *regexp.Regexp) ( []int) {if == nil {return } := .FindAllStringSubmatchIndex(string(), -1)iflen() == 0 {return }return [0]}// Tested URL / IP regexp matchers, not working as well as the current ones//// "URL": regexp.MustCompile(`([\w+]+\:\/\/)?([\w\d-]+\.)*[\w-]+[\.\:]\w+([\/\?\=\&\#.]?[\w-]+)*\/?`),// "Domain|IP": regexp.MustCompile(ipv4_regex + `|` + ipv6_regex + `|` + domain_regex),// (http(s?):\/\/)?(((www\.)?+[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+)|(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b))(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?// "Domain|IP": regexp.MustCompile(`((www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+)|(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)`),// "URL path": ,// "URL parameters": regexp.MustCompile(`[(\?|\&)]([^=]+)\=([^&#]+)`),// ipv6Regex = `^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`// ipv4Regex = `^(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4})`// domainRegex = `^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]`
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.