`, styleAttr)
+ }
+
+ return ``
+ },
+ end: func(code bool) string {
+ if code {
+ return ``
+ }
+
+ return ``
+ },
+ }
+ }
+}
+
+// WithPreWrapper allows control of the surrounding pre tags.
+func WithPreWrapper(wrapper PreWrapper) Option {
+ return func(f *Formatter) {
+ f.preWrapper = wrapper
+ }
+}
+
+// WrapLongLines wraps long lines.
+func WrapLongLines(b bool) Option {
+ return func(f *Formatter) {
+ f.wrapLongLines = b
+ }
+}
+
+// WithLineNumbers formats output with line numbers.
+func WithLineNumbers(b bool) Option {
+ return func(f *Formatter) {
+ f.lineNumbers = b
+ }
+}
+
+// LineNumbersInTable will, when combined with WithLineNumbers, separate the line numbers
+// and code in table td's, which make them copy-and-paste friendly.
+func LineNumbersInTable(b bool) Option {
+ return func(f *Formatter) {
+ f.lineNumbersInTable = b
+ }
+}
+
+// WithLinkableLineNumbers decorates the line numbers HTML elements with an "id"
+// attribute so they can be linked.
+func WithLinkableLineNumbers(b bool, prefix string) Option {
+ return func(f *Formatter) {
+ f.linkableLineNumbers = b
+ f.lineNumbersIDPrefix = prefix
+ }
+}
+
+// HighlightLines higlights the given line ranges with the Highlight style.
+//
+// A range is the beginning and ending of a range as 1-based line numbers, inclusive.
+func HighlightLines(ranges [][2]int) Option {
+ return func(f *Formatter) {
+ f.highlightRanges = ranges
+ sort.Sort(f.highlightRanges)
+ }
+}
+
+// BaseLineNumber sets the initial number to start line numbering at. Defaults to 1.
+func BaseLineNumber(n int) Option {
+ return func(f *Formatter) {
+ f.baseLineNumber = n
+ }
+}
+
+// New HTML formatter.
+func New(options ...Option) *Formatter {
+ f := &Formatter{
+ baseLineNumber: 1,
+ preWrapper: defaultPreWrapper,
+ }
+ f.styleCache = newStyleCache(f)
+ for _, option := range options {
+ option(f)
+ }
+ return f
+}
+
+// PreWrapper defines the operations supported in WithPreWrapper.
+type PreWrapper interface {
+ // Start is called to write a start element. + // The code flag tells whether this block surrounds + // highlighted code. This will be false when surrounding + // line numbers. + Start(code bool, styleAttr string) string + + // End is called to write the endelement. + End(code bool) string +} + +type preWrapper struct { + start func(code bool, styleAttr string) string + end func(code bool) string +} + +func (p preWrapper) Start(code bool, styleAttr string) string { + return p.start(code, styleAttr) +} + +func (p preWrapper) End(code bool) string { + return p.end(code) +} + +var ( + nopPreWrapper = preWrapper{ + start: func(code bool, styleAttr string) string { return "" }, + end: func(code bool) string { return "" }, + } + defaultPreWrapper = preWrapper{ + start: func(code bool, styleAttr string) string { + if code { + return fmt.Sprintf(`
`, styleAttr)
+ }
+
+ return fmt.Sprintf(``, styleAttr)
+ },
+ end: func(code bool) string {
+ if code {
+ return ``
+ }
+
+ return ``
+ },
+ }
+)
+
+// Formatter that generates HTML.
+type Formatter struct {
+ styleCache *styleCache
+ standalone bool
+ prefix string
+ Classes bool // Exported field to detect when classes are being used
+ allClasses bool
+ customCSS map[chroma.TokenType]string
+ preWrapper PreWrapper
+ inlineCode bool
+ preventSurroundingPre bool
+ tabWidth int
+ wrapLongLines bool
+ lineNumbers bool
+ lineNumbersInTable bool
+ linkableLineNumbers bool
+ lineNumbersIDPrefix string
+ highlightRanges highlightRanges
+ baseLineNumber int
+}
+
+type highlightRanges [][2]int
+
+func (h highlightRanges) Len() int { return len(h) }
+func (h highlightRanges) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
+func (h highlightRanges) Less(i, j int) bool { return h[i][0] < h[j][0] }
+
+func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) (err error) {
+ return f.writeHTML(w, style, iterator.Tokens())
+}
+
+// We deliberately don't use html/template here because it is two orders of magnitude slower (benchmarked).
+//
+// OTOH we need to be super careful about correct escaping...
+func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.Token) (err error) { // nolint: gocyclo
+ css := f.styleCache.get(style, true)
+ if f.standalone {
+ fmt.Fprint(w, "\n")
+ if f.Classes {
+ fmt.Fprint(w, "")
+ }
+ fmt.Fprintf(w, "\n", f.styleAttr(css, chroma.Background))
+ }
+
+ wrapInTable := f.lineNumbers && f.lineNumbersInTable
+
+ lines := chroma.SplitTokensIntoLines(tokens)
+ lineDigits := len(strconv.Itoa(f.baseLineNumber + len(lines) - 1))
+ highlightIndex := 0
+
+ if wrapInTable {
+ // List line numbers in its own | \n", f.styleAttr(css, chroma.LineTableTD)) + fmt.Fprintf(w, "%s", f.preWrapper.Start(false, f.styleAttr(css, chroma.PreWrapper))) + for index := range lines { + line := f.baseLineNumber + index + highlight, next := f.shouldHighlight(highlightIndex, line) + if next { + highlightIndex++ + } + if highlight { + fmt.Fprintf(w, "", f.styleAttr(css, chroma.LineHighlight)) + } + + fmt.Fprintf(w, "%s\n", f.styleAttr(css, chroma.LineNumbersTable), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(css, lineDigits, line)) + + if highlight { + fmt.Fprintf(w, "") + } + } + fmt.Fprint(w, f.preWrapper.End(false)) + fmt.Fprint(w, " | \n") + fmt.Fprintf(w, "\n", f.styleAttr(css, chroma.LineTableTD, "width:100%")) + } + + fmt.Fprintf(w, "%s", f.preWrapper.Start(true, f.styleAttr(css, chroma.PreWrapper))) + + highlightIndex = 0 + for index, tokens := range lines { + // 1-based line number. + line := f.baseLineNumber + index + highlight, next := f.shouldHighlight(highlightIndex, line) + if next { + highlightIndex++ + } + + if !(f.preventSurroundingPre || f.inlineCode) { + // Start of Line + fmt.Fprint(w, ``) + } else { + fmt.Fprintf(w, "%s>", f.styleAttr(css, chroma.Line)) + } + + // Line number + if f.lineNumbers && !wrapInTable { + fmt.Fprintf(w, "%s", f.styleAttr(css, chroma.LineNumbers), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(css, lineDigits, line)) + } + + fmt.Fprintf(w, ``, f.styleAttr(css, chroma.CodeLine)) + } + + for _, token := range tokens { + html := html.EscapeString(token.String()) + attr := f.styleAttr(css, token.Type) + if attr != "" { + html = fmt.Sprintf("%s", attr, html) + } + fmt.Fprint(w, html) + } + + if !(f.preventSurroundingPre || f.inlineCode) { + fmt.Fprint(w, ``) // End of CodeLine + + fmt.Fprint(w, ``) // End of Line + } + } + fmt.Fprintf(w, "%s", f.preWrapper.End(true)) + + if wrapInTable { + fmt.Fprint(w, " |
, B, ...
+ {
+ `(?[CBIUDTKRPAELZVMSXN])(?<+|«)`,
+ ByGroups(Keyword, Punctuation),
+ findBrackets(rakuPodFormatter),
+ },
+ },
+ "pod-formatter": {
+ // Placeholder rule, will be replaced by mutators. DO NOT REMOVE!
+ {`>`, Punctuation, Pop(1)},
+ Include("pre-pod-formatter"),
+ // Placeholder rule, will be replaced by mutators. DO NOT REMOVE!
+ {`.+?`, StringOther, nil},
+ },
+ "variable": {
+ {variablePattern, NameVariable, Push("name-adverb")},
+ {globalVariablePattern, NameVariableGlobal, Push("name-adverb")},
+ {`[$@]<[^>]+>`, NameVariable, nil},
+ {`\$[/!¢]`, NameVariable, nil},
+ {`[$@%]`, NameVariable, nil},
+ },
+ "single-quote": {
+ {`(?>(?!\s*(?:\d+|\.(?:Int|Numeric)|[$@%]\*?[\w':-]+|\s+\[))`, Punctuation, Pop(1)},
+ Include("ww"),
+ },
+ "«": {
+ {`»(?!\s*(?:\d+|\.(?:Int|Numeric)|[$@%]\*?[\w':-]+|\s+\[))`, Punctuation, Pop(1)},
+ Include("ww"),
+ },
+ "ww": {
+ Include("single-quote"),
+ Include("qq"),
+ },
+ "qq": {
+ Include("qq-variable"),
+ Include("closure"),
+ Include(`escape-char`),
+ Include("escape-hexadecimal"),
+ Include("escape-c-name"),
+ Include("escape-qq"),
+ {`.+?`, StringDouble, nil},
+ },
+ "qq-variable": {
+ {
+ `(?\.)(?` + namePattern + `)` + colonPairLookahead + `\()`,
+ ByGroupNames(map[string]Emitter{
+ `operator`: Operator,
+ `method_name`: NameFunction,
+ }),
+ Push(`name-adverb`),
+ },
+ // Function/Signature
+ {
+ `\(`, Punctuation, replaceRule(
+ ruleReplacingConfig{
+ delimiter: []rune(`)`),
+ tokenType: Punctuation,
+ stateName: `root`,
+ pushState: true,
+ }),
+ },
+ Default(Pop(1)),
+ },
+ "Q": {
+ Include("escape-qq"),
+ {`.+?`, String, nil},
+ },
+ "Q-closure": {
+ Include("escape-qq"),
+ Include("closure"),
+ {`.+?`, String, nil},
+ },
+ "Q-variable": {
+ Include("escape-qq"),
+ Include("qq-variable"),
+ {`.+?`, String, nil},
+ },
+ "closure": {
+ {`(? -1 {
+ idx = utf8.RuneCountInString(text[:idx])
+
+ // Search again if the substr is escaped with backslash
+ if (idx > 1 && strFromPos[idx-1] == '\\' && strFromPos[idx-2] != '\\') ||
+ (idx == 1 && strFromPos[idx-1] == '\\') {
+ idx = indexAt(str[pos:], substr, idx+1)
+
+ idx = utf8.RuneCountInString(text[:idx])
+
+ if idx < 0 {
+ return idx
+ }
+ }
+ idx += pos
+ }
+
+ return idx
+}
+
+// Tells if an array of string contains a string
+func contains(s []string, e string) bool {
+ for _, value := range s {
+ if value == e {
+ return true
+ }
+ }
+ return false
+}
+
+type rulePosition int
+
+const (
+ topRule rulePosition = 0
+ bottomRule = -1
+)
+
+type ruleMakingConfig struct {
+ delimiter []rune
+ pattern string
+ tokenType Emitter
+ mutator Mutator
+ numberOfDelimiterChars int
+}
+
+type ruleReplacingConfig struct {
+ delimiter []rune
+ pattern string
+ tokenType Emitter
+ numberOfDelimiterChars int
+ mutator Mutator
+ appendMutator Mutator
+ rulePosition rulePosition
+ stateName string
+ pop bool
+ popState bool
+ pushState bool
+}
+
+// Pops rule from state-stack and replaces the rule with the previous rule
+func popRule(rule ruleReplacingConfig) MutatorFunc {
+ return func(state *LexerState) error {
+ stackName := genStackName(rule.stateName, rule.rulePosition)
+
+ stack, ok := state.Get(stackName).([]ruleReplacingConfig)
+
+ if ok && len(stack) > 0 {
+ // Pop from stack
+ stack = stack[:len(stack)-1]
+ lastRule := stack[len(stack)-1]
+ lastRule.pushState = false
+ lastRule.popState = false
+ lastRule.pop = true
+ state.Set(stackName, stack)
+
+ // Call replaceRule to use the last rule
+ err := replaceRule(lastRule)(state)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ return nil
+ }
+}
+
+// Replaces a state's rule based on the rule config and position
+func replaceRule(rule ruleReplacingConfig) MutatorFunc {
+ return func(state *LexerState) error {
+ stateName := rule.stateName
+ stackName := genStackName(rule.stateName, rule.rulePosition)
+
+ stack, ok := state.Get(stackName).([]ruleReplacingConfig)
+ if !ok {
+ stack = []ruleReplacingConfig{}
+ }
+
+ // If state-stack is empty fill it with the placeholder rule
+ if len(stack) == 0 {
+ stack = []ruleReplacingConfig{
+ {
+ // Placeholder, will be overwritten by mutators, DO NOT REMOVE!
+ pattern: `\A\z`,
+ tokenType: nil,
+ mutator: nil,
+ stateName: stateName,
+ rulePosition: rule.rulePosition,
+ },
+ }
+ state.Set(stackName, stack)
+ }
+
+ var mutator Mutator
+ mutators := []Mutator{}
+
+ switch {
+ case rule.rulePosition == topRule && rule.mutator == nil:
+ // Default mutator for top rule
+ mutators = []Mutator{Pop(1), popRule(rule)}
+ case rule.rulePosition == topRule && rule.mutator != nil:
+ // Default mutator for top rule, when rule.mutator is set
+ mutators = []Mutator{rule.mutator, popRule(rule)}
+ case rule.mutator != nil:
+ mutators = []Mutator{rule.mutator}
+ }
+
+ if rule.appendMutator != nil {
+ mutators = append(mutators, rule.appendMutator)
+ }
+
+ if len(mutators) > 0 {
+ mutator = Mutators(mutators...)
+ } else {
+ mutator = nil
+ }
+
+ ruleConfig := ruleMakingConfig{
+ pattern: rule.pattern,
+ delimiter: rule.delimiter,
+ numberOfDelimiterChars: rule.numberOfDelimiterChars,
+ tokenType: rule.tokenType,
+ mutator: mutator,
+ }
+
+ cRule := makeRule(ruleConfig)
+
+ switch rule.rulePosition {
+ case topRule:
+ state.Rules[stateName][0] = cRule
+ case bottomRule:
+ state.Rules[stateName][len(state.Rules[stateName])-1] = cRule
+ }
+
+ // Pop state name from stack if asked. State should be popped first before Pushing
+ if rule.popState {
+ err := Pop(1).Mutate(state)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ // Push state name to stack if asked
+ if rule.pushState {
+ err := Push(stateName).Mutate(state)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ if !rule.pop {
+ state.Set(stackName, append(stack, rule))
+ }
+
+ return nil
+ }
+}
+
+// Generates rule replacing stack using state name and rule position
+func genStackName(stateName string, rulePosition rulePosition) (stackName string) {
+ switch rulePosition {
+ case topRule:
+ stackName = stateName + `-top-stack`
+ case bottomRule:
+ stackName = stateName + `-bottom-stack`
+ }
+ return
+}
+
+// Makes a compiled rule and returns it
+func makeRule(config ruleMakingConfig) *CompiledRule {
+ var rePattern string
+
+ if len(config.delimiter) > 0 {
+ delimiter := string(config.delimiter)
+
+ if config.numberOfDelimiterChars > 1 {
+ delimiter = strings.Repeat(delimiter, config.numberOfDelimiterChars)
+ }
+
+ rePattern = `(? 1 {
+ lang = langMatch[1]
+ }
+
+ // Tokenise code based on lang property
+ sublexer := Get(lang)
+ if sublexer != nil {
+ iterator, err := sublexer.Tokenise(nil, state.NamedGroups[`value`])
+
+ if err != nil {
+ panic(err)
+ } else {
+ iterators = append(iterators, iterator)
+ }
+ } else {
+ iterators = append(iterators, Literator(tokens[4]))
+ }
+
+ // Append the rest of the tokens
+ iterators = append(iterators, Literator(tokens[5:]...))
+
+ return Concaterator(iterators...)
+}
diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/rst.go b/vendor/github.com/alecthomas/chroma/v2/lexers/rst.go
new file mode 100644
index 00000000..66ec03cd
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/lexers/rst.go
@@ -0,0 +1,89 @@
+package lexers
+
+import (
+ "strings"
+
+ . "github.com/alecthomas/chroma/v2" // nolint
+)
+
+// Restructuredtext lexer.
+var Restructuredtext = Register(MustNewLexer(
+ &Config{
+ Name: "reStructuredText",
+ Aliases: []string{"rst", "rest", "restructuredtext"},
+ Filenames: []string{"*.rst", "*.rest"},
+ MimeTypes: []string{"text/x-rst", "text/prs.fallenstein.rst"},
+ },
+ restructuredtextRules,
+))
+
+func restructuredtextRules() Rules {
+ return Rules{
+ "root": {
+ {"^(=+|-+|`+|:+|\\.+|\\'+|\"+|~+|\\^+|_+|\\*+|\\++|#+)([ \\t]*\\n)(.+)(\\n)(\\1)(\\n)", ByGroups(GenericHeading, Text, GenericHeading, Text, GenericHeading, Text), nil},
+ {"^(\\S.*)(\\n)(={3,}|-{3,}|`{3,}|:{3,}|\\.{3,}|\\'{3,}|\"{3,}|~{3,}|\\^{3,}|_{3,}|\\*{3,}|\\+{3,}|#{3,})(\\n)", ByGroups(GenericHeading, Text, GenericHeading, Text), nil},
+ {`^(\s*)([-*+])( .+\n(?:\1 .+\n)*)`, ByGroups(Text, LiteralNumber, UsingSelf("inline")), nil},
+ {`^(\s*)([0-9#ivxlcmIVXLCM]+\.)( .+\n(?:\1 .+\n)*)`, ByGroups(Text, LiteralNumber, UsingSelf("inline")), nil},
+ {`^(\s*)(\(?[0-9#ivxlcmIVXLCM]+\))( .+\n(?:\1 .+\n)*)`, ByGroups(Text, LiteralNumber, UsingSelf("inline")), nil},
+ {`^(\s*)([A-Z]+\.)( .+\n(?:\1 .+\n)+)`, ByGroups(Text, LiteralNumber, UsingSelf("inline")), nil},
+ {`^(\s*)(\(?[A-Za-z]+\))( .+\n(?:\1 .+\n)+)`, ByGroups(Text, LiteralNumber, UsingSelf("inline")), nil},
+ {`^(\s*)(\|)( .+\n(?:\| .+\n)*)`, ByGroups(Text, Operator, UsingSelf("inline")), nil},
+ {`^( *\.\.)(\s*)((?:source)?code(?:-block)?)(::)([ \t]*)([^\n]+)(\n[ \t]*\n)([ \t]+)(.*)(\n)((?:(?:\8.*|)\n)+)`, EmitterFunc(rstCodeBlock), nil},
+ {`^( *\.\.)(\s*)([\w:-]+?)(::)(?:([ \t]*)(.*))`, ByGroups(Punctuation, Text, OperatorWord, Punctuation, Text, UsingSelf("inline")), nil},
+ {`^( *\.\.)(\s*)(_(?:[^:\\]|\\.)+:)(.*?)$`, ByGroups(Punctuation, Text, NameTag, UsingSelf("inline")), nil},
+ {`^( *\.\.)(\s*)(\[.+\])(.*?)$`, ByGroups(Punctuation, Text, NameTag, UsingSelf("inline")), nil},
+ {`^( *\.\.)(\s*)(\|.+\|)(\s*)([\w:-]+?)(::)(?:([ \t]*)(.*))`, ByGroups(Punctuation, Text, NameTag, Text, OperatorWord, Punctuation, Text, UsingSelf("inline")), nil},
+ {`^ *\.\..*(\n( +.*\n|\n)+)?`, CommentPreproc, nil},
+ {`^( *)(:[a-zA-Z-]+:)(\s*)$`, ByGroups(Text, NameClass, Text), nil},
+ {`^( *)(:.*?:)([ \t]+)(.*?)$`, ByGroups(Text, NameClass, Text, NameFunction), nil},
+ {`^(\S.*(?)(`__?)", ByGroups(LiteralString, LiteralStringInterpol, LiteralString), nil},
+ {"`.+?`__?", LiteralString, nil},
+ {"(`.+?`)(:[a-zA-Z0-9:-]+?:)?", ByGroups(NameVariable, NameAttribute), nil},
+ {"(:[a-zA-Z0-9:-]+?:)(`.+?`)", ByGroups(NameAttribute, NameVariable), nil},
+ {`\*\*.+?\*\*`, GenericStrong, nil},
+ {`\*.+?\*`, GenericEmph, nil},
+ {`\[.*?\]_`, LiteralString, nil},
+ {`<.+?>`, NameTag, nil},
+ {"[^\\\\\\n\\[*`:]+", Text, nil},
+ {`.`, Text, nil},
+ },
+ "literal": {
+ {"[^`]+", LiteralString, nil},
+ {"``((?=$)|(?=[-/:.,; \\n\\x00\\\u2010\\\u2011\\\u2012\\\u2013\\\u2014\\\u00a0\\'\\\"\\)\\]\\}\\>\\\u2019\\\u201d\\\u00bb\\!\\?]))", LiteralString, Pop(1)},
+ {"`", LiteralString, nil},
+ },
+ }
+}
+
+func rstCodeBlock(groups []string, state *LexerState) Iterator {
+ iterators := []Iterator{}
+ tokens := []Token{
+ {Punctuation, groups[1]},
+ {Text, groups[2]},
+ {OperatorWord, groups[3]},
+ {Punctuation, groups[4]},
+ {Text, groups[5]},
+ {Keyword, groups[6]},
+ {Text, groups[7]},
+ }
+ code := strings.Join(groups[8:], "")
+ lexer := Get(groups[6])
+ if lexer == nil {
+ tokens = append(tokens, Token{String, code})
+ iterators = append(iterators, Literator(tokens...))
+ } else {
+ sub, err := lexer.Tokenise(nil, code)
+ if err != nil {
+ panic(err)
+ }
+ iterators = append(iterators, Literator(tokens...), sub)
+ }
+ return Concaterator(iterators...)
+}
diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/svelte.go b/vendor/github.com/alecthomas/chroma/v2/lexers/svelte.go
new file mode 100644
index 00000000..39211c4f
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/lexers/svelte.go
@@ -0,0 +1,70 @@
+package lexers
+
+import (
+ . "github.com/alecthomas/chroma/v2" // nolint
+)
+
+// Svelte lexer.
+var Svelte = Register(DelegatingLexer(HTML, MustNewLexer(
+ &Config{
+ Name: "Svelte",
+ Aliases: []string{"svelte"},
+ Filenames: []string{"*.svelte"},
+ MimeTypes: []string{"application/x-svelte"},
+ DotAll: true,
+ },
+ svelteRules,
+)))
+
+func svelteRules() Rules {
+ return Rules{
+ "root": {
+ // Let HTML handle the comments, including comments containing script and style tags
+ {``, Other, Pop(1)},
+ {`.+?`, Other, nil},
+ },
+ "templates": {
+ {`}`, Punctuation, Pop(1)},
+ // Let TypeScript handle strings and the curly braces inside them
+ {`(?]*>`, Using("TypoScriptHTMLData"), nil},
+ {`&[^;\n]*;`, LiteralString, nil},
+ {`(_CSS_DEFAULT_STYLE)(\s*)(\()(?s)(.*(?=\n\)))`, ByGroups(NameClass, Text, LiteralStringSymbol, Using("TypoScriptCSSData")), nil},
+ },
+ "literal": {
+ {`0x[0-9A-Fa-f]+t?`, LiteralNumberHex, nil},
+ {`[0-9]+`, LiteralNumberInteger, nil},
+ {`(###\w+###)`, NameConstant, nil},
+ },
+ "label": {
+ {`(EXT|FILE|LLL):[^}\n"]*`, LiteralString, nil},
+ {`(?![^\w\-])([\w\-]+(?:/[\w\-]+)+/?)(\S*\n)`, ByGroups(LiteralString, LiteralString), nil},
+ },
+ "punctuation": {
+ {`[,.]`, Punctuation, nil},
+ },
+ "operator": {
+ {`[<>,:=.*%+|]`, Operator, nil},
+ },
+ "structure": {
+ {`[{}()\[\]\\]`, LiteralStringSymbol, nil},
+ },
+ "constant": {
+ {`(\{)(\$)((?:[\w\-]+\.)*)([\w\-]+)(\})`, ByGroups(LiteralStringSymbol, Operator, NameConstant, NameConstant, LiteralStringSymbol), nil},
+ {`(\{)([\w\-]+)(\s*:\s*)([\w\-]+)(\})`, ByGroups(LiteralStringSymbol, NameConstant, Operator, NameConstant, LiteralStringSymbol), nil},
+ {`(#[a-fA-F0-9]{6}\b|#[a-fA-F0-9]{3}\b)`, LiteralStringChar, nil},
+ },
+ "comment": {
+ {`(? lexers.txt
+
+kotlin:
+ invalid unicode escape sequences
+ FIXED: Have to disable wide Unicode characters in unistring.py
+
+pygments.lexers.ambient.AmbientTalkLexer
+pygments.lexers.ampl.AmplLexer
+pygments.lexers.actionscript.ActionScriptLexer
+pygments.lexers.actionscript.ActionScript3Lexer
+pygments.lexers.actionscript.MxmlLexer
+pygments.lexers.algebra.GAPLexer
+pygments.lexers.algebra.MathematicaLexer
+pygments.lexers.algebra.MuPADLexer
+pygments.lexers.algebra.BCLexer
+pygments.lexers.apl.APLLexer
+pygments.lexers.bibtex.BibTeXLexer
+pygments.lexers.bibtex.BSTLexer
+pygments.lexers.basic.BlitzMaxLexer
+pygments.lexers.basic.BlitzBasicLexer
+pygments.lexers.basic.MonkeyLexer
+pygments.lexers.basic.CbmBasicV2Lexer
+pygments.lexers.basic.QBasicLexer
+pygments.lexers.automation.AutohotkeyLexer
+pygments.lexers.automation.AutoItLexer
+pygments.lexers.archetype.AtomsLexer
+pygments.lexers.c_like.ClayLexer
+pygments.lexers.c_like.ValaLexer
+pygments.lexers.asm.GasLexer
+pygments.lexers.asm.ObjdumpLexer
+pygments.lexers.asm.HsailLexer
+pygments.lexers.asm.LlvmLexer
+pygments.lexers.asm.NasmLexer
+pygments.lexers.asm.TasmLexer
+pygments.lexers.asm.Ca65Lexer
+pygments.lexers.business.CobolLexer
+pygments.lexers.business.ABAPLexer
+pygments.lexers.business.OpenEdgeLexer
+pygments.lexers.business.GoodDataCLLexer
+pygments.lexers.business.MaqlLexer
+pygments.lexers.capnproto.CapnProtoLexer
+pygments.lexers.chapel.ChapelLexer
+pygments.lexers.clean.CleanLexer
+pygments.lexers.c_cpp.CFamilyLexer
+pygments.lexers.console.VCTreeStatusLexer
+pygments.lexers.console.PyPyLogLexer
+pygments.lexers.csound.CsoundLexer
+pygments.lexers.csound.CsoundDocumentLexer
+pygments.lexers.csound.CsoundDocumentLexer
+pygments.lexers.crystal.CrystalLexer
+pygments.lexers.dalvik.SmaliLexer
+pygments.lexers.css.CssLexer
+pygments.lexers.css.SassLexer
+pygments.lexers.css.ScssLexer
+pygments.lexers.configs.IniLexer
+pygments.lexers.configs.RegeditLexer
+pygments.lexers.configs.PropertiesLexer
+pygments.lexers.configs.KconfigLexer
+pygments.lexers.configs.Cfengine3Lexer
+pygments.lexers.configs.ApacheConfLexer
+pygments.lexers.configs.SquidConfLexer
+pygments.lexers.configs.NginxConfLexer
+pygments.lexers.configs.LighttpdConfLexer
+pygments.lexers.configs.DockerLexer
+pygments.lexers.configs.TerraformLexer
+pygments.lexers.configs.TermcapLexer
+pygments.lexers.configs.TerminfoLexer
+pygments.lexers.configs.PkgConfigLexer
+pygments.lexers.configs.PacmanConfLexer
+pygments.lexers.data.YamlLexer
+pygments.lexers.data.JsonLexer
+pygments.lexers.diff.DiffLexer
+pygments.lexers.diff.DarcsPatchLexer
+pygments.lexers.diff.WDiffLexer
+pygments.lexers.dotnet.CSharpLexer
+pygments.lexers.dotnet.NemerleLexer
+pygments.lexers.dotnet.BooLexer
+pygments.lexers.dotnet.VbNetLexer
+pygments.lexers.dotnet.GenericAspxLexer
+pygments.lexers.dotnet.FSharpLexer
+pygments.lexers.dylan.DylanLexer
+pygments.lexers.dylan.DylanLidLexer
+pygments.lexers.ecl.ECLLexer
+pygments.lexers.eiffel.EiffelLexer
+pygments.lexers.dsls.ProtoBufLexer
+pygments.lexers.dsls.ThriftLexer
+pygments.lexers.dsls.BroLexer
+pygments.lexers.dsls.PuppetLexer
+pygments.lexers.dsls.RslLexer
+pygments.lexers.dsls.MscgenLexer
+pygments.lexers.dsls.VGLLexer
+pygments.lexers.dsls.AlloyLexer
+pygments.lexers.dsls.PanLexer
+pygments.lexers.dsls.CrmshLexer
+pygments.lexers.dsls.FlatlineLexer
+pygments.lexers.dsls.SnowballLexer
+pygments.lexers.elm.ElmLexer
+pygments.lexers.erlang.ErlangLexer
+pygments.lexers.erlang.ElixirLexer
+pygments.lexers.ezhil.EzhilLexer
+pygments.lexers.esoteric.BrainfuckLexer
+pygments.lexers.esoteric.BefungeLexer
+pygments.lexers.esoteric.CAmkESLexer
+pygments.lexers.esoteric.CapDLLexer
+pygments.lexers.esoteric.RedcodeLexer
+pygments.lexers.esoteric.AheuiLexer
+pygments.lexers.factor.FactorLexer
+pygments.lexers.fantom.FantomLexer
+pygments.lexers.felix.FelixLexer
+pygments.lexers.forth.ForthLexer
+pygments.lexers.fortran.FortranLexer
+pygments.lexers.fortran.FortranFixedLexer
+pygments.lexers.go.GoLexer
+pygments.lexers.foxpro.FoxProLexer
+pygments.lexers.graph.CypherLexer
+pygments.lexers.grammar_notation.BnfLexer
+pygments.lexers.grammar_notation.AbnfLexer
+pygments.lexers.grammar_notation.JsgfLexer
+pygments.lexers.graphics.GLShaderLexer
+pygments.lexers.graphics.PostScriptLexer
+pygments.lexers.graphics.AsymptoteLexer
+pygments.lexers.graphics.GnuplotLexer
+pygments.lexers.graphics.PovrayLexer
+pygments.lexers.hexdump.HexdumpLexer
+pygments.lexers.haskell.HaskellLexer
+pygments.lexers.haskell.IdrisLexer
+pygments.lexers.haskell.AgdaLexer
+pygments.lexers.haskell.CryptolLexer
+pygments.lexers.haskell.KokaLexer
+pygments.lexers.haxe.HaxeLexer
+pygments.lexers.haxe.HxmlLexer
+pygments.lexers.hdl.VerilogLexer
+pygments.lexers.hdl.SystemVerilogLexer
+pygments.lexers.hdl.VhdlLexer
+pygments.lexers.idl.IDLLexer
+pygments.lexers.inferno.LimboLexer
+pygments.lexers.igor.IgorLexer
+pygments.lexers.html.HtmlLexer
+pygments.lexers.html.DtdLexer
+pygments.lexers.html.XmlLexer
+pygments.lexers.html.HamlLexer
+pygments.lexers.html.ScamlLexer
+pygments.lexers.html.PugLexer
+pygments.lexers.installers.NSISLexer
+pygments.lexers.installers.RPMSpecLexer
+pygments.lexers.installers.SourcesListLexer
+pygments.lexers.installers.DebianControlLexer
+pygments.lexers.iolang.IoLexer
+pygments.lexers.julia.JuliaLexer
+pygments.lexers.int_fiction.Inform6Lexer
+pygments.lexers.int_fiction.Inform7Lexer
+pygments.lexers.int_fiction.Tads3Lexer
+pygments.lexers.make.BaseMakefileLexer
+pygments.lexers.make.CMakeLexer
+pygments.lexers.javascript.JavascriptLexer
+pygments.lexers.javascript.KalLexer
+pygments.lexers.javascript.LiveScriptLexer
+pygments.lexers.javascript.DartLexer
+pygments.lexers.javascript.TypeScriptLexer
+pygments.lexers.javascript.LassoLexer
+pygments.lexers.javascript.ObjectiveJLexer
+pygments.lexers.javascript.CoffeeScriptLexer
+pygments.lexers.javascript.MaskLexer
+pygments.lexers.javascript.EarlGreyLexer
+pygments.lexers.javascript.JuttleLexer
+pygments.lexers.jvm.JavaLexer
+pygments.lexers.jvm.ScalaLexer
+pygments.lexers.jvm.GosuLexer
+pygments.lexers.jvm.GroovyLexer
+pygments.lexers.jvm.IokeLexer
+pygments.lexers.jvm.ClojureLexer
+pygments.lexers.jvm.TeaLangLexer
+pygments.lexers.jvm.CeylonLexer
+pygments.lexers.jvm.KotlinLexer
+pygments.lexers.jvm.XtendLexer
+pygments.lexers.jvm.PigLexer
+pygments.lexers.jvm.GoloLexer
+pygments.lexers.jvm.JasminLexer
+pygments.lexers.markup.BBCodeLexer
+pygments.lexers.markup.MoinWikiLexer
+pygments.lexers.markup.RstLexer
+pygments.lexers.markup.TexLexer
+pygments.lexers.markup.GroffLexer
+pygments.lexers.markup.MozPreprocHashLexer
+pygments.lexers.markup.MarkdownLexer
+pygments.lexers.ml.SMLLexer
+pygments.lexers.ml.OcamlLexer
+pygments.lexers.ml.OpaLexer
+pygments.lexers.modeling.ModelicaLexer
+pygments.lexers.modeling.BugsLexer
+pygments.lexers.modeling.JagsLexer
+pygments.lexers.modeling.StanLexer
+pygments.lexers.matlab.MatlabLexer
+pygments.lexers.matlab.OctaveLexer
+pygments.lexers.matlab.ScilabLexer
+pygments.lexers.monte.MonteLexer
+pygments.lexers.lisp.SchemeLexer
+pygments.lexers.lisp.CommonLispLexer
+pygments.lexers.lisp.HyLexer
+pygments.lexers.lisp.RacketLexer
+pygments.lexers.lisp.NewLispLexer
+pygments.lexers.lisp.EmacsLispLexer
+pygments.lexers.lisp.ShenLexer
+pygments.lexers.lisp.XtlangLexer
+pygments.lexers.modula2.Modula2Lexer
+pygments.lexers.ncl.NCLLexer
+pygments.lexers.nim.NimLexer
+pygments.lexers.nit.NitLexer
+pygments.lexers.nix.NixLexer
+pygments.lexers.oberon.ComponentPascalLexer
+pygments.lexers.ooc.OocLexer
+pygments.lexers.objective.SwiftLexer
+pygments.lexers.parasail.ParaSailLexer
+pygments.lexers.pawn.SourcePawnLexer
+pygments.lexers.pawn.PawnLexer
+pygments.lexers.pascal.AdaLexer
+pygments.lexers.parsers.RagelLexer
+pygments.lexers.parsers.RagelEmbeddedLexer
+pygments.lexers.parsers.AntlrLexer
+pygments.lexers.parsers.TreetopBaseLexer
+pygments.lexers.parsers.EbnfLexer
+pygments.lexers.php.ZephirLexer
+pygments.lexers.php.PhpLexer
+pygments.lexers.perl.PerlLexer
+pygments.lexers.perl.Perl6Lexer
+pygments.lexers.praat.PraatLexer
+pygments.lexers.prolog.PrologLexer
+pygments.lexers.prolog.LogtalkLexer
+pygments.lexers.qvt.QVToLexer
+pygments.lexers.rdf.SparqlLexer
+pygments.lexers.rdf.TurtleLexer
+pygments.lexers.python.PythonLexer
+pygments.lexers.python.Python3Lexer
+pygments.lexers.python.PythonTracebackLexer
+pygments.lexers.python.Python3TracebackLexer
+pygments.lexers.python.CythonLexer
+pygments.lexers.python.DgLexer
+pygments.lexers.rebol.RebolLexer
+pygments.lexers.rebol.RedLexer
+pygments.lexers.resource.ResourceLexer
+pygments.lexers.rnc.RNCCompactLexer
+pygments.lexers.roboconf.RoboconfGraphLexer
+pygments.lexers.roboconf.RoboconfInstancesLexer
+pygments.lexers.rust.RustLexer
+pygments.lexers.ruby.RubyLexer
+pygments.lexers.ruby.FancyLexer
+pygments.lexers.sas.SASLexer
+pygments.lexers.smalltalk.SmalltalkLexer
+pygments.lexers.smalltalk.NewspeakLexer
+pygments.lexers.smv.NuSMVLexer
+pygments.lexers.shell.BashLexer
+pygments.lexers.shell.BatchLexer
+pygments.lexers.shell.TcshLexer
+pygments.lexers.shell.PowerShellLexer
+pygments.lexers.shell.FishShellLexer
+pygments.lexers.snobol.SnobolLexer
+pygments.lexers.scripting.LuaLexer
+pygments.lexers.scripting.ChaiscriptLexer
+pygments.lexers.scripting.LSLLexer
+pygments.lexers.scripting.AppleScriptLexer
+pygments.lexers.scripting.RexxLexer
+pygments.lexers.scripting.MOOCodeLexer
+pygments.lexers.scripting.HybrisLexer
+pygments.lexers.scripting.EasytrieveLexer
+pygments.lexers.scripting.JclLexer
+pygments.lexers.supercollider.SuperColliderLexer
+pygments.lexers.stata.StataLexer
+pygments.lexers.tcl.TclLexer
+pygments.lexers.sql.PostgresLexer
+pygments.lexers.sql.PlPgsqlLexer
+pygments.lexers.sql.PsqlRegexLexer
+pygments.lexers.sql.SqlLexer
+pygments.lexers.sql.TransactSqlLexer
+pygments.lexers.sql.MySqlLexer
+pygments.lexers.sql.RqlLexer
+pygments.lexers.testing.GherkinLexer
+pygments.lexers.testing.TAPLexer
+pygments.lexers.textedit.AwkLexer
+pygments.lexers.textedit.VimLexer
+pygments.lexers.textfmts.IrcLogsLexer
+pygments.lexers.textfmts.GettextLexer
+pygments.lexers.textfmts.HttpLexer
+pygments.lexers.textfmts.TodotxtLexer
+pygments.lexers.trafficscript.RtsLexer
+pygments.lexers.theorem.CoqLexer
+pygments.lexers.theorem.IsabelleLexer
+pygments.lexers.theorem.LeanLexer
+pygments.lexers.templates.SmartyLexer
+pygments.lexers.templates.VelocityLexer
+pygments.lexers.templates.DjangoLexer
+pygments.lexers.templates.MyghtyLexer
+pygments.lexers.templates.MasonLexer
+pygments.lexers.templates.MakoLexer
+pygments.lexers.templates.CheetahLexer
+pygments.lexers.templates.GenshiTextLexer
+pygments.lexers.templates.GenshiMarkupLexer
+pygments.lexers.templates.JspRootLexer
+pygments.lexers.templates.EvoqueLexer
+pygments.lexers.templates.ColdfusionLexer
+pygments.lexers.templates.ColdfusionMarkupLexer
+pygments.lexers.templates.TeaTemplateRootLexer
+pygments.lexers.templates.HandlebarsLexer
+pygments.lexers.templates.LiquidLexer
+pygments.lexers.templates.TwigLexer
+pygments.lexers.templates.Angular2Lexer
+pygments.lexers.urbi.UrbiscriptLexer
+pygments.lexers.typoscript.TypoScriptCssDataLexer
+pygments.lexers.typoscript.TypoScriptHtmlDataLexer
+pygments.lexers.typoscript.TypoScriptLexer
+pygments.lexers.varnish.VCLLexer
+pygments.lexers.verification.BoogieLexer
+pygments.lexers.verification.SilverLexer
+pygments.lexers.x10.X10Lexer
+pygments.lexers.whiley.WhileyLexer
+pygments.lexers.xorg.XorgLexer
+pygments.lexers.webmisc.DuelLexer
+pygments.lexers.webmisc.XQueryLexer
+pygments.lexers.webmisc.QmlLexer
+pygments.lexers.webmisc.CirruLexer
+pygments.lexers.webmisc.SlimLexer
diff --git a/vendor/github.com/alecthomas/chroma/v2/quick/quick.go b/vendor/github.com/alecthomas/chroma/v2/quick/quick.go
new file mode 100644
index 00000000..af701d9c
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/quick/quick.go
@@ -0,0 +1,44 @@
+// Package quick provides simple, no-configuration source code highlighting.
+package quick
+
+import (
+ "io"
+
+ "github.com/alecthomas/chroma/v2"
+ "github.com/alecthomas/chroma/v2/formatters"
+ "github.com/alecthomas/chroma/v2/lexers"
+ "github.com/alecthomas/chroma/v2/styles"
+)
+
+// Highlight some text.
+//
+// Lexer, formatter and style may be empty, in which case a best-effort is made.
+func Highlight(w io.Writer, source, lexer, formatter, style string) error {
+ // Determine lexer.
+ l := lexers.Get(lexer)
+ if l == nil {
+ l = lexers.Analyse(source)
+ }
+ if l == nil {
+ l = lexers.Fallback
+ }
+ l = chroma.Coalesce(l)
+
+ // Determine formatter.
+ f := formatters.Get(formatter)
+ if f == nil {
+ f = formatters.Fallback
+ }
+
+ // Determine style.
+ s := styles.Get(style)
+ if s == nil {
+ s = styles.Fallback
+ }
+
+ it, err := l.Tokenise(nil, source)
+ if err != nil {
+ return err
+ }
+ return f.Format(w, s, it)
+}
diff --git a/vendor/github.com/alecthomas/chroma/v2/regexp.go b/vendor/github.com/alecthomas/chroma/v2/regexp.go
new file mode 100644
index 00000000..0dcb077f
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/regexp.go
@@ -0,0 +1,483 @@
+package chroma
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+ "sync"
+ "time"
+ "unicode/utf8"
+
+ "github.com/dlclark/regexp2"
+)
+
+// A Rule is the fundamental matching unit of the Regex lexer state machine.
+type Rule struct {
+ Pattern string
+ Type Emitter
+ Mutator Mutator
+}
+
+// Words creates a regex that matches any of the given literal words.
+func Words(prefix, suffix string, words ...string) string {
+ sort.Slice(words, func(i, j int) bool {
+ return len(words[j]) < len(words[i])
+ })
+ for i, word := range words {
+ words[i] = regexp.QuoteMeta(word)
+ }
+ return prefix + `(` + strings.Join(words, `|`) + `)` + suffix
+}
+
+// Tokenise text using lexer, returning tokens as a slice.
+func Tokenise(lexer Lexer, options *TokeniseOptions, text string) ([]Token, error) {
+ var out []Token
+ it, err := lexer.Tokenise(options, text)
+ if err != nil {
+ return nil, err
+ }
+ for t := it(); t != EOF; t = it() {
+ out = append(out, t)
+ }
+ return out, nil
+}
+
+// Rules maps from state to a sequence of Rules.
+type Rules map[string][]Rule
+
+// Rename clones rules then a rule.
+func (r Rules) Rename(oldRule, newRule string) Rules {
+ r = r.Clone()
+ r[newRule] = r[oldRule]
+ delete(r, oldRule)
+ return r
+}
+
+// Clone returns a clone of the Rules.
+func (r Rules) Clone() Rules {
+ out := map[string][]Rule{}
+ for key, rules := range r {
+ out[key] = make([]Rule, len(rules))
+ copy(out[key], rules)
+ }
+ return out
+}
+
+// Merge creates a clone of "r" then merges "rules" into the clone.
+func (r Rules) Merge(rules Rules) Rules {
+ out := r.Clone()
+ for k, v := range rules.Clone() {
+ out[k] = v
+ }
+ return out
+}
+
+// MustNewLexer creates a new Lexer with deferred rules generation or panics.
+func MustNewLexer(config *Config, rules func() Rules) *RegexLexer {
+ lexer, err := NewLexer(config, rules)
+ if err != nil {
+ panic(err)
+ }
+ return lexer
+}
+
+// NewLexer creates a new regex-based Lexer.
+//
+// "rules" is a state machine transition map. Each key is a state. Values are sets of rules
+// that match input, optionally modify lexer state, and output tokens.
+func NewLexer(config *Config, rulesFunc func() Rules) (*RegexLexer, error) {
+ if config == nil {
+ config = &Config{}
+ }
+ for _, glob := range append(config.Filenames, config.AliasFilenames...) {
+ _, err := filepath.Match(glob, "")
+ if err != nil {
+ return nil, fmt.Errorf("%s: %q is not a valid glob: %w", config.Name, glob, err)
+ }
+ }
+ r := &RegexLexer{
+ config: config,
+ fetchRulesFunc: func() (Rules, error) { return rulesFunc(), nil },
+ }
+ // One-off code to generate XML lexers in the Chroma source tree.
+ // var nameCleanRe = regexp.MustCompile(`[^-+A-Za-z0-9_]`)
+ // name := strings.ToLower(nameCleanRe.ReplaceAllString(config.Name, "_"))
+ // data, err := Marshal(r)
+ // if err != nil {
+ // if errors.Is(err, ErrNotSerialisable) {
+ // fmt.Fprintf(os.Stderr, "warning: %q: %s\n", name, err)
+ // return r, nil
+ // }
+ // return nil, err
+ // }
+ // _, file, _, ok := runtime.Caller(2)
+ // if !ok {
+ // panic("??")
+ // }
+ // fmt.Println(file)
+ // if strings.Contains(file, "/lexers/") {
+ // dir := filepath.Join(filepath.Dir(file), "embedded")
+ // err = os.MkdirAll(dir, 0700)
+ // if err != nil {
+ // return nil, err
+ // }
+ // filename := filepath.Join(dir, name) + ".xml"
+ // fmt.Println(filename)
+ // err = ioutil.WriteFile(filename, data, 0600)
+ // if err != nil {
+ // return nil, err
+ // }
+ // }
+ return r, nil
+}
+
+// Trace enables debug tracing.
+func (r *RegexLexer) Trace(trace bool) *RegexLexer {
+ r.trace = trace
+ return r
+}
+
+// A CompiledRule is a Rule with a pre-compiled regex.
+//
+// Note that regular expressions are lazily compiled on first use of the lexer.
+type CompiledRule struct {
+ Rule
+ Regexp *regexp2.Regexp
+ flags string
+}
+
+// CompiledRules is a map of rule name to sequence of compiled rules in that rule.
+type CompiledRules map[string][]*CompiledRule
+
+// LexerState contains the state for a single lex.
+type LexerState struct {
+ Lexer *RegexLexer
+ Registry *LexerRegistry
+ Text []rune
+ Pos int
+ Rules CompiledRules
+ Stack []string
+ State string
+ Rule int
+ // Group matches.
+ Groups []string
+ // Named Group matches.
+ NamedGroups map[string]string
+ // Custum context for mutators.
+ MutatorContext map[interface{}]interface{}
+ iteratorStack []Iterator
+ options *TokeniseOptions
+ newlineAdded bool
+}
+
+// Set mutator context.
+func (l *LexerState) Set(key interface{}, value interface{}) {
+ l.MutatorContext[key] = value
+}
+
+// Get mutator context.
+func (l *LexerState) Get(key interface{}) interface{} {
+ return l.MutatorContext[key]
+}
+
+// Iterator returns the next Token from the lexer.
+func (l *LexerState) Iterator() Token { // nolint: gocognit
+ end := len(l.Text)
+ if l.newlineAdded {
+ end--
+ }
+ for l.Pos < end && len(l.Stack) > 0 {
+ // Exhaust the iterator stack, if any.
+ for len(l.iteratorStack) > 0 {
+ n := len(l.iteratorStack) - 1
+ t := l.iteratorStack[n]()
+ if t == EOF {
+ l.iteratorStack = l.iteratorStack[:n]
+ continue
+ }
+ return t
+ }
+
+ l.State = l.Stack[len(l.Stack)-1]
+ if l.Lexer.trace {
+ fmt.Fprintf(os.Stderr, "%s: pos=%d, text=%q\n", l.State, l.Pos, string(l.Text[l.Pos:]))
+ }
+ selectedRule, ok := l.Rules[l.State]
+ if !ok {
+ panic("unknown state " + l.State)
+ }
+ ruleIndex, rule, groups, namedGroups := matchRules(l.Text, l.Pos, selectedRule)
+ // No match.
+ if groups == nil {
+ // From Pygments :\
+ //
+ // If the RegexLexer encounters a newline that is flagged as an error token, the stack is
+ // emptied and the lexer continues scanning in the 'root' state. This can help producing
+ // error-tolerant highlighting for erroneous input, e.g. when a single-line string is not
+ // closed.
+ if l.Text[l.Pos] == '\n' && l.State != l.options.State {
+ l.Stack = []string{l.options.State}
+ continue
+ }
+ l.Pos++
+ return Token{Error, string(l.Text[l.Pos-1 : l.Pos])}
+ }
+ l.Rule = ruleIndex
+ l.Groups = groups
+ l.NamedGroups = namedGroups
+ l.Pos += utf8.RuneCountInString(groups[0])
+ if rule.Mutator != nil {
+ if err := rule.Mutator.Mutate(l); err != nil {
+ panic(err)
+ }
+ }
+ if rule.Type != nil {
+ l.iteratorStack = append(l.iteratorStack, rule.Type.Emit(l.Groups, l))
+ }
+ }
+ // Exhaust the IteratorStack, if any.
+ // Duplicate code, but eh.
+ for len(l.iteratorStack) > 0 {
+ n := len(l.iteratorStack) - 1
+ t := l.iteratorStack[n]()
+ if t == EOF {
+ l.iteratorStack = l.iteratorStack[:n]
+ continue
+ }
+ return t
+ }
+
+ // If we get to here and we still have text, return it as an error.
+ if l.Pos != len(l.Text) && len(l.Stack) == 0 {
+ value := string(l.Text[l.Pos:])
+ l.Pos = len(l.Text)
+ return Token{Type: Error, Value: value}
+ }
+ return EOF
+}
+
+// RegexLexer is the default lexer implementation used in Chroma.
+type RegexLexer struct {
+ registry *LexerRegistry // The LexerRegistry this Lexer is associated with, if any.
+ config *Config
+ analyser func(text string) float32
+ trace bool
+
+ mu sync.Mutex
+ compiled bool
+ rawRules Rules
+ rules map[string][]*CompiledRule
+ fetchRulesFunc func() (Rules, error)
+ compileOnce sync.Once
+}
+
+func (r *RegexLexer) String() string {
+ return r.config.Name
+}
+
+// Rules in the Lexer.
+func (r *RegexLexer) Rules() (Rules, error) {
+ if err := r.needRules(); err != nil {
+ return nil, err
+ }
+ return r.rawRules, nil
+}
+
+// SetRegistry the lexer will use to lookup other lexers if necessary.
+func (r *RegexLexer) SetRegistry(registry *LexerRegistry) Lexer {
+ r.registry = registry
+ return r
+}
+
+// SetAnalyser sets the analyser function used to perform content inspection.
+func (r *RegexLexer) SetAnalyser(analyser func(text string) float32) Lexer {
+ r.analyser = analyser
+ return r
+}
+
+// AnalyseText scores how likely a fragment of text is to match this lexer, between 0.0 and 1.0.
+func (r *RegexLexer) AnalyseText(text string) float32 {
+ if r.analyser != nil {
+ return r.analyser(text)
+ }
+ return 0
+}
+
+// SetConfig replaces the Config for this Lexer.
+func (r *RegexLexer) SetConfig(config *Config) *RegexLexer {
+ r.config = config
+ return r
+}
+
+// Config returns the Config for this Lexer.
+func (r *RegexLexer) Config() *Config {
+ return r.config
+}
+
+// Regex compilation is deferred until the lexer is used. This is to avoid significant init() time costs.
+func (r *RegexLexer) maybeCompile() (err error) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ if r.compiled {
+ return nil
+ }
+ for state, rules := range r.rules {
+ for i, rule := range rules {
+ if rule.Regexp == nil {
+ pattern := "(?:" + rule.Pattern + ")"
+ if rule.flags != "" {
+ pattern = "(?" + rule.flags + ")" + pattern
+ }
+ pattern = `\G` + pattern
+ rule.Regexp, err = regexp2.Compile(pattern, 0)
+ if err != nil {
+ return fmt.Errorf("failed to compile rule %s.%d: %s", state, i, err)
+ }
+ rule.Regexp.MatchTimeout = time.Millisecond * 250
+ }
+ }
+ }
+restart:
+ seen := map[LexerMutator]bool{}
+ for state := range r.rules {
+ for i := 0; i < len(r.rules[state]); i++ {
+ rule := r.rules[state][i]
+ if compile, ok := rule.Mutator.(LexerMutator); ok {
+ if seen[compile] {
+ return fmt.Errorf("saw mutator %T twice; this should not happen", compile)
+ }
+ seen[compile] = true
+ if err := compile.MutateLexer(r.rules, state, i); err != nil {
+ return err
+ }
+ // Process the rules again in case the mutator added/removed rules.
+ //
+ // This sounds bad, but shouldn't be significant in practice.
+ goto restart
+ }
+ }
+ }
+ r.compiled = true
+ return nil
+}
+
+func (r *RegexLexer) fetchRules() error {
+ rules, err := r.fetchRulesFunc()
+ if err != nil {
+ return fmt.Errorf("%s: failed to compile rules: %w", r.config.Name, err)
+ }
+ if _, ok := rules["root"]; !ok {
+ return fmt.Errorf("no \"root\" state")
+ }
+ compiledRules := map[string][]*CompiledRule{}
+ for state, rules := range rules {
+ compiledRules[state] = nil
+ for _, rule := range rules {
+ flags := ""
+ if !r.config.NotMultiline {
+ flags += "m"
+ }
+ if r.config.CaseInsensitive {
+ flags += "i"
+ }
+ if r.config.DotAll {
+ flags += "s"
+ }
+ compiledRules[state] = append(compiledRules[state], &CompiledRule{Rule: rule, flags: flags})
+ }
+ }
+
+ r.rawRules = rules
+ r.rules = compiledRules
+ return nil
+}
+
+func (r *RegexLexer) needRules() error {
+ var err error
+ if r.fetchRulesFunc != nil {
+ r.compileOnce.Do(func() {
+ err = r.fetchRules()
+ })
+ }
+ if err := r.maybeCompile(); err != nil {
+ return err
+ }
+ return err
+}
+
+// Tokenise text using lexer, returning an iterator.
+func (r *RegexLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) {
+ err := r.needRules()
+ if err != nil {
+ return nil, err
+ }
+ if options == nil {
+ options = defaultOptions
+ }
+ if options.EnsureLF {
+ text = ensureLF(text)
+ }
+ newlineAdded := false
+ if !options.Nested && r.config.EnsureNL && !strings.HasSuffix(text, "\n") {
+ text += "\n"
+ newlineAdded = true
+ }
+ state := &LexerState{
+ Registry: r.registry,
+ newlineAdded: newlineAdded,
+ options: options,
+ Lexer: r,
+ Text: []rune(text),
+ Stack: []string{options.State},
+ Rules: r.rules,
+ MutatorContext: map[interface{}]interface{}{},
+ }
+ return state.Iterator, nil
+}
+
+// MustRules is like Rules() but will panic on error.
+func (r *RegexLexer) MustRules() Rules {
+ rules, err := r.Rules()
+ if err != nil {
+ panic(err)
+ }
+ return rules
+}
+
+func matchRules(text []rune, pos int, rules []*CompiledRule) (int, *CompiledRule, []string, map[string]string) {
+ for i, rule := range rules {
+ match, err := rule.Regexp.FindRunesMatchStartingAt(text, pos)
+ if match != nil && err == nil && match.Index == pos {
+ groups := []string{}
+ namedGroups := make(map[string]string)
+ for _, g := range match.Groups() {
+ namedGroups[g.Name] = g.String()
+ groups = append(groups, g.String())
+ }
+ return i, rule, groups, namedGroups
+ }
+ }
+ return 0, &CompiledRule{}, nil, nil
+}
+
+// replace \r and \r\n with \n
+// same as strings.ReplaceAll but more efficient
+func ensureLF(text string) string {
+ buf := make([]byte, len(text))
+ var j int
+ for i := 0; i < len(text); i++ {
+ c := text[i]
+ if c == '\r' {
+ if i < len(text)-1 && text[i+1] == '\n' {
+ continue
+ }
+ c = '\n'
+ }
+ buf[j] = c
+ j++
+ }
+ return string(buf[:j])
+}
diff --git a/vendor/github.com/alecthomas/chroma/v2/registry.go b/vendor/github.com/alecthomas/chroma/v2/registry.go
new file mode 100644
index 00000000..4742e8c5
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/registry.go
@@ -0,0 +1,210 @@
+package chroma
+
+import (
+ "path/filepath"
+ "sort"
+ "strings"
+)
+
+var (
+ ignoredSuffixes = [...]string{
+ // Editor backups
+ "~", ".bak", ".old", ".orig",
+ // Debian and derivatives apt/dpkg/ucf backups
+ ".dpkg-dist", ".dpkg-old", ".ucf-dist", ".ucf-new", ".ucf-old",
+ // Red Hat and derivatives rpm backups
+ ".rpmnew", ".rpmorig", ".rpmsave",
+ // Build system input/template files
+ ".in",
+ }
+)
+
+// LexerRegistry is a registry of Lexers.
+type LexerRegistry struct {
+ Lexers Lexers
+ byName map[string]Lexer
+ byAlias map[string]Lexer
+}
+
+// NewLexerRegistry creates a new LexerRegistry of Lexers.
+func NewLexerRegistry() *LexerRegistry {
+ return &LexerRegistry{
+ byName: map[string]Lexer{},
+ byAlias: map[string]Lexer{},
+ }
+}
+
+// Names of all lexers, optionally including aliases.
+func (l *LexerRegistry) Names(withAliases bool) []string {
+ out := []string{}
+ for _, lexer := range l.Lexers {
+ config := lexer.Config()
+ out = append(out, config.Name)
+ if withAliases {
+ out = append(out, config.Aliases...)
+ }
+ }
+ sort.Strings(out)
+ return out
+}
+
+// Get a Lexer by name, alias or file extension.
+func (l *LexerRegistry) Get(name string) Lexer {
+ if lexer := l.byName[name]; lexer != nil {
+ return lexer
+ }
+ if lexer := l.byAlias[name]; lexer != nil {
+ return lexer
+ }
+ if lexer := l.byName[strings.ToLower(name)]; lexer != nil {
+ return lexer
+ }
+ if lexer := l.byAlias[strings.ToLower(name)]; lexer != nil {
+ return lexer
+ }
+
+ candidates := PrioritisedLexers{}
+ // Try file extension.
+ if lexer := l.Match("filename." + name); lexer != nil {
+ candidates = append(candidates, lexer)
+ }
+ // Try exact filename.
+ if lexer := l.Match(name); lexer != nil {
+ candidates = append(candidates, lexer)
+ }
+ if len(candidates) == 0 {
+ return nil
+ }
+ sort.Sort(candidates)
+ return candidates[0]
+}
+
+// MatchMimeType attempts to find a lexer for the given MIME type.
+func (l *LexerRegistry) MatchMimeType(mimeType string) Lexer {
+ matched := PrioritisedLexers{}
+ for _, l := range l.Lexers {
+ for _, lmt := range l.Config().MimeTypes {
+ if mimeType == lmt {
+ matched = append(matched, l)
+ }
+ }
+ }
+ if len(matched) != 0 {
+ sort.Sort(matched)
+ return matched[0]
+ }
+ return nil
+}
+
+// Match returns the first lexer matching filename.
+//
+// Note that this iterates over all file patterns in all lexers, so is not fast.
+func (l *LexerRegistry) Match(filename string) Lexer {
+ filename = filepath.Base(filename)
+ matched := PrioritisedLexers{}
+ // First, try primary filename matches.
+ for _, lexer := range l.Lexers {
+ config := lexer.Config()
+ for _, glob := range config.Filenames {
+ ok, err := filepath.Match(glob, filename)
+ if err != nil { // nolint
+ panic(err)
+ } else if ok {
+ matched = append(matched, lexer)
+ } else {
+ for _, suf := range &ignoredSuffixes {
+ ok, err := filepath.Match(glob+suf, filename)
+ if err != nil {
+ panic(err)
+ } else if ok {
+ matched = append(matched, lexer)
+ break
+ }
+ }
+ }
+ }
+ }
+ if len(matched) > 0 {
+ sort.Sort(matched)
+ return matched[0]
+ }
+ matched = nil
+ // Next, try filename aliases.
+ for _, lexer := range l.Lexers {
+ config := lexer.Config()
+ for _, glob := range config.AliasFilenames {
+ ok, err := filepath.Match(glob, filename)
+ if err != nil { // nolint
+ panic(err)
+ } else if ok {
+ matched = append(matched, lexer)
+ } else {
+ for _, suf := range &ignoredSuffixes {
+ ok, err := filepath.Match(glob+suf, filename)
+ if err != nil {
+ panic(err)
+ } else if ok {
+ matched = append(matched, lexer)
+ break
+ }
+ }
+ }
+ }
+ }
+ if len(matched) > 0 {
+ sort.Sort(matched)
+ return matched[0]
+ }
+ return nil
+}
+
+// Analyse text content and return the "best" lexer..
+func (l *LexerRegistry) Analyse(text string) Lexer {
+ var picked Lexer
+ highest := float32(0.0)
+ for _, lexer := range l.Lexers {
+ if analyser, ok := lexer.(Analyser); ok {
+ weight := analyser.AnalyseText(text)
+ if weight > highest {
+ picked = lexer
+ highest = weight
+ }
+ }
+ }
+ return picked
+}
+
+// Register a Lexer with the LexerRegistry. If the lexer is already registered
+// it will be replaced.
+func (l *LexerRegistry) Register(lexer Lexer) Lexer {
+ lexer.SetRegistry(l)
+ config := lexer.Config()
+
+ l.byName[config.Name] = lexer
+ l.byName[strings.ToLower(config.Name)] = lexer
+
+ for _, alias := range config.Aliases {
+ l.byAlias[alias] = lexer
+ l.byAlias[strings.ToLower(alias)] = lexer
+ }
+
+ l.Lexers = add(l.Lexers, lexer)
+
+ return lexer
+}
+
+// add adds a lexer to a slice of lexers if it doesn't already exist, or if found will replace it.
+func add(lexers Lexers, lexer Lexer) Lexers {
+ for i, val := range lexers {
+ if val == nil {
+ continue
+ }
+
+ if val.Config().Name == lexer.Config().Name {
+ lexers[i] = lexer
+ return lexers
+ }
+ }
+
+ return append(lexers, lexer)
+}
diff --git a/vendor/github.com/alecthomas/chroma/v2/remap.go b/vendor/github.com/alecthomas/chroma/v2/remap.go
new file mode 100644
index 00000000..bcf5e66d
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/remap.go
@@ -0,0 +1,94 @@
+package chroma
+
+type remappingLexer struct {
+ lexer Lexer
+ mapper func(Token) []Token
+}
+
+// RemappingLexer remaps a token to a set of, potentially empty, tokens.
+func RemappingLexer(lexer Lexer, mapper func(Token) []Token) Lexer {
+ return &remappingLexer{lexer, mapper}
+}
+
+func (r *remappingLexer) AnalyseText(text string) float32 {
+ return r.lexer.AnalyseText(text)
+}
+
+func (r *remappingLexer) SetAnalyser(analyser func(text string) float32) Lexer {
+ r.lexer.SetAnalyser(analyser)
+ return r
+}
+
+func (r *remappingLexer) SetRegistry(registry *LexerRegistry) Lexer {
+ r.lexer.SetRegistry(registry)
+ return r
+}
+
+func (r *remappingLexer) Config() *Config {
+ return r.lexer.Config()
+}
+
+func (r *remappingLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) {
+ it, err := r.lexer.Tokenise(options, text)
+ if err != nil {
+ return nil, err
+ }
+ var buffer []Token
+ return func() Token {
+ for {
+ if len(buffer) > 0 {
+ t := buffer[0]
+ buffer = buffer[1:]
+ return t
+ }
+ t := it()
+ if t == EOF {
+ return t
+ }
+ buffer = r.mapper(t)
+ }
+ }, nil
+}
+
+// TypeMapping defines type maps for the TypeRemappingLexer.
+type TypeMapping []struct {
+ From, To TokenType
+ Words []string
+}
+
+// TypeRemappingLexer remaps types of tokens coming from a parent Lexer.
+//
+// eg. Map "defvaralias" tokens of type NameVariable to NameFunction:
+//
+// mapping := TypeMapping{
+// {NameVariable, NameFunction, []string{"defvaralias"},
+// }
+// lexer = TypeRemappingLexer(lexer, mapping)
+func TypeRemappingLexer(lexer Lexer, mapping TypeMapping) Lexer {
+ // Lookup table for fast remapping.
+ lut := map[TokenType]map[string]TokenType{}
+ for _, rt := range mapping {
+ km, ok := lut[rt.From]
+ if !ok {
+ km = map[string]TokenType{}
+ lut[rt.From] = km
+ }
+ if len(rt.Words) == 0 {
+ km[""] = rt.To
+ } else {
+ for _, k := range rt.Words {
+ km[k] = rt.To
+ }
+ }
+ }
+ return RemappingLexer(lexer, func(t Token) []Token {
+ if k, ok := lut[t.Type]; ok {
+ if tt, ok := k[t.Value]; ok {
+ t.Type = tt
+ } else if tt, ok := k[""]; ok {
+ t.Type = tt
+ }
+ }
+ return []Token{t}
+ })
+}
diff --git a/vendor/github.com/alecthomas/chroma/v2/renovate.json5 b/vendor/github.com/alecthomas/chroma/v2/renovate.json5
new file mode 100644
index 00000000..77c7b016
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/renovate.json5
@@ -0,0 +1,18 @@
+{
+ $schema: "https://docs.renovatebot.com/renovate-schema.json",
+ extends: [
+ "config:recommended",
+ ":semanticCommits",
+ ":semanticCommitTypeAll(chore)",
+ ":semanticCommitScope(deps)",
+ "group:allNonMajor",
+ "schedule:earlyMondays", // Run once a week.
+ ],
+ packageRules: [
+ {
+ matchPackageNames: ["golangci-lint"],
+ matchManagers: ["hermit"],
+ enabled: false,
+ },
+ ],
+}
diff --git a/vendor/github.com/alecthomas/chroma/v2/serialise.go b/vendor/github.com/alecthomas/chroma/v2/serialise.go
new file mode 100644
index 00000000..645a5faa
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/serialise.go
@@ -0,0 +1,479 @@
+package chroma
+
+import (
+ "compress/gzip"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "io"
+ "io/fs"
+ "math"
+ "path/filepath"
+ "reflect"
+ "regexp"
+ "strings"
+
+ "github.com/dlclark/regexp2"
+)
+
+// Serialisation of Chroma rules to XML. The format is:
+//
+//
+//
+//
+// [<$EMITTER ...>]
+// [<$MUTATOR ...>]
+//
+//
+//
+//
+// eg. Include("String") would become:
+//
+//
+//
+//
+//
+// [null, null, {"kind": "include", "state": "String"}]
+//
+// eg. Rule{`\d+`, Text, nil} would become:
+//
+//
+//
+//
+//
+// eg. Rule{`"`, String, Push("String")}
+//
+//
+//
+//
+//
+//
+// eg. Rule{`(\w+)(\n)`, ByGroups(Keyword, Whitespace), nil},
+//
+//
+//
+//
+//
+var (
+ // ErrNotSerialisable is returned if a lexer contains Rules that cannot be serialised.
+ ErrNotSerialisable = fmt.Errorf("not serialisable")
+ emitterTemplates = func() map[string]SerialisableEmitter {
+ out := map[string]SerialisableEmitter{}
+ for _, emitter := range []SerialisableEmitter{
+ &byGroupsEmitter{},
+ &usingSelfEmitter{},
+ TokenType(0),
+ &usingEmitter{},
+ &usingByGroup{},
+ } {
+ out[emitter.EmitterKind()] = emitter
+ }
+ return out
+ }()
+ mutatorTemplates = func() map[string]SerialisableMutator {
+ out := map[string]SerialisableMutator{}
+ for _, mutator := range []SerialisableMutator{
+ &includeMutator{},
+ &combinedMutator{},
+ &multiMutator{},
+ &pushMutator{},
+ &popMutator{},
+ } {
+ out[mutator.MutatorKind()] = mutator
+ }
+ return out
+ }()
+)
+
+// fastUnmarshalConfig unmarshals only the Config from a serialised lexer.
+func fastUnmarshalConfig(from fs.FS, path string) (*Config, error) {
+ r, err := from.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer r.Close()
+ dec := xml.NewDecoder(r)
+ for {
+ token, err := dec.Token()
+ if err != nil {
+ if errors.Is(err, io.EOF) {
+ return nil, fmt.Errorf("could not find element")
+ }
+ return nil, err
+ }
+ switch se := token.(type) {
+ case xml.StartElement:
+ if se.Name.Local != "config" {
+ break
+ }
+
+ var config Config
+ err = dec.DecodeElement(&config, &se)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", path, err)
+ }
+ return &config, nil
+ }
+ }
+}
+
+// MustNewXMLLexer constructs a new RegexLexer from an XML file or panics.
+func MustNewXMLLexer(from fs.FS, path string) *RegexLexer {
+ lex, err := NewXMLLexer(from, path)
+ if err != nil {
+ panic(err)
+ }
+ return lex
+}
+
+// NewXMLLexer creates a new RegexLexer from a serialised RegexLexer.
+func NewXMLLexer(from fs.FS, path string) (*RegexLexer, error) {
+ config, err := fastUnmarshalConfig(from, path)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, glob := range append(config.Filenames, config.AliasFilenames...) {
+ _, err := filepath.Match(glob, "")
+ if err != nil {
+ return nil, fmt.Errorf("%s: %q is not a valid glob: %w", config.Name, glob, err)
+ }
+ }
+
+ var analyserFn func(string) float32
+
+ if config.Analyse != nil {
+ type regexAnalyse struct {
+ re *regexp2.Regexp
+ score float32
+ }
+
+ regexAnalysers := make([]regexAnalyse, 0, len(config.Analyse.Regexes))
+
+ for _, ra := range config.Analyse.Regexes {
+ re, err := regexp2.Compile(ra.Pattern, regexp2.None)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %q is not a valid analyser regex: %w", config.Name, ra.Pattern, err)
+ }
+
+ regexAnalysers = append(regexAnalysers, regexAnalyse{re, ra.Score})
+ }
+
+ analyserFn = func(text string) float32 {
+ var score float32
+
+ for _, ra := range regexAnalysers {
+ ok, err := ra.re.MatchString(text)
+ if err != nil {
+ return 0
+ }
+
+ if ok && config.Analyse.First {
+ return float32(math.Min(float64(ra.score), 1.0))
+ }
+
+ if ok {
+ score += ra.score
+ }
+ }
+
+ return float32(math.Min(float64(score), 1.0))
+ }
+ }
+
+ return &RegexLexer{
+ config: config,
+ analyser: analyserFn,
+ fetchRulesFunc: func() (Rules, error) {
+ var lexer struct {
+ Config
+ Rules Rules `xml:"rules"`
+ }
+ // Try to open .xml fallback to .xml.gz
+ fr, err := from.Open(path)
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ path += ".gz"
+ fr, err = from.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ return nil, err
+ }
+ }
+ defer fr.Close()
+ var r io.Reader = fr
+ if strings.HasSuffix(path, ".gz") {
+ r, err = gzip.NewReader(r)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", path, err)
+ }
+ }
+ err = xml.NewDecoder(r).Decode(&lexer)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", path, err)
+ }
+ return lexer.Rules, nil
+ },
+ }, nil
+}
+
+// Marshal a RegexLexer to XML.
+func Marshal(l *RegexLexer) ([]byte, error) {
+ type lexer struct {
+ Config Config `xml:"config"`
+ Rules Rules `xml:"rules"`
+ }
+
+ rules, err := l.Rules()
+ if err != nil {
+ return nil, err
+ }
+ root := &lexer{
+ Config: *l.Config(),
+ Rules: rules,
+ }
+ data, err := xml.MarshalIndent(root, "", " ")
+ if err != nil {
+ return nil, err
+ }
+ re := regexp.MustCompile(`>[a-zA-Z]+>`)
+ data = re.ReplaceAll(data, []byte(`/>`))
+ return data, nil
+}
+
+// Unmarshal a RegexLexer from XML.
+func Unmarshal(data []byte) (*RegexLexer, error) {
+ type lexer struct {
+ Config Config `xml:"config"`
+ Rules Rules `xml:"rules"`
+ }
+ root := &lexer{}
+ err := xml.Unmarshal(data, root)
+ if err != nil {
+ return nil, fmt.Errorf("invalid Lexer XML: %w", err)
+ }
+ lex, err := NewLexer(&root.Config, func() Rules { return root.Rules })
+ if err != nil {
+ return nil, err
+ }
+ return lex, nil
+}
+
+func marshalMutator(e *xml.Encoder, mutator Mutator) error {
+ if mutator == nil {
+ return nil
+ }
+ smutator, ok := mutator.(SerialisableMutator)
+ if !ok {
+ return fmt.Errorf("unsupported mutator: %w", ErrNotSerialisable)
+ }
+ return e.EncodeElement(mutator, xml.StartElement{Name: xml.Name{Local: smutator.MutatorKind()}})
+}
+
+func unmarshalMutator(d *xml.Decoder, start xml.StartElement) (Mutator, error) {
+ kind := start.Name.Local
+ mutator, ok := mutatorTemplates[kind]
+ if !ok {
+ return nil, fmt.Errorf("unknown mutator %q: %w", kind, ErrNotSerialisable)
+ }
+ value, target := newFromTemplate(mutator)
+ if err := d.DecodeElement(target, &start); err != nil {
+ return nil, err
+ }
+ return value().(SerialisableMutator), nil
+}
+
+func marshalEmitter(e *xml.Encoder, emitter Emitter) error {
+ if emitter == nil {
+ return nil
+ }
+ semitter, ok := emitter.(SerialisableEmitter)
+ if !ok {
+ return fmt.Errorf("unsupported emitter %T: %w", emitter, ErrNotSerialisable)
+ }
+ return e.EncodeElement(emitter, xml.StartElement{
+ Name: xml.Name{Local: semitter.EmitterKind()},
+ })
+}
+
+func unmarshalEmitter(d *xml.Decoder, start xml.StartElement) (Emitter, error) {
+ kind := start.Name.Local
+ mutator, ok := emitterTemplates[kind]
+ if !ok {
+ return nil, fmt.Errorf("unknown emitter %q: %w", kind, ErrNotSerialisable)
+ }
+ value, target := newFromTemplate(mutator)
+ if err := d.DecodeElement(target, &start); err != nil {
+ return nil, err
+ }
+ return value().(SerialisableEmitter), nil
+}
+
+func (r Rule) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
+ start := xml.StartElement{
+ Name: xml.Name{Local: "rule"},
+ }
+ if r.Pattern != "" {
+ start.Attr = append(start.Attr, xml.Attr{
+ Name: xml.Name{Local: "pattern"},
+ Value: r.Pattern,
+ })
+ }
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+ if err := marshalEmitter(e, r.Type); err != nil {
+ return err
+ }
+ if err := marshalMutator(e, r.Mutator); err != nil {
+ return err
+ }
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+func (r *Rule) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ for _, attr := range start.Attr {
+ if attr.Name.Local == "pattern" {
+ r.Pattern = attr.Value
+ break
+ }
+ }
+ for {
+ token, err := d.Token()
+ if err != nil {
+ return err
+ }
+ switch token := token.(type) {
+ case xml.StartElement:
+ mutator, err := unmarshalMutator(d, token)
+ if err != nil && !errors.Is(err, ErrNotSerialisable) {
+ return err
+ } else if err == nil {
+ if r.Mutator != nil {
+ return fmt.Errorf("duplicate mutator")
+ }
+ r.Mutator = mutator
+ continue
+ }
+ emitter, err := unmarshalEmitter(d, token)
+ if err != nil && !errors.Is(err, ErrNotSerialisable) { // nolint: gocritic
+ return err
+ } else if err == nil {
+ if r.Type != nil {
+ return fmt.Errorf("duplicate emitter")
+ }
+ r.Type = emitter
+ continue
+ } else {
+ return err
+ }
+
+ case xml.EndElement:
+ return nil
+ }
+ }
+}
+
+type xmlRuleState struct {
+ Name string `xml:"name,attr"`
+ Rules []Rule `xml:"rule"`
+}
+
+type xmlRules struct {
+ States []xmlRuleState `xml:"state"`
+}
+
+func (r Rules) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
+ xr := xmlRules{}
+ for state, rules := range r {
+ xr.States = append(xr.States, xmlRuleState{
+ Name: state,
+ Rules: rules,
+ })
+ }
+ return e.EncodeElement(xr, xml.StartElement{Name: xml.Name{Local: "rules"}})
+}
+
+func (r *Rules) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ xr := xmlRules{}
+ if err := d.DecodeElement(&xr, &start); err != nil {
+ return err
+ }
+ if *r == nil {
+ *r = Rules{}
+ }
+ for _, state := range xr.States {
+ (*r)[state.Name] = state.Rules
+ }
+ return nil
+}
+
+type xmlTokenType struct {
+ Type string `xml:"type,attr"`
+}
+
+func (t *TokenType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ el := xmlTokenType{}
+ if err := d.DecodeElement(&el, &start); err != nil {
+ return err
+ }
+ tt, err := TokenTypeString(el.Type)
+ if err != nil {
+ return err
+ }
+ *t = tt
+ return nil
+}
+
+func (t TokenType) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "type"}, Value: t.String()})
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+// This hijinks is a bit unfortunate but without it we can't deserialise into TokenType.
+func newFromTemplate(template interface{}) (value func() interface{}, target interface{}) {
+ t := reflect.TypeOf(template)
+ if t.Kind() == reflect.Ptr {
+ v := reflect.New(t.Elem())
+ return v.Interface, v.Interface()
+ }
+ v := reflect.New(t)
+ return func() interface{} { return v.Elem().Interface() }, v.Interface()
+}
+
+func (b *Emitters) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ for {
+ token, err := d.Token()
+ if err != nil {
+ return err
+ }
+ switch token := token.(type) {
+ case xml.StartElement:
+ emitter, err := unmarshalEmitter(d, token)
+ if err != nil {
+ return err
+ }
+ *b = append(*b, emitter)
+
+ case xml.EndElement:
+ return nil
+ }
+ }
+}
+
+func (b Emitters) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+ for _, m := range b {
+ if err := marshalEmitter(e, m); err != nil {
+ return err
+ }
+ }
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
diff --git a/vendor/github.com/alecthomas/chroma/v2/style.go b/vendor/github.com/alecthomas/chroma/v2/style.go
new file mode 100644
index 00000000..cc8d9a60
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/style.go
@@ -0,0 +1,481 @@
+package chroma
+
+import (
+ "encoding/xml"
+ "fmt"
+ "io"
+ "sort"
+ "strings"
+)
+
+// Trilean value for StyleEntry value inheritance.
+type Trilean uint8
+
+// Trilean states.
+const (
+ Pass Trilean = iota
+ Yes
+ No
+)
+
+func (t Trilean) String() string {
+ switch t {
+ case Yes:
+ return "Yes"
+ case No:
+ return "No"
+ default:
+ return "Pass"
+ }
+}
+
+// Prefix returns s with "no" as a prefix if Trilean is no.
+func (t Trilean) Prefix(s string) string {
+ if t == Yes {
+ return s
+ } else if t == No {
+ return "no" + s
+ }
+ return ""
+}
+
+// A StyleEntry in the Style map.
+type StyleEntry struct {
+ // Hex colours.
+ Colour Colour
+ Background Colour
+ Border Colour
+
+ Bold Trilean
+ Italic Trilean
+ Underline Trilean
+ NoInherit bool
+}
+
+func (s StyleEntry) MarshalText() ([]byte, error) {
+ return []byte(s.String()), nil
+}
+
+func (s StyleEntry) String() string {
+ out := []string{}
+ if s.Bold != Pass {
+ out = append(out, s.Bold.Prefix("bold"))
+ }
+ if s.Italic != Pass {
+ out = append(out, s.Italic.Prefix("italic"))
+ }
+ if s.Underline != Pass {
+ out = append(out, s.Underline.Prefix("underline"))
+ }
+ if s.NoInherit {
+ out = append(out, "noinherit")
+ }
+ if s.Colour.IsSet() {
+ out = append(out, s.Colour.String())
+ }
+ if s.Background.IsSet() {
+ out = append(out, "bg:"+s.Background.String())
+ }
+ if s.Border.IsSet() {
+ out = append(out, "border:"+s.Border.String())
+ }
+ return strings.Join(out, " ")
+}
+
+// Sub subtracts e from s where elements match.
+func (s StyleEntry) Sub(e StyleEntry) StyleEntry {
+ out := StyleEntry{}
+ if e.Colour != s.Colour {
+ out.Colour = s.Colour
+ }
+ if e.Background != s.Background {
+ out.Background = s.Background
+ }
+ if e.Bold != s.Bold {
+ out.Bold = s.Bold
+ }
+ if e.Italic != s.Italic {
+ out.Italic = s.Italic
+ }
+ if e.Underline != s.Underline {
+ out.Underline = s.Underline
+ }
+ if e.Border != s.Border {
+ out.Border = s.Border
+ }
+ return out
+}
+
+// Inherit styles from ancestors.
+//
+// Ancestors should be provided from oldest to newest.
+func (s StyleEntry) Inherit(ancestors ...StyleEntry) StyleEntry {
+ out := s
+ for i := len(ancestors) - 1; i >= 0; i-- {
+ if out.NoInherit {
+ return out
+ }
+ ancestor := ancestors[i]
+ if !out.Colour.IsSet() {
+ out.Colour = ancestor.Colour
+ }
+ if !out.Background.IsSet() {
+ out.Background = ancestor.Background
+ }
+ if !out.Border.IsSet() {
+ out.Border = ancestor.Border
+ }
+ if out.Bold == Pass {
+ out.Bold = ancestor.Bold
+ }
+ if out.Italic == Pass {
+ out.Italic = ancestor.Italic
+ }
+ if out.Underline == Pass {
+ out.Underline = ancestor.Underline
+ }
+ }
+ return out
+}
+
+func (s StyleEntry) IsZero() bool {
+ return s.Colour == 0 && s.Background == 0 && s.Border == 0 && s.Bold == Pass && s.Italic == Pass &&
+ s.Underline == Pass && !s.NoInherit
+}
+
+// A StyleBuilder is a mutable structure for building styles.
+//
+// Once built, a Style is immutable.
+type StyleBuilder struct {
+ entries map[TokenType]string
+ name string
+ parent *Style
+}
+
+func NewStyleBuilder(name string) *StyleBuilder {
+ return &StyleBuilder{name: name, entries: map[TokenType]string{}}
+}
+
+func (s *StyleBuilder) AddAll(entries StyleEntries) *StyleBuilder {
+ for ttype, entry := range entries {
+ s.entries[ttype] = entry
+ }
+ return s
+}
+
+func (s *StyleBuilder) Get(ttype TokenType) StyleEntry {
+ // This is less than ideal, but it's the price for not having to check errors on each Add().
+ entry, _ := ParseStyleEntry(s.entries[ttype])
+ if s.parent != nil {
+ entry = entry.Inherit(s.parent.Get(ttype))
+ }
+ return entry
+}
+
+// Add an entry to the Style map.
+//
+// See http://pygments.org/docs/styles/#style-rules for details.
+func (s *StyleBuilder) Add(ttype TokenType, entry string) *StyleBuilder { // nolint: gocyclo
+ s.entries[ttype] = entry
+ return s
+}
+
+func (s *StyleBuilder) AddEntry(ttype TokenType, entry StyleEntry) *StyleBuilder {
+ s.entries[ttype] = entry.String()
+ return s
+}
+
+// Transform passes each style entry currently defined in the builder to the supplied
+// function and saves the returned value. This can be used to adjust a style's colours;
+// see Colour's ClampBrightness function, for example.
+func (s *StyleBuilder) Transform(transform func(StyleEntry) StyleEntry) *StyleBuilder {
+ types := make(map[TokenType]struct{})
+ for tt := range s.entries {
+ types[tt] = struct{}{}
+ }
+ if s.parent != nil {
+ for _, tt := range s.parent.Types() {
+ types[tt] = struct{}{}
+ }
+ }
+ for tt := range types {
+ s.AddEntry(tt, transform(s.Get(tt)))
+ }
+ return s
+}
+
+func (s *StyleBuilder) Build() (*Style, error) {
+ style := &Style{
+ Name: s.name,
+ entries: map[TokenType]StyleEntry{},
+ parent: s.parent,
+ }
+ for ttype, descriptor := range s.entries {
+ entry, err := ParseStyleEntry(descriptor)
+ if err != nil {
+ return nil, fmt.Errorf("invalid entry for %s: %s", ttype, err)
+ }
+ style.entries[ttype] = entry
+ }
+ return style, nil
+}
+
+// StyleEntries mapping TokenType to colour definition.
+type StyleEntries map[TokenType]string
+
+// NewXMLStyle parses an XML style definition.
+func NewXMLStyle(r io.Reader) (*Style, error) {
+ dec := xml.NewDecoder(r)
+ style := &Style{}
+ return style, dec.Decode(style)
+}
+
+// MustNewXMLStyle is like NewXMLStyle but panics on error.
+func MustNewXMLStyle(r io.Reader) *Style {
+ style, err := NewXMLStyle(r)
+ if err != nil {
+ panic(err)
+ }
+ return style
+}
+
+// NewStyle creates a new style definition.
+func NewStyle(name string, entries StyleEntries) (*Style, error) {
+ return NewStyleBuilder(name).AddAll(entries).Build()
+}
+
+// MustNewStyle creates a new style or panics.
+func MustNewStyle(name string, entries StyleEntries) *Style {
+ style, err := NewStyle(name, entries)
+ if err != nil {
+ panic(err)
+ }
+ return style
+}
+
+// A Style definition.
+//
+// See http://pygments.org/docs/styles/ for details. Semantics are intended to be identical.
+type Style struct {
+ Name string
+ entries map[TokenType]StyleEntry
+ parent *Style
+}
+
+func (s *Style) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if s.parent != nil {
+ return fmt.Errorf("cannot marshal style with parent")
+ }
+ start.Name = xml.Name{Local: "style"}
+ start.Attr = []xml.Attr{{Name: xml.Name{Local: "name"}, Value: s.Name}}
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+ sorted := make([]TokenType, 0, len(s.entries))
+ for ttype := range s.entries {
+ sorted = append(sorted, ttype)
+ }
+ sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
+ for _, ttype := range sorted {
+ entry := s.entries[ttype]
+ el := xml.StartElement{Name: xml.Name{Local: "entry"}}
+ el.Attr = []xml.Attr{
+ {Name: xml.Name{Local: "type"}, Value: ttype.String()},
+ {Name: xml.Name{Local: "style"}, Value: entry.String()},
+ }
+ if err := e.EncodeToken(el); err != nil {
+ return err
+ }
+ if err := e.EncodeToken(xml.EndElement{Name: el.Name}); err != nil {
+ return err
+ }
+ }
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+func (s *Style) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ for _, attr := range start.Attr {
+ if attr.Name.Local == "name" {
+ s.Name = attr.Value
+ } else {
+ return fmt.Errorf("unexpected attribute %s", attr.Name.Local)
+ }
+ }
+ if s.Name == "" {
+ return fmt.Errorf("missing style name attribute")
+ }
+ s.entries = map[TokenType]StyleEntry{}
+ for {
+ tok, err := d.Token()
+ if err != nil {
+ return err
+ }
+ switch el := tok.(type) {
+ case xml.StartElement:
+ if el.Name.Local != "entry" {
+ return fmt.Errorf("unexpected element %s", el.Name.Local)
+ }
+ var ttype TokenType
+ var entry StyleEntry
+ for _, attr := range el.Attr {
+ switch attr.Name.Local {
+ case "type":
+ ttype, err = TokenTypeString(attr.Value)
+ if err != nil {
+ return err
+ }
+
+ case "style":
+ entry, err = ParseStyleEntry(attr.Value)
+ if err != nil {
+ return err
+ }
+
+ default:
+ return fmt.Errorf("unexpected attribute %s", attr.Name.Local)
+ }
+ }
+ s.entries[ttype] = entry
+
+ case xml.EndElement:
+ if el.Name.Local == start.Name.Local {
+ return nil
+ }
+ }
+ }
+}
+
+// Types that are styled.
+func (s *Style) Types() []TokenType {
+ dedupe := map[TokenType]bool{}
+ for tt := range s.entries {
+ dedupe[tt] = true
+ }
+ if s.parent != nil {
+ for _, tt := range s.parent.Types() {
+ dedupe[tt] = true
+ }
+ }
+ out := make([]TokenType, 0, len(dedupe))
+ for tt := range dedupe {
+ out = append(out, tt)
+ }
+ return out
+}
+
+// Builder creates a mutable builder from this Style.
+//
+// The builder can then be safely modified. This is a cheap operation.
+func (s *Style) Builder() *StyleBuilder {
+ return &StyleBuilder{
+ name: s.Name,
+ entries: map[TokenType]string{},
+ parent: s,
+ }
+}
+
+// Has checks if an exact style entry match exists for a token type.
+//
+// This is distinct from Get() which will merge parent tokens.
+func (s *Style) Has(ttype TokenType) bool {
+ return !s.get(ttype).IsZero() || s.synthesisable(ttype)
+}
+
+// Get a style entry. Will try sub-category or category if an exact match is not found, and
+// finally return the Background.
+func (s *Style) Get(ttype TokenType) StyleEntry {
+ return s.get(ttype).Inherit(
+ s.get(Background),
+ s.get(Text),
+ s.get(ttype.Category()),
+ s.get(ttype.SubCategory()))
+}
+
+func (s *Style) get(ttype TokenType) StyleEntry {
+ out := s.entries[ttype]
+ if out.IsZero() && s.parent != nil {
+ return s.parent.get(ttype)
+ }
+ if out.IsZero() && s.synthesisable(ttype) {
+ out = s.synthesise(ttype)
+ }
+ return out
+}
+
+func (s *Style) synthesise(ttype TokenType) StyleEntry {
+ bg := s.get(Background)
+ text := StyleEntry{Colour: bg.Colour}
+ text.Colour = text.Colour.BrightenOrDarken(0.5)
+
+ switch ttype {
+ // If we don't have a line highlight colour, make one that is 10% brighter/darker than the background.
+ case LineHighlight:
+ return StyleEntry{Background: bg.Background.BrightenOrDarken(0.1)}
+
+ // If we don't have line numbers, use the text colour but 20% brighter/darker
+ case LineNumbers, LineNumbersTable:
+ return text
+
+ default:
+ return StyleEntry{}
+ }
+}
+
+func (s *Style) synthesisable(ttype TokenType) bool {
+ return ttype == LineHighlight || ttype == LineNumbers || ttype == LineNumbersTable
+}
+
+// MustParseStyleEntry parses a Pygments style entry or panics.
+func MustParseStyleEntry(entry string) StyleEntry {
+ out, err := ParseStyleEntry(entry)
+ if err != nil {
+ panic(err)
+ }
+ return out
+}
+
+// ParseStyleEntry parses a Pygments style entry.
+func ParseStyleEntry(entry string) (StyleEntry, error) { // nolint: gocyclo
+ out := StyleEntry{}
+ parts := strings.Fields(entry)
+ for _, part := range parts {
+ switch {
+ case part == "italic":
+ out.Italic = Yes
+ case part == "noitalic":
+ out.Italic = No
+ case part == "bold":
+ out.Bold = Yes
+ case part == "nobold":
+ out.Bold = No
+ case part == "underline":
+ out.Underline = Yes
+ case part == "nounderline":
+ out.Underline = No
+ case part == "inherit":
+ out.NoInherit = false
+ case part == "noinherit":
+ out.NoInherit = true
+ case part == "bg:":
+ out.Background = 0
+ case strings.HasPrefix(part, "bg:#"):
+ out.Background = ParseColour(part[3:])
+ if !out.Background.IsSet() {
+ return StyleEntry{}, fmt.Errorf("invalid background colour %q", part)
+ }
+ case strings.HasPrefix(part, "border:#"):
+ out.Border = ParseColour(part[7:])
+ if !out.Border.IsSet() {
+ return StyleEntry{}, fmt.Errorf("invalid border colour %q", part)
+ }
+ case strings.HasPrefix(part, "#"):
+ out.Colour = ParseColour(part)
+ if !out.Colour.IsSet() {
+ return StyleEntry{}, fmt.Errorf("invalid colour %q", part)
+ }
+ default:
+ return StyleEntry{}, fmt.Errorf("unknown style element %q", part)
+ }
+ }
+ return out, nil
+}
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/abap.xml b/vendor/github.com/alecthomas/chroma/v2/styles/abap.xml
new file mode 100644
index 00000000..36ea2f1d
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/abap.xml
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/algol.xml b/vendor/github.com/alecthomas/chroma/v2/styles/algol.xml
new file mode 100644
index 00000000..e8a6dc1b
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/algol.xml
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/algol_nu.xml b/vendor/github.com/alecthomas/chroma/v2/styles/algol_nu.xml
new file mode 100644
index 00000000..7fa340f3
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/algol_nu.xml
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/api.go b/vendor/github.com/alecthomas/chroma/v2/styles/api.go
new file mode 100644
index 00000000..e26d6f0a
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/api.go
@@ -0,0 +1,65 @@
+package styles
+
+import (
+ "embed"
+ "io/fs"
+ "sort"
+
+ "github.com/alecthomas/chroma/v2"
+)
+
+//go:embed *.xml
+var embedded embed.FS
+
+// Registry of Styles.
+var Registry = func() map[string]*chroma.Style {
+ registry := map[string]*chroma.Style{}
+ // Register all embedded styles.
+ files, err := fs.ReadDir(embedded, ".")
+ if err != nil {
+ panic(err)
+ }
+ for _, file := range files {
+ if file.IsDir() {
+ continue
+ }
+ r, err := embedded.Open(file.Name())
+ if err != nil {
+ panic(err)
+ }
+ style, err := chroma.NewXMLStyle(r)
+ if err != nil {
+ panic(err)
+ }
+ registry[style.Name] = style
+ _ = r.Close()
+ }
+ return registry
+}()
+
+// Fallback style. Reassign to change the default fallback style.
+var Fallback = Registry["swapoff"]
+
+// Register a chroma.Style.
+func Register(style *chroma.Style) *chroma.Style {
+ Registry[style.Name] = style
+ return style
+}
+
+// Names of all available styles.
+func Names() []string {
+ out := []string{}
+ for name := range Registry {
+ out = append(out, name)
+ }
+ sort.Strings(out)
+ return out
+}
+
+// Get named style, or Fallback.
+func Get(name string) *chroma.Style {
+ if style, ok := Registry[name]; ok {
+ return style
+ }
+ return Fallback
+}
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/arduino.xml b/vendor/github.com/alecthomas/chroma/v2/styles/arduino.xml
new file mode 100644
index 00000000..d9891dc5
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/arduino.xml
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/autumn.xml b/vendor/github.com/alecthomas/chroma/v2/styles/autumn.xml
new file mode 100644
index 00000000..74d2eae9
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/autumn.xml
@@ -0,0 +1,36 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/average.xml b/vendor/github.com/alecthomas/chroma/v2/styles/average.xml
new file mode 100644
index 00000000..79bdb95f
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/average.xml
@@ -0,0 +1,74 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/base16-snazzy.xml b/vendor/github.com/alecthomas/chroma/v2/styles/base16-snazzy.xml
new file mode 100644
index 00000000..a05ba24e
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/base16-snazzy.xml
@@ -0,0 +1,74 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/borland.xml b/vendor/github.com/alecthomas/chroma/v2/styles/borland.xml
new file mode 100644
index 00000000..0d8f574c
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/borland.xml
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/bw.xml b/vendor/github.com/alecthomas/chroma/v2/styles/bw.xml
new file mode 100644
index 00000000..fb0e868d
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/bw.xml
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-frappe.xml b/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-frappe.xml
new file mode 100644
index 00000000..0adf1ba9
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-frappe.xml
@@ -0,0 +1,83 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-latte.xml b/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-latte.xml
new file mode 100644
index 00000000..3ea767fd
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-latte.xml
@@ -0,0 +1,83 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-macchiato.xml b/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-macchiato.xml
new file mode 100644
index 00000000..6b500284
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-macchiato.xml
@@ -0,0 +1,83 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-mocha.xml b/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-mocha.xml
new file mode 100644
index 00000000..9a401912
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-mocha.xml
@@ -0,0 +1,83 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/colorful.xml b/vendor/github.com/alecthomas/chroma/v2/styles/colorful.xml
new file mode 100644
index 00000000..32442d71
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/colorful.xml
@@ -0,0 +1,52 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/compat.go b/vendor/github.com/alecthomas/chroma/v2/styles/compat.go
new file mode 100644
index 00000000..4a6aaa66
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/compat.go
@@ -0,0 +1,66 @@
+package styles
+
+// Present for backwards compatibility.
+//
+// Deprecated: use styles.Get(name) instead.
+var (
+ Abap = Registry["abap"]
+ Algol = Registry["algol"]
+ AlgolNu = Registry["algol_nu"]
+ Arduino = Registry["arduino"]
+ Autumn = Registry["autumn"]
+ Average = Registry["average"]
+ Base16Snazzy = Registry["base16-snazzy"]
+ Borland = Registry["borland"]
+ BlackWhite = Registry["bw"]
+ CatppuccinFrappe = Registry["catppuccin-frappe"]
+ CatppuccinLatte = Registry["catppuccin-latte"]
+ CatppuccinMacchiato = Registry["catppuccin-macchiato"]
+ CatppuccinMocha = Registry["catppuccin-mocha"]
+ Colorful = Registry["colorful"]
+ DoomOne = Registry["doom-one"]
+ DoomOne2 = Registry["doom-one2"]
+ Dracula = Registry["dracula"]
+ Emacs = Registry["emacs"]
+ Friendly = Registry["friendly"]
+ Fruity = Registry["fruity"]
+ GitHubDark = Registry["github-dark"]
+ GitHub = Registry["github"]
+ GruvboxLight = Registry["gruvbox-light"]
+ Gruvbox = Registry["gruvbox"]
+ HrDark = Registry["hrdark"]
+ HrHighContrast = Registry["hr_high_contrast"]
+ Igor = Registry["igor"]
+ Lovelace = Registry["lovelace"]
+ Manni = Registry["manni"]
+ ModusOperandi = Registry["modus-operandi"]
+ ModusVivendi = Registry["modus-vivendi"]
+ Monokai = Registry["monokai"]
+ MonokaiLight = Registry["monokailight"]
+ Murphy = Registry["murphy"]
+ Native = Registry["native"]
+ Nord = Registry["nord"]
+ OnesEnterprise = Registry["onesenterprise"]
+ ParaisoDark = Registry["paraiso-dark"]
+ ParaisoLight = Registry["paraiso-light"]
+ Pastie = Registry["pastie"]
+ Perldoc = Registry["perldoc"]
+ Pygments = Registry["pygments"]
+ RainbowDash = Registry["rainbow_dash"]
+ RosePineDawn = Registry["rose-pine-dawn"]
+ RosePineMoon = Registry["rose-pine-moon"]
+ RosePine = Registry["rose-pine"]
+ Rrt = Registry["rrt"]
+ SolarizedDark = Registry["solarized-dark"]
+ SolarizedDark256 = Registry["solarized-dark256"]
+ SolarizedLight = Registry["solarized-light"]
+ SwapOff = Registry["swapoff"]
+ Tango = Registry["tango"]
+ Trac = Registry["trac"]
+ Vim = Registry["vim"]
+ VisualStudio = Registry["vs"]
+ Vulcan = Registry["vulcan"]
+ WitchHazel = Registry["witchhazel"]
+ XcodeDark = Registry["xcode-dark"]
+ Xcode = Registry["xcode"]
+)
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/doom-one.xml b/vendor/github.com/alecthomas/chroma/v2/styles/doom-one.xml
new file mode 100644
index 00000000..1f5127ef
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/doom-one.xml
@@ -0,0 +1,51 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/doom-one2.xml b/vendor/github.com/alecthomas/chroma/v2/styles/doom-one2.xml
new file mode 100644
index 00000000..f47debaf
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/doom-one2.xml
@@ -0,0 +1,64 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/dracula.xml b/vendor/github.com/alecthomas/chroma/v2/styles/dracula.xml
new file mode 100644
index 00000000..9df7da11
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/dracula.xml
@@ -0,0 +1,74 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/emacs.xml b/vendor/github.com/alecthomas/chroma/v2/styles/emacs.xml
new file mode 100644
index 00000000..981ce8e4
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/emacs.xml
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/friendly.xml b/vendor/github.com/alecthomas/chroma/v2/styles/friendly.xml
new file mode 100644
index 00000000..f4980104
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/friendly.xml
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/fruity.xml b/vendor/github.com/alecthomas/chroma/v2/styles/fruity.xml
new file mode 100644
index 00000000..bcc06aa7
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/fruity.xml
@@ -0,0 +1,19 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/github-dark.xml b/vendor/github.com/alecthomas/chroma/v2/styles/github-dark.xml
new file mode 100644
index 00000000..711aeafc
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/github-dark.xml
@@ -0,0 +1,45 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/github.xml b/vendor/github.com/alecthomas/chroma/v2/styles/github.xml
new file mode 100644
index 00000000..e7caee7b
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/github.xml
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/gruvbox-light.xml b/vendor/github.com/alecthomas/chroma/v2/styles/gruvbox-light.xml
new file mode 100644
index 00000000..8c4f0642
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/gruvbox-light.xml
@@ -0,0 +1,33 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/gruvbox.xml b/vendor/github.com/alecthomas/chroma/v2/styles/gruvbox.xml
new file mode 100644
index 00000000..2f6a0a2a
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/gruvbox.xml
@@ -0,0 +1,33 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/hr_high_contrast.xml b/vendor/github.com/alecthomas/chroma/v2/styles/hr_high_contrast.xml
new file mode 100644
index 00000000..61cde204
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/hr_high_contrast.xml
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/hrdark.xml b/vendor/github.com/alecthomas/chroma/v2/styles/hrdark.xml
new file mode 100644
index 00000000..bc7a6f31
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/hrdark.xml
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/igor.xml b/vendor/github.com/alecthomas/chroma/v2/styles/igor.xml
new file mode 100644
index 00000000..773c83b6
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/igor.xml
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/lovelace.xml b/vendor/github.com/alecthomas/chroma/v2/styles/lovelace.xml
new file mode 100644
index 00000000..e336c930
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/lovelace.xml
@@ -0,0 +1,53 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/manni.xml b/vendor/github.com/alecthomas/chroma/v2/styles/manni.xml
new file mode 100644
index 00000000..99324bd3
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/manni.xml
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/modus-operandi.xml b/vendor/github.com/alecthomas/chroma/v2/styles/modus-operandi.xml
new file mode 100644
index 00000000..023137aa
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/modus-operandi.xml
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/modus-vivendi.xml b/vendor/github.com/alecthomas/chroma/v2/styles/modus-vivendi.xml
new file mode 100644
index 00000000..8da663dc
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/modus-vivendi.xml
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/monokai.xml b/vendor/github.com/alecthomas/chroma/v2/styles/monokai.xml
new file mode 100644
index 00000000..1a789dde
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/monokai.xml
@@ -0,0 +1,29 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/monokailight.xml b/vendor/github.com/alecthomas/chroma/v2/styles/monokailight.xml
new file mode 100644
index 00000000..85cd23e0
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/monokailight.xml
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/murphy.xml b/vendor/github.com/alecthomas/chroma/v2/styles/murphy.xml
new file mode 100644
index 00000000..112d6205
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/murphy.xml
@@ -0,0 +1,52 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/native.xml b/vendor/github.com/alecthomas/chroma/v2/styles/native.xml
new file mode 100644
index 00000000..43eea7fd
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/native.xml
@@ -0,0 +1,35 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/nord.xml b/vendor/github.com/alecthomas/chroma/v2/styles/nord.xml
new file mode 100644
index 00000000..1c1d1ffb
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/nord.xml
@@ -0,0 +1,46 @@
+
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/onedark.xml b/vendor/github.com/alecthomas/chroma/v2/styles/onedark.xml
new file mode 100644
index 00000000..6921eb5e
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/onedark.xml
@@ -0,0 +1,25 @@
+
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/onesenterprise.xml b/vendor/github.com/alecthomas/chroma/v2/styles/onesenterprise.xml
new file mode 100644
index 00000000..ce86db3f
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/onesenterprise.xml
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/paraiso-dark.xml b/vendor/github.com/alecthomas/chroma/v2/styles/paraiso-dark.xml
new file mode 100644
index 00000000..788db3f7
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/paraiso-dark.xml
@@ -0,0 +1,37 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/paraiso-light.xml b/vendor/github.com/alecthomas/chroma/v2/styles/paraiso-light.xml
new file mode 100644
index 00000000..06a63bae
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/paraiso-light.xml
@@ -0,0 +1,37 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/pastie.xml b/vendor/github.com/alecthomas/chroma/v2/styles/pastie.xml
new file mode 100644
index 00000000..a3b0abde
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/pastie.xml
@@ -0,0 +1,45 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/perldoc.xml b/vendor/github.com/alecthomas/chroma/v2/styles/perldoc.xml
new file mode 100644
index 00000000..9e5564c3
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/perldoc.xml
@@ -0,0 +1,37 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/pygments.xml b/vendor/github.com/alecthomas/chroma/v2/styles/pygments.xml
new file mode 100644
index 00000000..a3d0d8ba
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/pygments.xml
@@ -0,0 +1,42 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/rainbow_dash.xml b/vendor/github.com/alecthomas/chroma/v2/styles/rainbow_dash.xml
new file mode 100644
index 00000000..5b0fe49d
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/rainbow_dash.xml
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-dawn.xml b/vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-dawn.xml
new file mode 100644
index 00000000..788bd6f6
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-dawn.xml
@@ -0,0 +1,29 @@
+
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-moon.xml b/vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-moon.xml
new file mode 100644
index 00000000..f67b8043
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-moon.xml
@@ -0,0 +1,29 @@
+
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/rose-pine.xml b/vendor/github.com/alecthomas/chroma/v2/styles/rose-pine.xml
new file mode 100644
index 00000000..3fb70a5a
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/rose-pine.xml
@@ -0,0 +1,29 @@
+
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/rrt.xml b/vendor/github.com/alecthomas/chroma/v2/styles/rrt.xml
new file mode 100644
index 00000000..5f1daaa2
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/rrt.xml
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark.xml b/vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark.xml
new file mode 100644
index 00000000..a3cf46fd
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark.xml
@@ -0,0 +1,39 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark256.xml b/vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark256.xml
new file mode 100644
index 00000000..977cfbe3
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark256.xml
@@ -0,0 +1,41 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/solarized-light.xml b/vendor/github.com/alecthomas/chroma/v2/styles/solarized-light.xml
new file mode 100644
index 00000000..4fbc1d4a
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/solarized-light.xml
@@ -0,0 +1,17 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/swapoff.xml b/vendor/github.com/alecthomas/chroma/v2/styles/swapoff.xml
new file mode 100644
index 00000000..8a398df8
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/swapoff.xml
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/tango.xml b/vendor/github.com/alecthomas/chroma/v2/styles/tango.xml
new file mode 100644
index 00000000..5ca46bb7
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/tango.xml
@@ -0,0 +1,72 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-day.xml b/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-day.xml
new file mode 100644
index 00000000..c20d9a41
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-day.xml
@@ -0,0 +1,83 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-moon.xml b/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-moon.xml
new file mode 100644
index 00000000..3312f029
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-moon.xml
@@ -0,0 +1,83 @@
+
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-night.xml b/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-night.xml
new file mode 100644
index 00000000..c798bad4
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-night.xml
@@ -0,0 +1,83 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-storm.xml b/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-storm.xml
new file mode 100644
index 00000000..c0811524
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-storm.xml
@@ -0,0 +1,83 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/trac.xml b/vendor/github.com/alecthomas/chroma/v2/styles/trac.xml
new file mode 100644
index 00000000..9f1d2667
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/trac.xml
@@ -0,0 +1,35 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/vim.xml b/vendor/github.com/alecthomas/chroma/v2/styles/vim.xml
new file mode 100644
index 00000000..fec69343
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/vim.xml
@@ -0,0 +1,29 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/vs.xml b/vendor/github.com/alecthomas/chroma/v2/styles/vs.xml
new file mode 100644
index 00000000..56435015
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/vs.xml
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/vulcan.xml b/vendor/github.com/alecthomas/chroma/v2/styles/vulcan.xml
new file mode 100644
index 00000000..4e690945
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/vulcan.xml
@@ -0,0 +1,74 @@
+
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/witchhazel.xml b/vendor/github.com/alecthomas/chroma/v2/styles/witchhazel.xml
new file mode 100644
index 00000000..52f22991
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/witchhazel.xml
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/xcode-dark.xml b/vendor/github.com/alecthomas/chroma/v2/styles/xcode-dark.xml
new file mode 100644
index 00000000..93439791
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/xcode-dark.xml
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/styles/xcode.xml b/vendor/github.com/alecthomas/chroma/v2/styles/xcode.xml
new file mode 100644
index 00000000..523d746c
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/styles/xcode.xml
@@ -0,0 +1,22 @@
+
\ No newline at end of file
diff --git a/vendor/github.com/alecthomas/chroma/v2/table.py b/vendor/github.com/alecthomas/chroma/v2/table.py
new file mode 100644
index 00000000..ea4b7556
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/table.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+import re
+from collections import defaultdict
+from subprocess import check_output
+
+README_FILE = "README.md"
+
+lines = check_output(["chroma", "--list"]).decode("utf-8").splitlines()
+lines = [line.strip() for line in lines if line.startswith(" ") and not line.startswith(" ")]
+lines = sorted(lines, key=lambda l: l.lower())
+
+table = defaultdict(list)
+
+for line in lines:
+ table[line[0].upper()].append(line)
+
+rows = []
+for key, value in table.items():
+ rows.append("{} | {}".format(key, ", ".join(value)))
+tbody = "\n".join(rows)
+
+with open(README_FILE, "r") as f:
+ content = f.read()
+
+with open(README_FILE, "w") as f:
+ marker = re.compile(r"(?P:----: \\| --------\n).*?(?P\n\n)", re.DOTALL)
+ replacement = r"\g%s\g" % tbody
+ updated_content = marker.sub(replacement, content)
+ f.write(updated_content)
+
+print(tbody)
diff --git a/vendor/github.com/alecthomas/chroma/v2/tokentype_enumer.go b/vendor/github.com/alecthomas/chroma/v2/tokentype_enumer.go
new file mode 100644
index 00000000..696e9ce5
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/tokentype_enumer.go
@@ -0,0 +1,573 @@
+// Code generated by "enumer -text -type TokenType"; DO NOT EDIT.
+
+package chroma
+
+import (
+ "fmt"
+ "strings"
+)
+
+const _TokenTypeName = "NoneOtherErrorCodeLineLineLinkLineTableTDLineTableLineHighlightLineNumbersTableLineNumbersLinePreWrapperBackgroundEOFTypeKeywordKeywordConstantKeywordDeclarationKeywordNamespaceKeywordPseudoKeywordReservedKeywordTypeNameNameAttributeNameBuiltinNameBuiltinPseudoNameClassNameConstantNameDecoratorNameEntityNameExceptionNameFunctionNameFunctionMagicNameKeywordNameLabelNameNamespaceNameOperatorNameOtherNamePseudoNamePropertyNameTagNameVariableNameVariableAnonymousNameVariableClassNameVariableGlobalNameVariableInstanceNameVariableMagicLiteralLiteralDateLiteralOtherLiteralStringLiteralStringAffixLiteralStringAtomLiteralStringBacktickLiteralStringBooleanLiteralStringCharLiteralStringDelimiterLiteralStringDocLiteralStringDoubleLiteralStringEscapeLiteralStringHeredocLiteralStringInterpolLiteralStringNameLiteralStringOtherLiteralStringRegexLiteralStringSingleLiteralStringSymbolLiteralNumberLiteralNumberBinLiteralNumberFloatLiteralNumberHexLiteralNumberIntegerLiteralNumberIntegerLongLiteralNumberOctOperatorOperatorWordPunctuationCommentCommentHashbangCommentMultilineCommentSingleCommentSpecialCommentPreprocCommentPreprocFileGenericGenericDeletedGenericEmphGenericErrorGenericHeadingGenericInsertedGenericOutputGenericPromptGenericStrongGenericSubheadingGenericTracebackGenericUnderlineTextTextWhitespaceTextSymbolTextPunctuation"
+const _TokenTypeLowerName = "noneothererrorcodelinelinelinklinetabletdlinetablelinehighlightlinenumberstablelinenumberslineprewrapperbackgroundeoftypekeywordkeywordconstantkeyworddeclarationkeywordnamespacekeywordpseudokeywordreservedkeywordtypenamenameattributenamebuiltinnamebuiltinpseudonameclassnameconstantnamedecoratornameentitynameexceptionnamefunctionnamefunctionmagicnamekeywordnamelabelnamenamespacenameoperatornameothernamepseudonamepropertynametagnamevariablenamevariableanonymousnamevariableclassnamevariableglobalnamevariableinstancenamevariablemagicliteralliteraldateliteralotherliteralstringliteralstringaffixliteralstringatomliteralstringbacktickliteralstringbooleanliteralstringcharliteralstringdelimiterliteralstringdocliteralstringdoubleliteralstringescapeliteralstringheredocliteralstringinterpolliteralstringnameliteralstringotherliteralstringregexliteralstringsingleliteralstringsymbolliteralnumberliteralnumberbinliteralnumberfloatliteralnumberhexliteralnumberintegerliteralnumberintegerlongliteralnumberoctoperatoroperatorwordpunctuationcommentcommenthashbangcommentmultilinecommentsinglecommentspecialcommentpreproccommentpreprocfilegenericgenericdeletedgenericemphgenericerrorgenericheadinggenericinsertedgenericoutputgenericpromptgenericstronggenericsubheadinggenerictracebackgenericunderlinetexttextwhitespacetextsymboltextpunctuation"
+
+var _TokenTypeMap = map[TokenType]string{
+ -13: _TokenTypeName[0:4],
+ -12: _TokenTypeName[4:9],
+ -11: _TokenTypeName[9:14],
+ -10: _TokenTypeName[14:22],
+ -9: _TokenTypeName[22:30],
+ -8: _TokenTypeName[30:41],
+ -7: _TokenTypeName[41:50],
+ -6: _TokenTypeName[50:63],
+ -5: _TokenTypeName[63:79],
+ -4: _TokenTypeName[79:90],
+ -3: _TokenTypeName[90:94],
+ -2: _TokenTypeName[94:104],
+ -1: _TokenTypeName[104:114],
+ 0: _TokenTypeName[114:121],
+ 1000: _TokenTypeName[121:128],
+ 1001: _TokenTypeName[128:143],
+ 1002: _TokenTypeName[143:161],
+ 1003: _TokenTypeName[161:177],
+ 1004: _TokenTypeName[177:190],
+ 1005: _TokenTypeName[190:205],
+ 1006: _TokenTypeName[205:216],
+ 2000: _TokenTypeName[216:220],
+ 2001: _TokenTypeName[220:233],
+ 2002: _TokenTypeName[233:244],
+ 2003: _TokenTypeName[244:261],
+ 2004: _TokenTypeName[261:270],
+ 2005: _TokenTypeName[270:282],
+ 2006: _TokenTypeName[282:295],
+ 2007: _TokenTypeName[295:305],
+ 2008: _TokenTypeName[305:318],
+ 2009: _TokenTypeName[318:330],
+ 2010: _TokenTypeName[330:347],
+ 2011: _TokenTypeName[347:358],
+ 2012: _TokenTypeName[358:367],
+ 2013: _TokenTypeName[367:380],
+ 2014: _TokenTypeName[380:392],
+ 2015: _TokenTypeName[392:401],
+ 2016: _TokenTypeName[401:411],
+ 2017: _TokenTypeName[411:423],
+ 2018: _TokenTypeName[423:430],
+ 2019: _TokenTypeName[430:442],
+ 2020: _TokenTypeName[442:463],
+ 2021: _TokenTypeName[463:480],
+ 2022: _TokenTypeName[480:498],
+ 2023: _TokenTypeName[498:518],
+ 2024: _TokenTypeName[518:535],
+ 3000: _TokenTypeName[535:542],
+ 3001: _TokenTypeName[542:553],
+ 3002: _TokenTypeName[553:565],
+ 3100: _TokenTypeName[565:578],
+ 3101: _TokenTypeName[578:596],
+ 3102: _TokenTypeName[596:613],
+ 3103: _TokenTypeName[613:634],
+ 3104: _TokenTypeName[634:654],
+ 3105: _TokenTypeName[654:671],
+ 3106: _TokenTypeName[671:693],
+ 3107: _TokenTypeName[693:709],
+ 3108: _TokenTypeName[709:728],
+ 3109: _TokenTypeName[728:747],
+ 3110: _TokenTypeName[747:767],
+ 3111: _TokenTypeName[767:788],
+ 3112: _TokenTypeName[788:805],
+ 3113: _TokenTypeName[805:823],
+ 3114: _TokenTypeName[823:841],
+ 3115: _TokenTypeName[841:860],
+ 3116: _TokenTypeName[860:879],
+ 3200: _TokenTypeName[879:892],
+ 3201: _TokenTypeName[892:908],
+ 3202: _TokenTypeName[908:926],
+ 3203: _TokenTypeName[926:942],
+ 3204: _TokenTypeName[942:962],
+ 3205: _TokenTypeName[962:986],
+ 3206: _TokenTypeName[986:1002],
+ 4000: _TokenTypeName[1002:1010],
+ 4001: _TokenTypeName[1010:1022],
+ 5000: _TokenTypeName[1022:1033],
+ 6000: _TokenTypeName[1033:1040],
+ 6001: _TokenTypeName[1040:1055],
+ 6002: _TokenTypeName[1055:1071],
+ 6003: _TokenTypeName[1071:1084],
+ 6004: _TokenTypeName[1084:1098],
+ 6100: _TokenTypeName[1098:1112],
+ 6101: _TokenTypeName[1112:1130],
+ 7000: _TokenTypeName[1130:1137],
+ 7001: _TokenTypeName[1137:1151],
+ 7002: _TokenTypeName[1151:1162],
+ 7003: _TokenTypeName[1162:1174],
+ 7004: _TokenTypeName[1174:1188],
+ 7005: _TokenTypeName[1188:1203],
+ 7006: _TokenTypeName[1203:1216],
+ 7007: _TokenTypeName[1216:1229],
+ 7008: _TokenTypeName[1229:1242],
+ 7009: _TokenTypeName[1242:1259],
+ 7010: _TokenTypeName[1259:1275],
+ 7011: _TokenTypeName[1275:1291],
+ 8000: _TokenTypeName[1291:1295],
+ 8001: _TokenTypeName[1295:1309],
+ 8002: _TokenTypeName[1309:1319],
+ 8003: _TokenTypeName[1319:1334],
+}
+
+func (i TokenType) String() string {
+ if str, ok := _TokenTypeMap[i]; ok {
+ return str
+ }
+ return fmt.Sprintf("TokenType(%d)", i)
+}
+
+// An "invalid array index" compiler error signifies that the constant values have changed.
+// Re-run the stringer command to generate them again.
+func _TokenTypeNoOp() {
+ var x [1]struct{}
+ _ = x[None-(-13)]
+ _ = x[Other-(-12)]
+ _ = x[Error-(-11)]
+ _ = x[CodeLine-(-10)]
+ _ = x[LineLink-(-9)]
+ _ = x[LineTableTD-(-8)]
+ _ = x[LineTable-(-7)]
+ _ = x[LineHighlight-(-6)]
+ _ = x[LineNumbersTable-(-5)]
+ _ = x[LineNumbers-(-4)]
+ _ = x[Line-(-3)]
+ _ = x[PreWrapper-(-2)]
+ _ = x[Background-(-1)]
+ _ = x[EOFType-(0)]
+ _ = x[Keyword-(1000)]
+ _ = x[KeywordConstant-(1001)]
+ _ = x[KeywordDeclaration-(1002)]
+ _ = x[KeywordNamespace-(1003)]
+ _ = x[KeywordPseudo-(1004)]
+ _ = x[KeywordReserved-(1005)]
+ _ = x[KeywordType-(1006)]
+ _ = x[Name-(2000)]
+ _ = x[NameAttribute-(2001)]
+ _ = x[NameBuiltin-(2002)]
+ _ = x[NameBuiltinPseudo-(2003)]
+ _ = x[NameClass-(2004)]
+ _ = x[NameConstant-(2005)]
+ _ = x[NameDecorator-(2006)]
+ _ = x[NameEntity-(2007)]
+ _ = x[NameException-(2008)]
+ _ = x[NameFunction-(2009)]
+ _ = x[NameFunctionMagic-(2010)]
+ _ = x[NameKeyword-(2011)]
+ _ = x[NameLabel-(2012)]
+ _ = x[NameNamespace-(2013)]
+ _ = x[NameOperator-(2014)]
+ _ = x[NameOther-(2015)]
+ _ = x[NamePseudo-(2016)]
+ _ = x[NameProperty-(2017)]
+ _ = x[NameTag-(2018)]
+ _ = x[NameVariable-(2019)]
+ _ = x[NameVariableAnonymous-(2020)]
+ _ = x[NameVariableClass-(2021)]
+ _ = x[NameVariableGlobal-(2022)]
+ _ = x[NameVariableInstance-(2023)]
+ _ = x[NameVariableMagic-(2024)]
+ _ = x[Literal-(3000)]
+ _ = x[LiteralDate-(3001)]
+ _ = x[LiteralOther-(3002)]
+ _ = x[LiteralString-(3100)]
+ _ = x[LiteralStringAffix-(3101)]
+ _ = x[LiteralStringAtom-(3102)]
+ _ = x[LiteralStringBacktick-(3103)]
+ _ = x[LiteralStringBoolean-(3104)]
+ _ = x[LiteralStringChar-(3105)]
+ _ = x[LiteralStringDelimiter-(3106)]
+ _ = x[LiteralStringDoc-(3107)]
+ _ = x[LiteralStringDouble-(3108)]
+ _ = x[LiteralStringEscape-(3109)]
+ _ = x[LiteralStringHeredoc-(3110)]
+ _ = x[LiteralStringInterpol-(3111)]
+ _ = x[LiteralStringName-(3112)]
+ _ = x[LiteralStringOther-(3113)]
+ _ = x[LiteralStringRegex-(3114)]
+ _ = x[LiteralStringSingle-(3115)]
+ _ = x[LiteralStringSymbol-(3116)]
+ _ = x[LiteralNumber-(3200)]
+ _ = x[LiteralNumberBin-(3201)]
+ _ = x[LiteralNumberFloat-(3202)]
+ _ = x[LiteralNumberHex-(3203)]
+ _ = x[LiteralNumberInteger-(3204)]
+ _ = x[LiteralNumberIntegerLong-(3205)]
+ _ = x[LiteralNumberOct-(3206)]
+ _ = x[Operator-(4000)]
+ _ = x[OperatorWord-(4001)]
+ _ = x[Punctuation-(5000)]
+ _ = x[Comment-(6000)]
+ _ = x[CommentHashbang-(6001)]
+ _ = x[CommentMultiline-(6002)]
+ _ = x[CommentSingle-(6003)]
+ _ = x[CommentSpecial-(6004)]
+ _ = x[CommentPreproc-(6100)]
+ _ = x[CommentPreprocFile-(6101)]
+ _ = x[Generic-(7000)]
+ _ = x[GenericDeleted-(7001)]
+ _ = x[GenericEmph-(7002)]
+ _ = x[GenericError-(7003)]
+ _ = x[GenericHeading-(7004)]
+ _ = x[GenericInserted-(7005)]
+ _ = x[GenericOutput-(7006)]
+ _ = x[GenericPrompt-(7007)]
+ _ = x[GenericStrong-(7008)]
+ _ = x[GenericSubheading-(7009)]
+ _ = x[GenericTraceback-(7010)]
+ _ = x[GenericUnderline-(7011)]
+ _ = x[Text-(8000)]
+ _ = x[TextWhitespace-(8001)]
+ _ = x[TextSymbol-(8002)]
+ _ = x[TextPunctuation-(8003)]
+}
+
+var _TokenTypeValues = []TokenType{None, Other, Error, CodeLine, LineLink, LineTableTD, LineTable, LineHighlight, LineNumbersTable, LineNumbers, Line, PreWrapper, Background, EOFType, Keyword, KeywordConstant, KeywordDeclaration, KeywordNamespace, KeywordPseudo, KeywordReserved, KeywordType, Name, NameAttribute, NameBuiltin, NameBuiltinPseudo, NameClass, NameConstant, NameDecorator, NameEntity, NameException, NameFunction, NameFunctionMagic, NameKeyword, NameLabel, NameNamespace, NameOperator, NameOther, NamePseudo, NameProperty, NameTag, NameVariable, NameVariableAnonymous, NameVariableClass, NameVariableGlobal, NameVariableInstance, NameVariableMagic, Literal, LiteralDate, LiteralOther, LiteralString, LiteralStringAffix, LiteralStringAtom, LiteralStringBacktick, LiteralStringBoolean, LiteralStringChar, LiteralStringDelimiter, LiteralStringDoc, LiteralStringDouble, LiteralStringEscape, LiteralStringHeredoc, LiteralStringInterpol, LiteralStringName, LiteralStringOther, LiteralStringRegex, LiteralStringSingle, LiteralStringSymbol, LiteralNumber, LiteralNumberBin, LiteralNumberFloat, LiteralNumberHex, LiteralNumberInteger, LiteralNumberIntegerLong, LiteralNumberOct, Operator, OperatorWord, Punctuation, Comment, CommentHashbang, CommentMultiline, CommentSingle, CommentSpecial, CommentPreproc, CommentPreprocFile, Generic, GenericDeleted, GenericEmph, GenericError, GenericHeading, GenericInserted, GenericOutput, GenericPrompt, GenericStrong, GenericSubheading, GenericTraceback, GenericUnderline, Text, TextWhitespace, TextSymbol, TextPunctuation}
+
+var _TokenTypeNameToValueMap = map[string]TokenType{
+ _TokenTypeName[0:4]: None,
+ _TokenTypeLowerName[0:4]: None,
+ _TokenTypeName[4:9]: Other,
+ _TokenTypeLowerName[4:9]: Other,
+ _TokenTypeName[9:14]: Error,
+ _TokenTypeLowerName[9:14]: Error,
+ _TokenTypeName[14:22]: CodeLine,
+ _TokenTypeLowerName[14:22]: CodeLine,
+ _TokenTypeName[22:30]: LineLink,
+ _TokenTypeLowerName[22:30]: LineLink,
+ _TokenTypeName[30:41]: LineTableTD,
+ _TokenTypeLowerName[30:41]: LineTableTD,
+ _TokenTypeName[41:50]: LineTable,
+ _TokenTypeLowerName[41:50]: LineTable,
+ _TokenTypeName[50:63]: LineHighlight,
+ _TokenTypeLowerName[50:63]: LineHighlight,
+ _TokenTypeName[63:79]: LineNumbersTable,
+ _TokenTypeLowerName[63:79]: LineNumbersTable,
+ _TokenTypeName[79:90]: LineNumbers,
+ _TokenTypeLowerName[79:90]: LineNumbers,
+ _TokenTypeName[90:94]: Line,
+ _TokenTypeLowerName[90:94]: Line,
+ _TokenTypeName[94:104]: PreWrapper,
+ _TokenTypeLowerName[94:104]: PreWrapper,
+ _TokenTypeName[104:114]: Background,
+ _TokenTypeLowerName[104:114]: Background,
+ _TokenTypeName[114:121]: EOFType,
+ _TokenTypeLowerName[114:121]: EOFType,
+ _TokenTypeName[121:128]: Keyword,
+ _TokenTypeLowerName[121:128]: Keyword,
+ _TokenTypeName[128:143]: KeywordConstant,
+ _TokenTypeLowerName[128:143]: KeywordConstant,
+ _TokenTypeName[143:161]: KeywordDeclaration,
+ _TokenTypeLowerName[143:161]: KeywordDeclaration,
+ _TokenTypeName[161:177]: KeywordNamespace,
+ _TokenTypeLowerName[161:177]: KeywordNamespace,
+ _TokenTypeName[177:190]: KeywordPseudo,
+ _TokenTypeLowerName[177:190]: KeywordPseudo,
+ _TokenTypeName[190:205]: KeywordReserved,
+ _TokenTypeLowerName[190:205]: KeywordReserved,
+ _TokenTypeName[205:216]: KeywordType,
+ _TokenTypeLowerName[205:216]: KeywordType,
+ _TokenTypeName[216:220]: Name,
+ _TokenTypeLowerName[216:220]: Name,
+ _TokenTypeName[220:233]: NameAttribute,
+ _TokenTypeLowerName[220:233]: NameAttribute,
+ _TokenTypeName[233:244]: NameBuiltin,
+ _TokenTypeLowerName[233:244]: NameBuiltin,
+ _TokenTypeName[244:261]: NameBuiltinPseudo,
+ _TokenTypeLowerName[244:261]: NameBuiltinPseudo,
+ _TokenTypeName[261:270]: NameClass,
+ _TokenTypeLowerName[261:270]: NameClass,
+ _TokenTypeName[270:282]: NameConstant,
+ _TokenTypeLowerName[270:282]: NameConstant,
+ _TokenTypeName[282:295]: NameDecorator,
+ _TokenTypeLowerName[282:295]: NameDecorator,
+ _TokenTypeName[295:305]: NameEntity,
+ _TokenTypeLowerName[295:305]: NameEntity,
+ _TokenTypeName[305:318]: NameException,
+ _TokenTypeLowerName[305:318]: NameException,
+ _TokenTypeName[318:330]: NameFunction,
+ _TokenTypeLowerName[318:330]: NameFunction,
+ _TokenTypeName[330:347]: NameFunctionMagic,
+ _TokenTypeLowerName[330:347]: NameFunctionMagic,
+ _TokenTypeName[347:358]: NameKeyword,
+ _TokenTypeLowerName[347:358]: NameKeyword,
+ _TokenTypeName[358:367]: NameLabel,
+ _TokenTypeLowerName[358:367]: NameLabel,
+ _TokenTypeName[367:380]: NameNamespace,
+ _TokenTypeLowerName[367:380]: NameNamespace,
+ _TokenTypeName[380:392]: NameOperator,
+ _TokenTypeLowerName[380:392]: NameOperator,
+ _TokenTypeName[392:401]: NameOther,
+ _TokenTypeLowerName[392:401]: NameOther,
+ _TokenTypeName[401:411]: NamePseudo,
+ _TokenTypeLowerName[401:411]: NamePseudo,
+ _TokenTypeName[411:423]: NameProperty,
+ _TokenTypeLowerName[411:423]: NameProperty,
+ _TokenTypeName[423:430]: NameTag,
+ _TokenTypeLowerName[423:430]: NameTag,
+ _TokenTypeName[430:442]: NameVariable,
+ _TokenTypeLowerName[430:442]: NameVariable,
+ _TokenTypeName[442:463]: NameVariableAnonymous,
+ _TokenTypeLowerName[442:463]: NameVariableAnonymous,
+ _TokenTypeName[463:480]: NameVariableClass,
+ _TokenTypeLowerName[463:480]: NameVariableClass,
+ _TokenTypeName[480:498]: NameVariableGlobal,
+ _TokenTypeLowerName[480:498]: NameVariableGlobal,
+ _TokenTypeName[498:518]: NameVariableInstance,
+ _TokenTypeLowerName[498:518]: NameVariableInstance,
+ _TokenTypeName[518:535]: NameVariableMagic,
+ _TokenTypeLowerName[518:535]: NameVariableMagic,
+ _TokenTypeName[535:542]: Literal,
+ _TokenTypeLowerName[535:542]: Literal,
+ _TokenTypeName[542:553]: LiteralDate,
+ _TokenTypeLowerName[542:553]: LiteralDate,
+ _TokenTypeName[553:565]: LiteralOther,
+ _TokenTypeLowerName[553:565]: LiteralOther,
+ _TokenTypeName[565:578]: LiteralString,
+ _TokenTypeLowerName[565:578]: LiteralString,
+ _TokenTypeName[578:596]: LiteralStringAffix,
+ _TokenTypeLowerName[578:596]: LiteralStringAffix,
+ _TokenTypeName[596:613]: LiteralStringAtom,
+ _TokenTypeLowerName[596:613]: LiteralStringAtom,
+ _TokenTypeName[613:634]: LiteralStringBacktick,
+ _TokenTypeLowerName[613:634]: LiteralStringBacktick,
+ _TokenTypeName[634:654]: LiteralStringBoolean,
+ _TokenTypeLowerName[634:654]: LiteralStringBoolean,
+ _TokenTypeName[654:671]: LiteralStringChar,
+ _TokenTypeLowerName[654:671]: LiteralStringChar,
+ _TokenTypeName[671:693]: LiteralStringDelimiter,
+ _TokenTypeLowerName[671:693]: LiteralStringDelimiter,
+ _TokenTypeName[693:709]: LiteralStringDoc,
+ _TokenTypeLowerName[693:709]: LiteralStringDoc,
+ _TokenTypeName[709:728]: LiteralStringDouble,
+ _TokenTypeLowerName[709:728]: LiteralStringDouble,
+ _TokenTypeName[728:747]: LiteralStringEscape,
+ _TokenTypeLowerName[728:747]: LiteralStringEscape,
+ _TokenTypeName[747:767]: LiteralStringHeredoc,
+ _TokenTypeLowerName[747:767]: LiteralStringHeredoc,
+ _TokenTypeName[767:788]: LiteralStringInterpol,
+ _TokenTypeLowerName[767:788]: LiteralStringInterpol,
+ _TokenTypeName[788:805]: LiteralStringName,
+ _TokenTypeLowerName[788:805]: LiteralStringName,
+ _TokenTypeName[805:823]: LiteralStringOther,
+ _TokenTypeLowerName[805:823]: LiteralStringOther,
+ _TokenTypeName[823:841]: LiteralStringRegex,
+ _TokenTypeLowerName[823:841]: LiteralStringRegex,
+ _TokenTypeName[841:860]: LiteralStringSingle,
+ _TokenTypeLowerName[841:860]: LiteralStringSingle,
+ _TokenTypeName[860:879]: LiteralStringSymbol,
+ _TokenTypeLowerName[860:879]: LiteralStringSymbol,
+ _TokenTypeName[879:892]: LiteralNumber,
+ _TokenTypeLowerName[879:892]: LiteralNumber,
+ _TokenTypeName[892:908]: LiteralNumberBin,
+ _TokenTypeLowerName[892:908]: LiteralNumberBin,
+ _TokenTypeName[908:926]: LiteralNumberFloat,
+ _TokenTypeLowerName[908:926]: LiteralNumberFloat,
+ _TokenTypeName[926:942]: LiteralNumberHex,
+ _TokenTypeLowerName[926:942]: LiteralNumberHex,
+ _TokenTypeName[942:962]: LiteralNumberInteger,
+ _TokenTypeLowerName[942:962]: LiteralNumberInteger,
+ _TokenTypeName[962:986]: LiteralNumberIntegerLong,
+ _TokenTypeLowerName[962:986]: LiteralNumberIntegerLong,
+ _TokenTypeName[986:1002]: LiteralNumberOct,
+ _TokenTypeLowerName[986:1002]: LiteralNumberOct,
+ _TokenTypeName[1002:1010]: Operator,
+ _TokenTypeLowerName[1002:1010]: Operator,
+ _TokenTypeName[1010:1022]: OperatorWord,
+ _TokenTypeLowerName[1010:1022]: OperatorWord,
+ _TokenTypeName[1022:1033]: Punctuation,
+ _TokenTypeLowerName[1022:1033]: Punctuation,
+ _TokenTypeName[1033:1040]: Comment,
+ _TokenTypeLowerName[1033:1040]: Comment,
+ _TokenTypeName[1040:1055]: CommentHashbang,
+ _TokenTypeLowerName[1040:1055]: CommentHashbang,
+ _TokenTypeName[1055:1071]: CommentMultiline,
+ _TokenTypeLowerName[1055:1071]: CommentMultiline,
+ _TokenTypeName[1071:1084]: CommentSingle,
+ _TokenTypeLowerName[1071:1084]: CommentSingle,
+ _TokenTypeName[1084:1098]: CommentSpecial,
+ _TokenTypeLowerName[1084:1098]: CommentSpecial,
+ _TokenTypeName[1098:1112]: CommentPreproc,
+ _TokenTypeLowerName[1098:1112]: CommentPreproc,
+ _TokenTypeName[1112:1130]: CommentPreprocFile,
+ _TokenTypeLowerName[1112:1130]: CommentPreprocFile,
+ _TokenTypeName[1130:1137]: Generic,
+ _TokenTypeLowerName[1130:1137]: Generic,
+ _TokenTypeName[1137:1151]: GenericDeleted,
+ _TokenTypeLowerName[1137:1151]: GenericDeleted,
+ _TokenTypeName[1151:1162]: GenericEmph,
+ _TokenTypeLowerName[1151:1162]: GenericEmph,
+ _TokenTypeName[1162:1174]: GenericError,
+ _TokenTypeLowerName[1162:1174]: GenericError,
+ _TokenTypeName[1174:1188]: GenericHeading,
+ _TokenTypeLowerName[1174:1188]: GenericHeading,
+ _TokenTypeName[1188:1203]: GenericInserted,
+ _TokenTypeLowerName[1188:1203]: GenericInserted,
+ _TokenTypeName[1203:1216]: GenericOutput,
+ _TokenTypeLowerName[1203:1216]: GenericOutput,
+ _TokenTypeName[1216:1229]: GenericPrompt,
+ _TokenTypeLowerName[1216:1229]: GenericPrompt,
+ _TokenTypeName[1229:1242]: GenericStrong,
+ _TokenTypeLowerName[1229:1242]: GenericStrong,
+ _TokenTypeName[1242:1259]: GenericSubheading,
+ _TokenTypeLowerName[1242:1259]: GenericSubheading,
+ _TokenTypeName[1259:1275]: GenericTraceback,
+ _TokenTypeLowerName[1259:1275]: GenericTraceback,
+ _TokenTypeName[1275:1291]: GenericUnderline,
+ _TokenTypeLowerName[1275:1291]: GenericUnderline,
+ _TokenTypeName[1291:1295]: Text,
+ _TokenTypeLowerName[1291:1295]: Text,
+ _TokenTypeName[1295:1309]: TextWhitespace,
+ _TokenTypeLowerName[1295:1309]: TextWhitespace,
+ _TokenTypeName[1309:1319]: TextSymbol,
+ _TokenTypeLowerName[1309:1319]: TextSymbol,
+ _TokenTypeName[1319:1334]: TextPunctuation,
+ _TokenTypeLowerName[1319:1334]: TextPunctuation,
+}
+
+var _TokenTypeNames = []string{
+ _TokenTypeName[0:4],
+ _TokenTypeName[4:9],
+ _TokenTypeName[9:14],
+ _TokenTypeName[14:22],
+ _TokenTypeName[22:30],
+ _TokenTypeName[30:41],
+ _TokenTypeName[41:50],
+ _TokenTypeName[50:63],
+ _TokenTypeName[63:79],
+ _TokenTypeName[79:90],
+ _TokenTypeName[90:94],
+ _TokenTypeName[94:104],
+ _TokenTypeName[104:114],
+ _TokenTypeName[114:121],
+ _TokenTypeName[121:128],
+ _TokenTypeName[128:143],
+ _TokenTypeName[143:161],
+ _TokenTypeName[161:177],
+ _TokenTypeName[177:190],
+ _TokenTypeName[190:205],
+ _TokenTypeName[205:216],
+ _TokenTypeName[216:220],
+ _TokenTypeName[220:233],
+ _TokenTypeName[233:244],
+ _TokenTypeName[244:261],
+ _TokenTypeName[261:270],
+ _TokenTypeName[270:282],
+ _TokenTypeName[282:295],
+ _TokenTypeName[295:305],
+ _TokenTypeName[305:318],
+ _TokenTypeName[318:330],
+ _TokenTypeName[330:347],
+ _TokenTypeName[347:358],
+ _TokenTypeName[358:367],
+ _TokenTypeName[367:380],
+ _TokenTypeName[380:392],
+ _TokenTypeName[392:401],
+ _TokenTypeName[401:411],
+ _TokenTypeName[411:423],
+ _TokenTypeName[423:430],
+ _TokenTypeName[430:442],
+ _TokenTypeName[442:463],
+ _TokenTypeName[463:480],
+ _TokenTypeName[480:498],
+ _TokenTypeName[498:518],
+ _TokenTypeName[518:535],
+ _TokenTypeName[535:542],
+ _TokenTypeName[542:553],
+ _TokenTypeName[553:565],
+ _TokenTypeName[565:578],
+ _TokenTypeName[578:596],
+ _TokenTypeName[596:613],
+ _TokenTypeName[613:634],
+ _TokenTypeName[634:654],
+ _TokenTypeName[654:671],
+ _TokenTypeName[671:693],
+ _TokenTypeName[693:709],
+ _TokenTypeName[709:728],
+ _TokenTypeName[728:747],
+ _TokenTypeName[747:767],
+ _TokenTypeName[767:788],
+ _TokenTypeName[788:805],
+ _TokenTypeName[805:823],
+ _TokenTypeName[823:841],
+ _TokenTypeName[841:860],
+ _TokenTypeName[860:879],
+ _TokenTypeName[879:892],
+ _TokenTypeName[892:908],
+ _TokenTypeName[908:926],
+ _TokenTypeName[926:942],
+ _TokenTypeName[942:962],
+ _TokenTypeName[962:986],
+ _TokenTypeName[986:1002],
+ _TokenTypeName[1002:1010],
+ _TokenTypeName[1010:1022],
+ _TokenTypeName[1022:1033],
+ _TokenTypeName[1033:1040],
+ _TokenTypeName[1040:1055],
+ _TokenTypeName[1055:1071],
+ _TokenTypeName[1071:1084],
+ _TokenTypeName[1084:1098],
+ _TokenTypeName[1098:1112],
+ _TokenTypeName[1112:1130],
+ _TokenTypeName[1130:1137],
+ _TokenTypeName[1137:1151],
+ _TokenTypeName[1151:1162],
+ _TokenTypeName[1162:1174],
+ _TokenTypeName[1174:1188],
+ _TokenTypeName[1188:1203],
+ _TokenTypeName[1203:1216],
+ _TokenTypeName[1216:1229],
+ _TokenTypeName[1229:1242],
+ _TokenTypeName[1242:1259],
+ _TokenTypeName[1259:1275],
+ _TokenTypeName[1275:1291],
+ _TokenTypeName[1291:1295],
+ _TokenTypeName[1295:1309],
+ _TokenTypeName[1309:1319],
+ _TokenTypeName[1319:1334],
+}
+
+// TokenTypeString retrieves an enum value from the enum constants string name.
+// Throws an error if the param is not part of the enum.
+func TokenTypeString(s string) (TokenType, error) {
+ if val, ok := _TokenTypeNameToValueMap[s]; ok {
+ return val, nil
+ }
+
+ if val, ok := _TokenTypeNameToValueMap[strings.ToLower(s)]; ok {
+ return val, nil
+ }
+ return 0, fmt.Errorf("%s does not belong to TokenType values", s)
+}
+
+// TokenTypeValues returns all values of the enum
+func TokenTypeValues() []TokenType {
+ return _TokenTypeValues
+}
+
+// TokenTypeStrings returns a slice of all String values of the enum
+func TokenTypeStrings() []string {
+ strs := make([]string, len(_TokenTypeNames))
+ copy(strs, _TokenTypeNames)
+ return strs
+}
+
+// IsATokenType returns "true" if the value is listed in the enum definition. "false" otherwise
+func (i TokenType) IsATokenType() bool {
+ _, ok := _TokenTypeMap[i]
+ return ok
+}
+
+// MarshalText implements the encoding.TextMarshaler interface for TokenType
+func (i TokenType) MarshalText() ([]byte, error) {
+ return []byte(i.String()), nil
+}
+
+// UnmarshalText implements the encoding.TextUnmarshaler interface for TokenType
+func (i *TokenType) UnmarshalText(text []byte) error {
+ var err error
+ *i, err = TokenTypeString(string(text))
+ return err
+}
diff --git a/vendor/github.com/alecthomas/chroma/v2/types.go b/vendor/github.com/alecthomas/chroma/v2/types.go
new file mode 100644
index 00000000..3d12310a
--- /dev/null
+++ b/vendor/github.com/alecthomas/chroma/v2/types.go
@@ -0,0 +1,340 @@
+package chroma
+
+//go:generate enumer -text -type TokenType
+
+// TokenType is the type of token to highlight.
+//
+// It is also an Emitter, emitting a single token of itself
+type TokenType int
+
+// Set of TokenTypes.
+//
+// Categories of types are grouped in ranges of 1000, while sub-categories are in ranges of 100. For
+// example, the literal category is in the range 3000-3999. The sub-category for literal strings is
+// in the range 3100-3199.
+
+// Meta token types.
+const (
+ // Default background style.
+ Background TokenType = -1 - iota
+ // PreWrapper style.
+ PreWrapper
+ // Line style.
+ Line
+ // Line numbers in output.
+ LineNumbers
+ // Line numbers in output when in table.
+ LineNumbersTable
+ // Line higlight style.
+ LineHighlight
+ // Line numbers table wrapper style.
+ LineTable
+ // Line numbers table TD wrapper style.
+ LineTableTD
+ // Line number links.
+ LineLink
+ // Code line wrapper style.
+ CodeLine
+ // Input that could not be tokenised.
+ Error
+ // Other is used by the Delegate lexer to indicate which tokens should be handled by the delegate.
+ Other
+ // No highlighting.
+ None
+ // Used as an EOF marker / nil token
+ EOFType TokenType = 0
+)
+
+// Keywords.
+const (
+ Keyword TokenType = 1000 + iota
+ KeywordConstant
+ KeywordDeclaration
+ KeywordNamespace
+ KeywordPseudo
+ KeywordReserved
+ KeywordType
+)
+
+// Names.
+const (
+ Name TokenType = 2000 + iota
+ NameAttribute
+ NameBuiltin
+ NameBuiltinPseudo
+ NameClass
+ NameConstant
+ NameDecorator
+ NameEntity
+ NameException
+ NameFunction
+ NameFunctionMagic
+ NameKeyword
+ NameLabel
+ NameNamespace
+ NameOperator
+ NameOther
+ NamePseudo
+ NameProperty
+ NameTag
+ NameVariable
+ NameVariableAnonymous
+ NameVariableClass
+ NameVariableGlobal
+ NameVariableInstance
+ NameVariableMagic
+)
+
+// Literals.
+const (
+ Literal TokenType = 3000 + iota
+ LiteralDate
+ LiteralOther
+)
+
+// Strings.
+const (
+ LiteralString TokenType = 3100 + iota
+ LiteralStringAffix
+ LiteralStringAtom
+ LiteralStringBacktick
+ LiteralStringBoolean
+ LiteralStringChar
+ LiteralStringDelimiter
+ LiteralStringDoc
+ LiteralStringDouble
+ LiteralStringEscape
+ LiteralStringHeredoc
+ LiteralStringInterpol
+ LiteralStringName
+ LiteralStringOther
+ LiteralStringRegex
+ LiteralStringSingle
+ LiteralStringSymbol
+)
+
+// Literals.
+const (
+ LiteralNumber TokenType = 3200 + iota
+ LiteralNumberBin
+ LiteralNumberFloat
+ LiteralNumberHex
+ LiteralNumberInteger
+ LiteralNumberIntegerLong
+ LiteralNumberOct
+)
+
+// Operators.
+const (
+ Operator TokenType = 4000 + iota
+ OperatorWord
+)
+
+// Punctuation.
+const (
+ Punctuation TokenType = 5000 + iota
+)
+
+// Comments.
+const (
+ Comment TokenType = 6000 + iota
+ CommentHashbang
+ CommentMultiline
+ CommentSingle
+ CommentSpecial
+)
+
+// Preprocessor "comments".
+const (
+ CommentPreproc TokenType = 6100 + iota
+ CommentPreprocFile
+)
+
+// Generic tokens.
+const (
+ Generic TokenType = 7000 + iota
+ GenericDeleted
+ GenericEmph
+ GenericError
+ GenericHeading
+ GenericInserted
+ GenericOutput
+ GenericPrompt
+ GenericStrong
+ GenericSubheading
+ GenericTraceback
+ GenericUnderline
+)
+
+// Text.
+const (
+ Text TokenType = 8000 + iota
+ TextWhitespace
+ TextSymbol
+ TextPunctuation
+)
+
+// Aliases.
+const (
+ Whitespace = TextWhitespace
+
+ Date = LiteralDate
+
+ String = LiteralString
+ StringAffix = LiteralStringAffix
+ StringBacktick = LiteralStringBacktick
+ StringChar = LiteralStringChar
+ StringDelimiter = LiteralStringDelimiter
+ StringDoc = LiteralStringDoc
+ StringDouble = LiteralStringDouble
+ StringEscape = LiteralStringEscape
+ StringHeredoc = LiteralStringHeredoc
+ StringInterpol = LiteralStringInterpol
+ StringOther = LiteralStringOther
+ StringRegex = LiteralStringRegex
+ StringSingle = LiteralStringSingle
+ StringSymbol = LiteralStringSymbol
+
+ Number = LiteralNumber
+ NumberBin = LiteralNumberBin
+ NumberFloat = LiteralNumberFloat
+ NumberHex = LiteralNumberHex
+ NumberInteger = LiteralNumberInteger
+ NumberIntegerLong = LiteralNumberIntegerLong
+ NumberOct = LiteralNumberOct
+)
+
+var (
+ StandardTypes = map[TokenType]string{
+ Background: "bg",
+ PreWrapper: "chroma",
+ Line: "line",
+ LineNumbers: "ln",
+ LineNumbersTable: "lnt",
+ LineHighlight: "hl",
+ LineTable: "lntable",
+ LineTableTD: "lntd",
+ LineLink: "lnlinks",
+ CodeLine: "cl",
+ Text: "",
+ Whitespace: "w",
+ Error: "err",
+ Other: "x",
+ // I have no idea what this is used for...
+ // Escape: "esc",
+
+ Keyword: "k",
+ KeywordConstant: "kc",
+ KeywordDeclaration: "kd",
+ KeywordNamespace: "kn",
+ KeywordPseudo: "kp",
+ KeywordReserved: "kr",
+ KeywordType: "kt",
+
+ Name: "n",
+ NameAttribute: "na",
+ NameBuiltin: "nb",
+ NameBuiltinPseudo: "bp",
+ NameClass: "nc",
+ NameConstant: "no",
+ NameDecorator: "nd",
+ NameEntity: "ni",
+ NameException: "ne",
+ NameFunction: "nf",
+ NameFunctionMagic: "fm",
+ NameProperty: "py",
+ NameLabel: "nl",
+ NameNamespace: "nn",
+ NameOther: "nx",
+ NameTag: "nt",
+ NameVariable: "nv",
+ NameVariableClass: "vc",
+ NameVariableGlobal: "vg",
+ NameVariableInstance: "vi",
+ NameVariableMagic: "vm",
+
+ Literal: "l",
+ LiteralDate: "ld",
+
+ String: "s",
+ StringAffix: "sa",
+ StringBacktick: "sb",
+ StringChar: "sc",
+ StringDelimiter: "dl",
+ StringDoc: "sd",
+ StringDouble: "s2",
+ StringEscape: "se",
+ StringHeredoc: "sh",
+ StringInterpol: "si",
+ StringOther: "sx",
+ StringRegex: "sr",
+ StringSingle: "s1",
+ StringSymbol: "ss",
+
+ Number: "m",
+ NumberBin: "mb",
+ NumberFloat: "mf",
+ NumberHex: "mh",
+ NumberInteger: "mi",
+ NumberIntegerLong: "il",
+ NumberOct: "mo",
+
+ Operator: "o",
+ OperatorWord: "ow",
+
+ Punctuation: "p",
+
+ Comment: "c",
+ CommentHashbang: "ch",
+ CommentMultiline: "cm",
+ CommentPreproc: "cp",
+ CommentPreprocFile: "cpf",
+ CommentSingle: "c1",
+ CommentSpecial: "cs",
+
+ Generic: "g",
+ GenericDeleted: "gd",
+ GenericEmph: "ge",
+ GenericError: "gr",
+ GenericHeading: "gh",
+ GenericInserted: "gi",
+ GenericOutput: "go",
+ GenericPrompt: "gp",
+ GenericStrong: "gs",
+ GenericSubheading: "gu",
+ GenericTraceback: "gt",
+ GenericUnderline: "gl",
+ }
+)
+
+func (t TokenType) Parent() TokenType {
+ if t%100 != 0 {
+ return t / 100 * 100
+ }
+ if t%1000 != 0 {
+ return t / 1000 * 1000
+ }
+ return 0
+}
+
+func (t TokenType) Category() TokenType {
+ return t / 1000 * 1000
+}
+
+func (t TokenType) SubCategory() TokenType {
+ return t / 100 * 100
+}
+
+func (t TokenType) InCategory(other TokenType) bool {
+ return t/1000 == other/1000
+}
+
+func (t TokenType) InSubCategory(other TokenType) bool {
+ return t/100 == other/100
+}
+
+func (t TokenType) Emit(groups []string, _ *LexerState) Iterator {
+ return Literator(Token{Type: t, Value: groups[0]})
+}
+
+func (t TokenType) EmitterKind() string { return "token" }
diff --git a/vendor/github.com/aymanbagabas/go-osc52/v2/LICENSE b/vendor/github.com/aymanbagabas/go-osc52/v2/LICENSE
new file mode 100644
index 00000000..25cec1ed
--- /dev/null
+++ b/vendor/github.com/aymanbagabas/go-osc52/v2/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Ayman Bagabas
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/aymanbagabas/go-osc52/v2/README.md b/vendor/github.com/aymanbagabas/go-osc52/v2/README.md
new file mode 100644
index 00000000..4de3a22d
--- /dev/null
+++ b/vendor/github.com/aymanbagabas/go-osc52/v2/README.md
@@ -0,0 +1,83 @@
+
+# go-osc52
+
+
+
+A Go library to work with the [ANSI OSC52](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands) terminal sequence.
+
+## Usage
+
+You can use this small library to construct an ANSI OSC52 sequence suitable for
+your terminal.
+
+
+### Example
+
+```go
+import (
+ "os"
+ "fmt"
+
+ "github.com/aymanbagabas/go-osc52/v2"
+)
+
+func main() {
+ s := "Hello World!"
+
+ // Copy `s` to system clipboard
+ osc52.New(s).WriteTo(os.Stderr)
+
+ // Copy `s` to primary clipboard (X11)
+ osc52.New(s).Primary().WriteTo(os.Stderr)
+
+ // Query the clipboard
+ osc52.Query().WriteTo(os.Stderr)
+
+ // Clear system clipboard
+ osc52.Clear().WriteTo(os.Stderr)
+
+ // Use the fmt.Stringer interface to copy `s` to system clipboard
+ fmt.Fprint(os.Stderr, osc52.New(s))
+
+ // Or to primary clipboard
+ fmt.Fprint(os.Stderr, osc52.New(s).Primary())
+}
+```
+
+## SSH Example
+
+You can use this over SSH using [gliderlabs/ssh](https://github.com/gliderlabs/ssh) for instance:
+
+```go
+var sshSession ssh.Session
+seq := osc52.New("Hello awesome!")
+// Check if term is screen or tmux
+pty, _, _ := s.Pty()
+if pty.Term == "screen" {
+ seq = seq.Screen()
+} else if isTmux {
+ seq = seq.Tmux()
+}
+seq.WriteTo(sshSession.Stderr())
+```
+
+## Tmux
+
+Make sure you have `set-clipboard on` in your config, otherwise, tmux won't
+allow your application to access the clipboard [^1].
+
+Using the tmux option, `osc52.TmuxMode` or `osc52.New(...).Tmux()`, wraps the
+OSC52 sequence in a special tmux DCS sequence and pass it to the outer
+terminal. This requires `allow-passthrough on` in your config.
+`allow-passthrough` is no longer enabled by default
+[since tmux 3.3a](https://github.com/tmux/tmux/issues/3218#issuecomment-1153089282) [^2].
+
+[^1]: See [tmux clipboard](https://github.com/tmux/tmux/wiki/Clipboard)
+[^2]: [What is allow-passthrough](https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it)
+
+## Credits
+
+* [vim-oscyank](https://github.com/ojroques/vim-oscyank) this is heavily inspired by vim-oscyank.
diff --git a/vendor/github.com/aymanbagabas/go-osc52/v2/osc52.go b/vendor/github.com/aymanbagabas/go-osc52/v2/osc52.go
new file mode 100644
index 00000000..dc758d28
--- /dev/null
+++ b/vendor/github.com/aymanbagabas/go-osc52/v2/osc52.go
@@ -0,0 +1,305 @@
+// OSC52 is a terminal escape sequence that allows copying text to the clipboard.
+//
+// The sequence consists of the following:
+//
+// OSC 52 ; Pc ; Pd BEL
+//
+// Pc is the clipboard choice:
+//
+// c: clipboard
+// p: primary
+// q: secondary (not supported)
+// s: select (not supported)
+// 0-7: cut-buffers (not supported)
+//
+// Pd is the data to copy to the clipboard. This string should be encoded in
+// base64 (RFC-4648).
+//
+// If Pd is "?", the terminal replies to the host with the current contents of
+// the clipboard.
+//
+// If Pd is neither a base64 string nor "?", the terminal clears the clipboard.
+//
+// See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+// where Ps = 52 => Manipulate Selection Data.
+//
+// Examples:
+//
+// // copy "hello world" to the system clipboard
+// fmt.Fprint(os.Stderr, osc52.New("hello world"))
+//
+// // copy "hello world" to the primary Clipboard
+// fmt.Fprint(os.Stderr, osc52.New("hello world").Primary())
+//
+// // limit the size of the string to copy 10 bytes
+// fmt.Fprint(os.Stderr, osc52.New("0123456789").Limit(10))
+//
+// // escape the OSC52 sequence for screen using DCS sequences
+// fmt.Fprint(os.Stderr, osc52.New("hello world").Screen())
+//
+// // escape the OSC52 sequence for Tmux
+// fmt.Fprint(os.Stderr, osc52.New("hello world").Tmux())
+//
+// // query the system Clipboard
+// fmt.Fprint(os.Stderr, osc52.Query())
+//
+// // query the primary clipboard
+// fmt.Fprint(os.Stderr, osc52.Query().Primary())
+//
+// // clear the system Clipboard
+// fmt.Fprint(os.Stderr, osc52.Clear())
+//
+// // clear the primary Clipboard
+// fmt.Fprint(os.Stderr, osc52.Clear().Primary())
+package osc52
+
+import (
+ "encoding/base64"
+ "fmt"
+ "io"
+ "strings"
+)
+
+// Clipboard is the clipboard buffer to use.
+type Clipboard rune
+
+const (
+ // SystemClipboard is the system clipboard buffer.
+ SystemClipboard Clipboard = 'c'
+ // PrimaryClipboard is the primary clipboard buffer (X11).
+ PrimaryClipboard = 'p'
+)
+
+// Mode is the mode to use for the OSC52 sequence.
+type Mode uint
+
+const (
+ // DefaultMode is the default OSC52 sequence mode.
+ DefaultMode Mode = iota
+ // ScreenMode escapes the OSC52 sequence for screen using DCS sequences.
+ ScreenMode
+ // TmuxMode escapes the OSC52 sequence for tmux. Not needed if tmux
+ // clipboard is set to `set-clipboard on`
+ TmuxMode
+)
+
+// Operation is the OSC52 operation.
+type Operation uint
+
+const (
+ // SetOperation is the copy operation.
+ SetOperation Operation = iota
+ // QueryOperation is the query operation.
+ QueryOperation
+ // ClearOperation is the clear operation.
+ ClearOperation
+)
+
+// Sequence is the OSC52 sequence.
+type Sequence struct {
+ str string
+ limit int
+ op Operation
+ mode Mode
+ clipboard Clipboard
+}
+
+var _ fmt.Stringer = Sequence{}
+
+var _ io.WriterTo = Sequence{}
+
+// String returns the OSC52 sequence.
+func (s Sequence) String() string {
+ var seq strings.Builder
+ // mode escape sequences start
+ seq.WriteString(s.seqStart())
+ // actual OSC52 sequence start
+ seq.WriteString(fmt.Sprintf("\x1b]52;%c;", s.clipboard))
+ switch s.op {
+ case SetOperation:
+ str := s.str
+ if s.limit > 0 && len(str) > s.limit {
+ return ""
+ }
+ b64 := base64.StdEncoding.EncodeToString([]byte(str))
+ switch s.mode {
+ case ScreenMode:
+ // Screen doesn't support OSC52 but will pass the contents of a DCS
+ // sequence to the outer terminal unchanged.
+ //
+ // Here, we split the encoded string into 76 bytes chunks and then
+ // join the chunks with sequences. Finally,
+ // wrap the whole thing in
+ // .
+ // s := strings.SplitN(b64, "", 76)
+ s := make([]string, 0, len(b64)/76+1)
+ for i := 0; i < len(b64); i += 76 {
+ end := i + 76
+ if end > len(b64) {
+ end = len(b64)
+ }
+ s = append(s, b64[i:end])
+ }
+ seq.WriteString(strings.Join(s, "\x1b\\\x1bP"))
+ default:
+ seq.WriteString(b64)
+ }
+ case QueryOperation:
+ // OSC52 queries the clipboard using "?"
+ seq.WriteString("?")
+ case ClearOperation:
+ // OSC52 clears the clipboard if the data is neither a base64 string nor "?"
+ // we're using "!" as a default
+ seq.WriteString("!")
+ }
+ // actual OSC52 sequence end
+ seq.WriteString("\x07")
+ // mode escape end
+ seq.WriteString(s.seqEnd())
+ return seq.String()
+}
+
+// WriteTo writes the OSC52 sequence to the writer.
+func (s Sequence) WriteTo(out io.Writer) (int64, error) {
+ n, err := out.Write([]byte(s.String()))
+ return int64(n), err
+}
+
+// Mode sets the mode for the OSC52 sequence.
+func (s Sequence) Mode(m Mode) Sequence {
+ s.mode = m
+ return s
+}
+
+// Tmux sets the mode to TmuxMode.
+// Used to escape the OSC52 sequence for `tmux`.
+//
+// Note: this is not needed if tmux clipboard is set to `set-clipboard on`. If
+// TmuxMode is used, tmux must have `allow-passthrough on` set.
+//
+// This is a syntactic sugar for s.Mode(TmuxMode).
+func (s Sequence) Tmux() Sequence {
+ return s.Mode(TmuxMode)
+}
+
+// Screen sets the mode to ScreenMode.
+// Used to escape the OSC52 sequence for `screen`.
+//
+// This is a syntactic sugar for s.Mode(ScreenMode).
+func (s Sequence) Screen() Sequence {
+ return s.Mode(ScreenMode)
+}
+
+// Clipboard sets the clipboard buffer for the OSC52 sequence.
+func (s Sequence) Clipboard(c Clipboard) Sequence {
+ s.clipboard = c
+ return s
+}
+
+// Primary sets the clipboard buffer to PrimaryClipboard.
+// This is the X11 primary clipboard.
+//
+// This is a syntactic sugar for s.Clipboard(PrimaryClipboard).
+func (s Sequence) Primary() Sequence {
+ return s.Clipboard(PrimaryClipboard)
+}
+
+// Limit sets the limit for the OSC52 sequence.
+// The default limit is 0 (no limit).
+//
+// Strings longer than the limit get ignored. Settting the limit to 0 or a
+// negative value disables the limit. Each terminal defines its own escapse
+// sequence limit.
+func (s Sequence) Limit(l int) Sequence {
+ if l < 0 {
+ s.limit = 0
+ } else {
+ s.limit = l
+ }
+ return s
+}
+
+// Operation sets the operation for the OSC52 sequence.
+// The default operation is SetOperation.
+func (s Sequence) Operation(o Operation) Sequence {
+ s.op = o
+ return s
+}
+
+// Clear sets the operation to ClearOperation.
+// This clears the clipboard.
+//
+// This is a syntactic sugar for s.Operation(ClearOperation).
+func (s Sequence) Clear() Sequence {
+ return s.Operation(ClearOperation)
+}
+
+// Query sets the operation to QueryOperation.
+// This queries the clipboard contents.
+//
+// This is a syntactic sugar for s.Operation(QueryOperation).
+func (s Sequence) Query() Sequence {
+ return s.Operation(QueryOperation)
+}
+
+// SetString sets the string for the OSC52 sequence. Strings are joined with a
+// space character.
+func (s Sequence) SetString(strs ...string) Sequence {
+ s.str = strings.Join(strs, " ")
+ return s
+}
+
+// New creates a new OSC52 sequence with the given string(s). Strings are
+// joined with a space character.
+func New(strs ...string) Sequence {
+ s := Sequence{
+ str: strings.Join(strs, " "),
+ limit: 0,
+ mode: DefaultMode,
+ clipboard: SystemClipboard,
+ op: SetOperation,
+ }
+ return s
+}
+
+// Query creates a new OSC52 sequence with the QueryOperation.
+// This returns a new OSC52 sequence to query the clipboard contents.
+//
+// This is a syntactic sugar for New().Query().
+func Query() Sequence {
+ return New().Query()
+}
+
+// Clear creates a new OSC52 sequence with the ClearOperation.
+// This returns a new OSC52 sequence to clear the clipboard.
+//
+// This is a syntactic sugar for New().Clear().
+func Clear() Sequence {
+ return New().Clear()
+}
+
+func (s Sequence) seqStart() string {
+ switch s.mode {
+ case TmuxMode:
+ // Write the start of a tmux escape sequence.
+ return "\x1bPtmux;\x1b"
+ case ScreenMode:
+ // Write the start of a DCS sequence.
+ return "\x1bP"
+ default:
+ return ""
+ }
+}
+
+func (s Sequence) seqEnd() string {
+ switch s.mode {
+ case TmuxMode:
+ // Terminate the tmux escape sequence.
+ return "\x1b\\"
+ case ScreenMode:
+ // Write the end of a DCS sequence.
+ return "\x1b\x5c"
+ default:
+ return ""
+ }
+}
diff --git a/vendor/github.com/aymerick/douceur/LICENSE b/vendor/github.com/aymerick/douceur/LICENSE
new file mode 100644
index 00000000..6ce87cd3
--- /dev/null
+++ b/vendor/github.com/aymerick/douceur/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Aymerick JEHANNE
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/github.com/aymerick/douceur/css/declaration.go b/vendor/github.com/aymerick/douceur/css/declaration.go
new file mode 100644
index 00000000..61d29d33
--- /dev/null
+++ b/vendor/github.com/aymerick/douceur/css/declaration.go
@@ -0,0 +1,60 @@
+package css
+
+import "fmt"
+
+// Declaration represents a parsed style property
+type Declaration struct {
+ Property string
+ Value string
+ Important bool
+}
+
+// NewDeclaration instanciates a new Declaration
+func NewDeclaration() *Declaration {
+ return &Declaration{}
+}
+
+// Returns string representation of the Declaration
+func (decl *Declaration) String() string {
+ return decl.StringWithImportant(true)
+}
+
+// StringWithImportant returns string representation with optional !important part
+func (decl *Declaration) StringWithImportant(option bool) string {
+ result := fmt.Sprintf("%s: %s", decl.Property, decl.Value)
+
+ if option && decl.Important {
+ result += " !important"
+ }
+
+ result += ";"
+
+ return result
+}
+
+// Equal returns true if both Declarations are equals
+func (decl *Declaration) Equal(other *Declaration) bool {
+ return (decl.Property == other.Property) && (decl.Value == other.Value) && (decl.Important == other.Important)
+}
+
+//
+// DeclarationsByProperty
+//
+
+// DeclarationsByProperty represents sortable style declarations
+type DeclarationsByProperty []*Declaration
+
+// Implements sort.Interface
+func (declarations DeclarationsByProperty) Len() int {
+ return len(declarations)
+}
+
+// Implements sort.Interface
+func (declarations DeclarationsByProperty) Swap(i, j int) {
+ declarations[i], declarations[j] = declarations[j], declarations[i]
+}
+
+// Implements sort.Interface
+func (declarations DeclarationsByProperty) Less(i, j int) bool {
+ return declarations[i].Property < declarations[j].Property
+}
diff --git a/vendor/github.com/aymerick/douceur/css/rule.go b/vendor/github.com/aymerick/douceur/css/rule.go
new file mode 100644
index 00000000..b5a44b54
--- /dev/null
+++ b/vendor/github.com/aymerick/douceur/css/rule.go
@@ -0,0 +1,230 @@
+package css
+
+import (
+ "fmt"
+ "strings"
+)
+
+const (
+ indentSpace = 2
+)
+
+// RuleKind represents a Rule kind
+type RuleKind int
+
+// Rule kinds
+const (
+ QualifiedRule RuleKind = iota
+ AtRule
+)
+
+// At Rules than have Rules inside their block instead of Declarations
+var atRulesWithRulesBlock = []string{
+ "@document", "@font-feature-values", "@keyframes", "@media", "@supports",
+}
+
+// Rule represents a parsed CSS rule
+type Rule struct {
+ Kind RuleKind
+
+ // At Rule name (eg: "@media")
+ Name string
+
+ // Raw prelude
+ Prelude string
+
+ // Qualified Rule selectors parsed from prelude
+ Selectors []string
+
+ // Style properties
+ Declarations []*Declaration
+
+ // At Rule embedded rules
+ Rules []*Rule
+
+ // Current rule embedding level
+ EmbedLevel int
+}
+
+// NewRule instanciates a new Rule
+func NewRule(kind RuleKind) *Rule {
+ return &Rule{
+ Kind: kind,
+ }
+}
+
+// Returns string representation of rule kind
+func (kind RuleKind) String() string {
+ switch kind {
+ case QualifiedRule:
+ return "Qualified Rule"
+ case AtRule:
+ return "At Rule"
+ default:
+ return "WAT"
+ }
+}
+
+// EmbedsRules returns true if this rule embeds another rules
+func (rule *Rule) EmbedsRules() bool {
+ if rule.Kind == AtRule {
+ for _, atRuleName := range atRulesWithRulesBlock {
+ if rule.Name == atRuleName {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+// Equal returns true if both rules are equals
+func (rule *Rule) Equal(other *Rule) bool {
+ if (rule.Kind != other.Kind) ||
+ (rule.Prelude != other.Prelude) ||
+ (rule.Name != other.Name) {
+ return false
+ }
+
+ if (len(rule.Selectors) != len(other.Selectors)) ||
+ (len(rule.Declarations) != len(other.Declarations)) ||
+ (len(rule.Rules) != len(other.Rules)) {
+ return false
+ }
+
+ for i, sel := range rule.Selectors {
+ if sel != other.Selectors[i] {
+ return false
+ }
+ }
+
+ for i, decl := range rule.Declarations {
+ if !decl.Equal(other.Declarations[i]) {
+ return false
+ }
+ }
+
+ for i, rule := range rule.Rules {
+ if !rule.Equal(other.Rules[i]) {
+ return false
+ }
+ }
+
+ return true
+}
+
+// Diff returns a string representation of rules differences
+func (rule *Rule) Diff(other *Rule) []string {
+ result := []string{}
+
+ if rule.Kind != other.Kind {
+ result = append(result, fmt.Sprintf("Kind: %s | %s", rule.Kind.String(), other.Kind.String()))
+ }
+
+ if rule.Prelude != other.Prelude {
+ result = append(result, fmt.Sprintf("Prelude: \"%s\" | \"%s\"", rule.Prelude, other.Prelude))
+ }
+
+ if rule.Name != other.Name {
+ result = append(result, fmt.Sprintf("Name: \"%s\" | \"%s\"", rule.Name, other.Name))
+ }
+
+ if len(rule.Selectors) != len(other.Selectors) {
+ result = append(result, fmt.Sprintf("Selectors: %v | %v", strings.Join(rule.Selectors, ", "), strings.Join(other.Selectors, ", ")))
+ } else {
+ for i, sel := range rule.Selectors {
+ if sel != other.Selectors[i] {
+ result = append(result, fmt.Sprintf("Selector: \"%s\" | \"%s\"", sel, other.Selectors[i]))
+ }
+ }
+ }
+
+ if len(rule.Declarations) != len(other.Declarations) {
+ result = append(result, fmt.Sprintf("Declarations Nb: %d | %d", len(rule.Declarations), len(other.Declarations)))
+ } else {
+ for i, decl := range rule.Declarations {
+ if !decl.Equal(other.Declarations[i]) {
+ result = append(result, fmt.Sprintf("Declaration: \"%s\" | \"%s\"", decl.String(), other.Declarations[i].String()))
+ }
+ }
+ }
+
+ if len(rule.Rules) != len(other.Rules) {
+ result = append(result, fmt.Sprintf("Rules Nb: %d | %d", len(rule.Rules), len(other.Rules)))
+ } else {
+
+ for i, rule := range rule.Rules {
+ if !rule.Equal(other.Rules[i]) {
+ result = append(result, fmt.Sprintf("Rule: \"%s\" | \"%s\"", rule.String(), other.Rules[i].String()))
+ }
+ }
+ }
+
+ return result
+}
+
+// Returns the string representation of a rule
+func (rule *Rule) String() string {
+ result := ""
+
+ if rule.Kind == QualifiedRule {
+ for i, sel := range rule.Selectors {
+ if i != 0 {
+ result += ", "
+ }
+ result += sel
+ }
+ } else {
+ // AtRule
+ result += fmt.Sprintf("%s", rule.Name)
+
+ if rule.Prelude != "" {
+ if result != "" {
+ result += " "
+ }
+ result += fmt.Sprintf("%s", rule.Prelude)
+ }
+ }
+
+ if (len(rule.Declarations) == 0) && (len(rule.Rules) == 0) {
+ result += ";"
+ } else {
+ result += " {\n"
+
+ if rule.EmbedsRules() {
+ for _, subRule := range rule.Rules {
+ result += fmt.Sprintf("%s%s\n", rule.indent(), subRule.String())
+ }
+ } else {
+ for _, decl := range rule.Declarations {
+ result += fmt.Sprintf("%s%s\n", rule.indent(), decl.String())
+ }
+ }
+
+ result += fmt.Sprintf("%s}", rule.indentEndBlock())
+ }
+
+ return result
+}
+
+// Returns identation spaces for declarations and rules
+func (rule *Rule) indent() string {
+ result := ""
+
+ for i := 0; i < ((rule.EmbedLevel + 1) * indentSpace); i++ {
+ result += " "
+ }
+
+ return result
+}
+
+// Returns identation spaces for end of block character
+func (rule *Rule) indentEndBlock() string {
+ result := ""
+
+ for i := 0; i < (rule.EmbedLevel * indentSpace); i++ {
+ result += " "
+ }
+
+ return result
+}
diff --git a/vendor/github.com/aymerick/douceur/css/stylesheet.go b/vendor/github.com/aymerick/douceur/css/stylesheet.go
new file mode 100644
index 00000000..6b32c2ec
--- /dev/null
+++ b/vendor/github.com/aymerick/douceur/css/stylesheet.go
@@ -0,0 +1,25 @@
+package css
+
+// Stylesheet represents a parsed stylesheet
+type Stylesheet struct {
+ Rules []*Rule
+}
+
+// NewStylesheet instanciate a new Stylesheet
+func NewStylesheet() *Stylesheet {
+ return &Stylesheet{}
+}
+
+// Returns string representation of the Stylesheet
+func (sheet *Stylesheet) String() string {
+ result := ""
+
+ for _, rule := range sheet.Rules {
+ if result != "" {
+ result += "\n"
+ }
+ result += rule.String()
+ }
+
+ return result
+}
diff --git a/vendor/github.com/aymerick/douceur/parser/parser.go b/vendor/github.com/aymerick/douceur/parser/parser.go
new file mode 100644
index 00000000..6c4917cc
--- /dev/null
+++ b/vendor/github.com/aymerick/douceur/parser/parser.go
@@ -0,0 +1,409 @@
+package parser
+
+import (
+ "errors"
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/gorilla/css/scanner"
+
+ "github.com/aymerick/douceur/css"
+)
+
+const (
+ importantSuffixRegexp = `(?i)\s*!important\s*$`
+)
+
+var (
+ importantRegexp *regexp.Regexp
+)
+
+// Parser represents a CSS parser
+type Parser struct {
+ scan *scanner.Scanner // Tokenizer
+
+ // Tokens parsed but not consumed yet
+ tokens []*scanner.Token
+
+ // Rule embedding level
+ embedLevel int
+}
+
+func init() {
+ importantRegexp = regexp.MustCompile(importantSuffixRegexp)
+}
+
+// NewParser instanciates a new parser
+func NewParser(txt string) *Parser {
+ return &Parser{
+ scan: scanner.New(txt),
+ }
+}
+
+// Parse parses a whole stylesheet
+func Parse(text string) (*css.Stylesheet, error) {
+ result, err := NewParser(text).ParseStylesheet()
+ if err != nil {
+ return nil, err
+ }
+
+ return result, nil
+}
+
+// ParseDeclarations parses CSS declarations
+func ParseDeclarations(text string) ([]*css.Declaration, error) {
+ result, err := NewParser(text).ParseDeclarations()
+ if err != nil {
+ return nil, err
+ }
+
+ return result, nil
+}
+
+// ParseStylesheet parses a stylesheet
+func (parser *Parser) ParseStylesheet() (*css.Stylesheet, error) {
+ result := css.NewStylesheet()
+
+ // Parse BOM
+ if _, err := parser.parseBOM(); err != nil {
+ return result, err
+ }
+
+ // Parse list of rules
+ rules, err := parser.ParseRules()
+ if err != nil {
+ return result, err
+ }
+
+ result.Rules = rules
+
+ return result, nil
+}
+
+// ParseRules parses a list of rules
+func (parser *Parser) ParseRules() ([]*css.Rule, error) {
+ result := []*css.Rule{}
+
+ inBlock := false
+ if parser.tokenChar("{") {
+ // parsing a block of rules
+ inBlock = true
+ parser.embedLevel++
+
+ parser.shiftToken()
+ }
+
+ for parser.tokenParsable() {
+ if parser.tokenIgnorable() {
+ parser.shiftToken()
+ } else if parser.tokenChar("}") {
+ if !inBlock {
+ errMsg := fmt.Sprintf("Unexpected } character: %s", parser.nextToken().String())
+ return result, errors.New(errMsg)
+ }
+
+ parser.shiftToken()
+ parser.embedLevel--
+
+ // finished
+ break
+ } else {
+ rule, err := parser.ParseRule()
+ if err != nil {
+ return result, err
+ }
+
+ rule.EmbedLevel = parser.embedLevel
+ result = append(result, rule)
+ }
+ }
+
+ return result, parser.err()
+}
+
+// ParseRule parses a rule
+func (parser *Parser) ParseRule() (*css.Rule, error) {
+ if parser.tokenAtKeyword() {
+ return parser.parseAtRule()
+ }
+
+ return parser.parseQualifiedRule()
+}
+
+// ParseDeclarations parses a list of declarations
+func (parser *Parser) ParseDeclarations() ([]*css.Declaration, error) {
+ result := []*css.Declaration{}
+
+ if parser.tokenChar("{") {
+ parser.shiftToken()
+ }
+
+ for parser.tokenParsable() {
+ if parser.tokenIgnorable() {
+ parser.shiftToken()
+ } else if parser.tokenChar("}") {
+ // end of block
+ parser.shiftToken()
+ break
+ } else {
+ declaration, err := parser.ParseDeclaration()
+ if err != nil {
+ return result, err
+ }
+
+ result = append(result, declaration)
+ }
+ }
+
+ return result, parser.err()
+}
+
+// ParseDeclaration parses a declaration
+func (parser *Parser) ParseDeclaration() (*css.Declaration, error) {
+ result := css.NewDeclaration()
+ curValue := ""
+
+ for parser.tokenParsable() {
+ if parser.tokenChar(":") {
+ result.Property = strings.TrimSpace(curValue)
+ curValue = ""
+
+ parser.shiftToken()
+ } else if parser.tokenChar(";") || parser.tokenChar("}") {
+ if result.Property == "" {
+ errMsg := fmt.Sprintf("Unexpected ; character: %s", parser.nextToken().String())
+ return result, errors.New(errMsg)
+ }
+
+ if importantRegexp.MatchString(curValue) {
+ result.Important = true
+ curValue = importantRegexp.ReplaceAllString(curValue, "")
+ }
+
+ result.Value = strings.TrimSpace(curValue)
+
+ if parser.tokenChar(";") {
+ parser.shiftToken()
+ }
+
+ // finished
+ break
+ } else {
+ token := parser.shiftToken()
+ curValue += token.Value
+ }
+ }
+
+ // log.Printf("[parsed] Declaration: %s", result.String())
+
+ return result, parser.err()
+}
+
+// Parse an At Rule
+func (parser *Parser) parseAtRule() (*css.Rule, error) {
+ // parse rule name (eg: "@import")
+ token := parser.shiftToken()
+
+ result := css.NewRule(css.AtRule)
+ result.Name = token.Value
+
+ for parser.tokenParsable() {
+ if parser.tokenChar(";") {
+ parser.shiftToken()
+
+ // finished
+ break
+ } else if parser.tokenChar("{") {
+ if result.EmbedsRules() {
+ // parse rules block
+ rules, err := parser.ParseRules()
+ if err != nil {
+ return result, err
+ }
+
+ result.Rules = rules
+ } else {
+ // parse declarations block
+ declarations, err := parser.ParseDeclarations()
+ if err != nil {
+ return result, err
+ }
+
+ result.Declarations = declarations
+ }
+
+ // finished
+ break
+ } else {
+ // parse prelude
+ prelude, err := parser.parsePrelude()
+ if err != nil {
+ return result, err
+ }
+
+ result.Prelude = prelude
+ }
+ }
+
+ // log.Printf("[parsed] Rule: %s", result.String())
+
+ return result, parser.err()
+}
+
+// Parse a Qualified Rule
+func (parser *Parser) parseQualifiedRule() (*css.Rule, error) {
+ result := css.NewRule(css.QualifiedRule)
+
+ for parser.tokenParsable() {
+ if parser.tokenChar("{") {
+ if result.Prelude == "" {
+ errMsg := fmt.Sprintf("Unexpected { character: %s", parser.nextToken().String())
+ return result, errors.New(errMsg)
+ }
+
+ // parse declarations block
+ declarations, err := parser.ParseDeclarations()
+ if err != nil {
+ return result, err
+ }
+
+ result.Declarations = declarations
+
+ // finished
+ break
+ } else {
+ // parse prelude
+ prelude, err := parser.parsePrelude()
+ if err != nil {
+ return result, err
+ }
+
+ result.Prelude = prelude
+ }
+ }
+
+ result.Selectors = strings.Split(result.Prelude, ",")
+ for i, sel := range result.Selectors {
+ result.Selectors[i] = strings.TrimSpace(sel)
+ }
+
+ // log.Printf("[parsed] Rule: %s", result.String())
+
+ return result, parser.err()
+}
+
+// Parse Rule prelude
+func (parser *Parser) parsePrelude() (string, error) {
+ result := ""
+
+ for parser.tokenParsable() && !parser.tokenEndOfPrelude() {
+ token := parser.shiftToken()
+ result += token.Value
+ }
+
+ result = strings.TrimSpace(result)
+
+ // log.Printf("[parsed] prelude: %s", result)
+
+ return result, parser.err()
+}
+
+// Parse BOM
+func (parser *Parser) parseBOM() (bool, error) {
+ if parser.nextToken().Type == scanner.TokenBOM {
+ parser.shiftToken()
+ return true, nil
+ }
+
+ return false, parser.err()
+}
+
+// Returns next token without removing it from tokens buffer
+func (parser *Parser) nextToken() *scanner.Token {
+ if len(parser.tokens) == 0 {
+ // fetch next token
+ nextToken := parser.scan.Next()
+
+ // log.Printf("[token] %s => %v", nextToken.Type.String(), nextToken.Value)
+
+ // queue it
+ parser.tokens = append(parser.tokens, nextToken)
+ }
+
+ return parser.tokens[0]
+}
+
+// Returns next token and remove it from the tokens buffer
+func (parser *Parser) shiftToken() *scanner.Token {
+ var result *scanner.Token
+
+ result, parser.tokens = parser.tokens[0], parser.tokens[1:]
+ return result
+}
+
+// Returns tokenizer error, or nil if no error
+func (parser *Parser) err() error {
+ if parser.tokenError() {
+ token := parser.nextToken()
+ return fmt.Errorf("Tokenizer error: %s", token.String())
+ }
+
+ return nil
+}
+
+// Returns true if next token is Error
+func (parser *Parser) tokenError() bool {
+ return parser.nextToken().Type == scanner.TokenError
+}
+
+// Returns true if next token is EOF
+func (parser *Parser) tokenEOF() bool {
+ return parser.nextToken().Type == scanner.TokenEOF
+}
+
+// Returns true if next token is a whitespace
+func (parser *Parser) tokenWS() bool {
+ return parser.nextToken().Type == scanner.TokenS
+}
+
+// Returns true if next token is a comment
+func (parser *Parser) tokenComment() bool {
+ return parser.nextToken().Type == scanner.TokenComment
+}
+
+// Returns true if next token is a CDO or a CDC
+func (parser *Parser) tokenCDOorCDC() bool {
+ switch parser.nextToken().Type {
+ case scanner.TokenCDO, scanner.TokenCDC:
+ return true
+ default:
+ return false
+ }
+}
+
+// Returns true if next token is ignorable
+func (parser *Parser) tokenIgnorable() bool {
+ return parser.tokenWS() || parser.tokenComment() || parser.tokenCDOorCDC()
+}
+
+// Returns true if next token is parsable
+func (parser *Parser) tokenParsable() bool {
+ return !parser.tokenEOF() && !parser.tokenError()
+}
+
+// Returns true if next token is an At Rule keyword
+func (parser *Parser) tokenAtKeyword() bool {
+ return parser.nextToken().Type == scanner.TokenAtKeyword
+}
+
+// Returns true if next token is given character
+func (parser *Parser) tokenChar(value string) bool {
+ token := parser.nextToken()
+ return (token.Type == scanner.TokenChar) && (token.Value == value)
+}
+
+// Returns true if next token marks the end of a prelude
+func (parser *Parser) tokenEndOfPrelude() bool {
+ return parser.tokenChar(";") || parser.tokenChar("{")
+}
diff --git a/vendor/github.com/charmbracelet/colorprofile/.golangci-soft.yml b/vendor/github.com/charmbracelet/colorprofile/.golangci-soft.yml
new file mode 100644
index 00000000..d325d4fc
--- /dev/null
+++ b/vendor/github.com/charmbracelet/colorprofile/.golangci-soft.yml
@@ -0,0 +1,40 @@
+run:
+ tests: false
+ issues-exit-code: 0
+
+issues:
+ include:
+ - EXC0001
+ - EXC0005
+ - EXC0011
+ - EXC0012
+ - EXC0013
+
+ max-issues-per-linter: 0
+ max-same-issues: 0
+
+linters:
+ enable:
+ - exhaustive
+ - goconst
+ - godot
+ - godox
+ - mnd
+ - gomoddirectives
+ - goprintffuncname
+ - misspell
+ - nakedret
+ - nestif
+ - noctx
+ - nolintlint
+ - prealloc
+ - wrapcheck
+
+ # disable default linters, they are already enabled in .golangci.yml
+ disable:
+ - errcheck
+ - gosimple
+ - govet
+ - ineffassign
+ - staticcheck
+ - unused
diff --git a/vendor/github.com/charmbracelet/colorprofile/.golangci.yml b/vendor/github.com/charmbracelet/colorprofile/.golangci.yml
new file mode 100644
index 00000000..d6789e01
--- /dev/null
+++ b/vendor/github.com/charmbracelet/colorprofile/.golangci.yml
@@ -0,0 +1,28 @@
+run:
+ tests: false
+
+issues:
+ include:
+ - EXC0001
+ - EXC0005
+ - EXC0011
+ - EXC0012
+ - EXC0013
+
+ max-issues-per-linter: 0
+ max-same-issues: 0
+
+linters:
+ enable:
+ - bodyclose
+ - gofumpt
+ - goimports
+ - gosec
+ - nilerr
+ - revive
+ - rowserrcheck
+ - sqlclosecheck
+ - tparallel
+ - unconvert
+ - unparam
+ - whitespace
diff --git a/vendor/github.com/charmbracelet/colorprofile/.goreleaser.yml b/vendor/github.com/charmbracelet/colorprofile/.goreleaser.yml
new file mode 100644
index 00000000..40d9f298
--- /dev/null
+++ b/vendor/github.com/charmbracelet/colorprofile/.goreleaser.yml
@@ -0,0 +1,6 @@
+includes:
+ - from_url:
+ url: charmbracelet/meta/main/goreleaser-lib.yaml
+
+# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
+
diff --git a/vendor/github.com/charmbracelet/colorprofile/LICENSE b/vendor/github.com/charmbracelet/colorprofile/LICENSE
new file mode 100644
index 00000000..b7974b07
--- /dev/null
+++ b/vendor/github.com/charmbracelet/colorprofile/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020-2024 Charmbracelet, Inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/charmbracelet/colorprofile/README.md b/vendor/github.com/charmbracelet/colorprofile/README.md
new file mode 100644
index 00000000..c72b2f4b
--- /dev/null
+++ b/vendor/github.com/charmbracelet/colorprofile/README.md
@@ -0,0 +1,103 @@
+# Colorprofile
+
+
+
+A simple, powerful—and at times magical—package for detecting terminal color
+profiles and performing color (and CSI) degradation.
+
+## Detecting the terminal’s color profile
+
+Detecting the terminal’s color profile is easy.
+
+```go
+import "github.com/charmbracelet/colorprofile"
+
+// Detect the color profile. If you’re planning on writing to stderr you'd want
+// to use os.Stderr instead.
+p := colorprofile.Detect(os.Stdout, os.Environ())
+
+// Comment on the profile.
+fmt.Printf("You know, your colors are quite %s.", func() string {
+ switch p {
+ case colorprofile.TrueColor:
+ return "fancy"
+ case colorprofile.ANSI256:
+ return "1990s fancy"
+ case colorprofile.ANSI:
+ return "normcore"
+ case colorprofile.Ascii:
+ return "ancient"
+ case colorprofile.NoTTY:
+ return "naughty!"
+ }
+ return "...IDK" // this should never happen
+}())
+```
+
+## Downsampling colors
+
+When necessary, colors can be downsampled to a given profile, or manually
+downsampled to a specific profile.
+
+```go
+p := colorprofile.Detect(os.Stdout, os.Environ())
+c := color.RGBA{0x6b, 0x50, 0xff, 0xff} // #6b50ff
+
+// Downsample to the detected profile, when necessary.
+convertedColor := p.Convert(c)
+
+// Or manually convert to a given profile.
+ansi256Color := colorprofile.ANSI256.Convert(c)
+ansiColor := colorprofile.ANSI.Convert(c)
+noColor := colorprofile.Ascii.Convert(c)
+noANSI := colorprofile.NoTTY.Convert(c)
+```
+
+## Automatic downsampling with a Writer
+
+You can also magically downsample colors in ANSI output, when necessary. If
+output is not a TTY ANSI will be dropped entirely.
+
+```go
+myFancyANSI := "\x1b[38;2;107;80;255mCute \x1b[1;3mpuppy!!\x1b[m"
+
+// Automatically downsample for the terminal at stdout.
+w := colorprofile.NewWriter(os.Stdout, os.Environ())
+fmt.Fprintf(w, myFancyANSI)
+
+// Downsample to 4-bit ANSI.
+w.Profile = colorprofile.ANSI
+fmt.Fprintf(w, myFancyANSI)
+
+// Ascii-fy, no colors.
+w.Profile = colorprofile.Ascii
+fmt.Fprintf(w, myFancyANSI)
+
+// Strip ANSI altogether.
+w.Profile = colorprofile.NoTTY
+fmt.Fprintf(w, myFancyANSI) // not as fancy
+```
+
+## Feedback
+
+We’d love to hear your thoughts on this project. Feel free to drop us a note!
+
+- [Twitter](https://twitter.com/charmcli)
+- [The Fediverse](https://mastodon.social/@charmcli)
+- [Discord](https://charm.sh/chat)
+
+## License
+
+[MIT](https://github.com/charmbracelet/bubbletea/raw/master/LICENSE)
+
+---
+
+Part of [Charm](https://charm.sh).
+
+
+
+Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة
diff --git a/vendor/github.com/charmbracelet/colorprofile/env.go b/vendor/github.com/charmbracelet/colorprofile/env.go
new file mode 100644
index 00000000..8df3d8f7
--- /dev/null
+++ b/vendor/github.com/charmbracelet/colorprofile/env.go
@@ -0,0 +1,287 @@
+package colorprofile
+
+import (
+ "bytes"
+ "io"
+ "os/exec"
+ "runtime"
+ "strconv"
+ "strings"
+
+ "github.com/charmbracelet/x/term"
+ "github.com/xo/terminfo"
+)
+
+// Detect returns the color profile based on the terminal output, and
+// environment variables. This respects NO_COLOR, CLICOLOR, and CLICOLOR_FORCE
+// environment variables.
+//
+// The rules as follows:
+// - TERM=dumb is always treated as NoTTY unless CLICOLOR_FORCE=1 is set.
+// - If COLORTERM=truecolor, and the profile is not NoTTY, it gest upgraded to TrueColor.
+// - Using any 256 color terminal (e.g. TERM=xterm-256color) will set the profile to ANSI256.
+// - Using any color terminal (e.g. TERM=xterm-color) will set the profile to ANSI.
+// - Using CLICOLOR=1 without TERM defined should be treated as ANSI if the
+// output is a terminal.
+// - NO_COLOR takes precedence over CLICOLOR/CLICOLOR_FORCE, and will disable
+// colors but not text decoration, i.e. bold, italic, faint, etc.
+//
+// See https://no-color.org/ and https://bixense.com/clicolors/ for more information.
+func Detect(output io.Writer, env []string) Profile {
+ out, ok := output.(term.File)
+ isatty := ok && term.IsTerminal(out.Fd())
+ environ := newEnviron(env)
+ term := environ.get("TERM")
+ isDumb := term == "dumb"
+ envp := colorProfile(isatty, environ)
+ if envp == TrueColor || envNoColor(environ) {
+ // We already know we have TrueColor, or NO_COLOR is set.
+ return envp
+ }
+
+ if isatty && !isDumb {
+ tip := Terminfo(term)
+ tmuxp := tmux(environ)
+
+ // Color profile is the maximum of env, terminfo, and tmux.
+ return max(envp, max(tip, tmuxp))
+ }
+
+ return envp
+}
+
+// Env returns the color profile based on the terminal environment variables.
+// This respects NO_COLOR, CLICOLOR, and CLICOLOR_FORCE environment variables.
+//
+// The rules as follows:
+// - TERM=dumb is always treated as NoTTY unless CLICOLOR_FORCE=1 is set.
+// - If COLORTERM=truecolor, and the profile is not NoTTY, it gest upgraded to TrueColor.
+// - Using any 256 color terminal (e.g. TERM=xterm-256color) will set the profile to ANSI256.
+// - Using any color terminal (e.g. TERM=xterm-color) will set the profile to ANSI.
+// - Using CLICOLOR=1 without TERM defined should be treated as ANSI if the
+// output is a terminal.
+// - NO_COLOR takes precedence over CLICOLOR/CLICOLOR_FORCE, and will disable
+// colors but not text decoration, i.e. bold, italic, faint, etc.
+//
+// See https://no-color.org/ and https://bixense.com/clicolors/ for more information.
+func Env(env []string) (p Profile) {
+ return colorProfile(true, newEnviron(env))
+}
+
+func colorProfile(isatty bool, env environ) (p Profile) {
+ isDumb := env.get("TERM") == "dumb"
+ envp := envColorProfile(env)
+ if !isatty || isDumb {
+ // Check if the output is a terminal.
+ // Treat dumb terminals as NoTTY
+ p = NoTTY
+ } else {
+ p = envp
+ }
+
+ if envNoColor(env) && isatty {
+ if p > Ascii {
+ p = Ascii
+ }
+ return
+ }
+
+ if cliColorForced(env) {
+ if p < ANSI {
+ p = ANSI
+ }
+ if envp > p {
+ p = envp
+ }
+
+ return
+ }
+
+ if cliColor(env) {
+ if isatty && !isDumb && p < ANSI {
+ p = ANSI
+ }
+ }
+
+ return p
+}
+
+// envNoColor returns true if the environment variables explicitly disable color output
+// by setting NO_COLOR (https://no-color.org/).
+func envNoColor(env environ) bool {
+ noColor, _ := strconv.ParseBool(env.get("NO_COLOR"))
+ return noColor
+}
+
+func cliColor(env environ) bool {
+ cliColor, _ := strconv.ParseBool(env.get("CLICOLOR"))
+ return cliColor
+}
+
+func cliColorForced(env environ) bool {
+ cliColorForce, _ := strconv.ParseBool(env.get("CLICOLOR_FORCE"))
+ return cliColorForce
+}
+
+func colorTerm(env environ) bool {
+ colorTerm := strings.ToLower(env.get("COLORTERM"))
+ return colorTerm == "truecolor" || colorTerm == "24bit" ||
+ colorTerm == "yes" || colorTerm == "true"
+}
+
+// envColorProfile returns infers the color profile from the environment.
+func envColorProfile(env environ) (p Profile) {
+ term, ok := env.lookup("TERM")
+ if !ok || len(term) == 0 || term == "dumb" {
+ p = NoTTY
+ if runtime.GOOS == "windows" {
+ // Use Windows API to detect color profile. Windows Terminal and
+ // cmd.exe don't define $TERM.
+ if wcp, ok := windowsColorProfile(env); ok {
+ p = wcp
+ }
+ }
+ } else {
+ p = ANSI
+ }
+
+ parts := strings.Split(term, "-")
+ switch parts[0] {
+ case "alacritty",
+ "contour",
+ "foot",
+ "ghostty",
+ "kitty",
+ "rio",
+ "st",
+ "wezterm":
+ return TrueColor
+ case "xterm":
+ if len(parts) > 1 {
+ switch parts[1] {
+ case "ghostty", "kitty":
+ // These terminals can be defined as xterm-TERMNAME
+ return TrueColor
+ }
+ }
+ case "tmux", "screen":
+ if p < ANSI256 {
+ p = ANSI256
+ }
+ }
+
+ if isCloudShell, _ := strconv.ParseBool(env.get("GOOGLE_CLOUD_SHELL")); isCloudShell {
+ return TrueColor
+ }
+
+ // GNU Screen doesn't support TrueColor
+ // Tmux doesn't support $COLORTERM
+ if colorTerm(env) && !strings.HasPrefix(term, "screen") && !strings.HasPrefix(term, "tmux") {
+ return TrueColor
+ }
+
+ if strings.HasSuffix(term, "256color") && p < ANSI256 {
+ p = ANSI256
+ }
+
+ return
+}
+
+// Terminfo returns the color profile based on the terminal's terminfo
+// database. This relies on the Tc and RGB capabilities to determine if the
+// terminal supports TrueColor.
+// If term is empty or "dumb", it returns NoTTY.
+func Terminfo(term string) (p Profile) {
+ if len(term) == 0 || term == "dumb" {
+ return NoTTY
+ }
+
+ p = ANSI
+ ti, err := terminfo.Load(term)
+ if err != nil {
+ return
+ }
+
+ extbools := ti.ExtBoolCapsShort()
+ if _, ok := extbools["Tc"]; ok {
+ return TrueColor
+ }
+
+ if _, ok := extbools["RGB"]; ok {
+ return TrueColor
+ }
+
+ return
+}
+
+// Tmux returns the color profile based on `tmux info` output. Tmux supports
+// overriding the terminal's color capabilities, so this function will return
+// the color profile based on the tmux configuration.
+func Tmux(env []string) Profile {
+ return tmux(newEnviron(env))
+}
+
+// tmux returns the color profile based on the tmux environment variables.
+func tmux(env environ) (p Profile) {
+ if tmux, ok := env.lookup("TMUX"); !ok || len(tmux) == 0 {
+ // Not in tmux
+ return NoTTY
+ }
+
+ // Check if tmux has either Tc or RGB capabilities. Otherwise, return
+ // ANSI256.
+ p = ANSI256
+ cmd := exec.Command("tmux", "info")
+ out, err := cmd.Output()
+ if err != nil {
+ return
+ }
+
+ for _, line := range bytes.Split(out, []byte("\n")) {
+ if (bytes.Contains(line, []byte("Tc")) || bytes.Contains(line, []byte("RGB"))) &&
+ bytes.Contains(line, []byte("true")) {
+ return TrueColor
+ }
+ }
+
+ return
+}
+
+// environ is a map of environment variables.
+type environ map[string]string
+
+// newEnviron returns a new environment map from a slice of environment
+// variables.
+func newEnviron(environ []string) environ {
+ m := make(map[string]string, len(environ))
+ for _, e := range environ {
+ parts := strings.SplitN(e, "=", 2)
+ var value string
+ if len(parts) == 2 {
+ value = parts[1]
+ }
+ m[parts[0]] = value
+ }
+ return m
+}
+
+// lookup returns the value of an environment variable and a boolean indicating
+// if it exists.
+func (e environ) lookup(key string) (string, bool) {
+ v, ok := e[key]
+ return v, ok
+}
+
+// get returns the value of an environment variable and empty string if it
+// doesn't exist.
+func (e environ) get(key string) string {
+ v, _ := e.lookup(key)
+ return v
+}
+
+func max[T ~byte | ~int](a, b T) T {
+ if a > b {
+ return a
+ }
+ return b
+}
diff --git a/vendor/github.com/charmbracelet/colorprofile/env_other.go b/vendor/github.com/charmbracelet/colorprofile/env_other.go
new file mode 100644
index 00000000..080994bc
--- /dev/null
+++ b/vendor/github.com/charmbracelet/colorprofile/env_other.go
@@ -0,0 +1,8 @@
+//go:build !windows
+// +build !windows
+
+package colorprofile
+
+func windowsColorProfile(map[string]string) (Profile, bool) {
+ return 0, false
+}
diff --git a/vendor/github.com/charmbracelet/colorprofile/env_windows.go b/vendor/github.com/charmbracelet/colorprofile/env_windows.go
new file mode 100644
index 00000000..3b9c28f9
--- /dev/null
+++ b/vendor/github.com/charmbracelet/colorprofile/env_windows.go
@@ -0,0 +1,45 @@
+//go:build windows
+// +build windows
+
+package colorprofile
+
+import (
+ "strconv"
+
+ "golang.org/x/sys/windows"
+)
+
+func windowsColorProfile(env map[string]string) (Profile, bool) {
+ if env["ConEmuANSI"] == "ON" {
+ return TrueColor, true
+ }
+
+ if len(env["WT_SESSION"]) > 0 {
+ // Windows Terminal supports TrueColor
+ return TrueColor, true
+ }
+
+ major, _, build := windows.RtlGetNtVersionNumbers()
+ if build < 10586 || major < 10 {
+ // No ANSI support before WindowsNT 10 build 10586
+ if len(env["ANSICON"]) > 0 {
+ ansiconVer := env["ANSICON_VER"]
+ cv, err := strconv.Atoi(ansiconVer)
+ if err != nil || cv < 181 {
+ // No 8 bit color support before ANSICON 1.81
+ return ANSI, true
+ }
+
+ return ANSI256, true
+ }
+
+ return NoTTY, true
+ }
+
+ if build < 14931 {
+ // No true color support before build 14931
+ return ANSI256, true
+ }
+
+ return TrueColor, true
+}
diff --git a/vendor/github.com/charmbracelet/colorprofile/profile.go b/vendor/github.com/charmbracelet/colorprofile/profile.go
new file mode 100644
index 00000000..97e37ac3
--- /dev/null
+++ b/vendor/github.com/charmbracelet/colorprofile/profile.go
@@ -0,0 +1,399 @@
+package colorprofile
+
+import (
+ "image/color"
+ "math"
+
+ "github.com/charmbracelet/x/ansi"
+ "github.com/lucasb-eyer/go-colorful"
+)
+
+// Profile is a color profile: NoTTY, Ascii, ANSI, ANSI256, or TrueColor.
+type Profile byte
+
+const (
+ // NoTTY, not a terminal profile.
+ NoTTY Profile = iota
+ // Ascii, uncolored profile.
+ Ascii //nolint:revive
+ // ANSI, 4-bit color profile.
+ ANSI
+ // ANSI256, 8-bit color profile.
+ ANSI256
+ // TrueColor, 24-bit color profile.
+ TrueColor
+)
+
+// String returns the string representation of a Profile.
+func (p Profile) String() string {
+ switch p {
+ case TrueColor:
+ return "TrueColor"
+ case ANSI256:
+ return "ANSI256"
+ case ANSI:
+ return "ANSI"
+ case Ascii:
+ return "Ascii"
+ case NoTTY:
+ return "NoTTY"
+ }
+ return "Unknown"
+}
+
+// Convert transforms a given Color to a Color supported within the Profile.
+func (p Profile) Convert(c color.Color) color.Color {
+ if p <= Ascii {
+ return nil
+ }
+
+ switch c := c.(type) {
+ case ansi.BasicColor:
+ return c
+
+ case ansi.ExtendedColor:
+ if p == ANSI {
+ return ansi256ToANSIColor(c)
+ }
+ return c
+
+ case ansi.TrueColor, color.Color:
+ h, ok := colorful.MakeColor(c)
+ if !ok {
+ return nil
+ }
+ if p != TrueColor {
+ ac := hexToANSI256Color(h)
+ if p == ANSI {
+ return ansi256ToANSIColor(ac)
+ }
+ return ac
+ }
+ return c
+ }
+
+ return c
+}
+
+func hexToANSI256Color(c colorful.Color) ansi.ExtendedColor {
+ v2ci := func(v float64) int {
+ if v < 48 {
+ return 0
+ }
+ if v < 115 {
+ return 1
+ }
+ return int((v - 35) / 40)
+ }
+
+ // Calculate the nearest 0-based color index at 16..231
+ r := v2ci(c.R * 255.0) // 0..5 each
+ g := v2ci(c.G * 255.0)
+ b := v2ci(c.B * 255.0)
+ ci := 36*r + 6*g + b /* 0..215 */
+
+ // Calculate the represented colors back from the index
+ i2cv := [6]int{0, 0x5f, 0x87, 0xaf, 0xd7, 0xff}
+ cr := i2cv[r] // r/g/b, 0..255 each
+ cg := i2cv[g]
+ cb := i2cv[b]
+
+ // Calculate the nearest 0-based gray index at 232..255
+ var grayIdx int
+ average := (cr + cg + cb) / 3
+ if average > 238 {
+ grayIdx = 23
+ } else {
+ grayIdx = (average - 3) / 10 // 0..23
+ }
+ gv := 8 + 10*grayIdx // same value for r/g/b, 0..255
+
+ // Return the one which is nearer to the original input rgb value
+ c2 := colorful.Color{R: float64(cr) / 255.0, G: float64(cg) / 255.0, B: float64(cb) / 255.0}
+ g2 := colorful.Color{R: float64(gv) / 255.0, G: float64(gv) / 255.0, B: float64(gv) / 255.0}
+ colorDist := c.DistanceHSLuv(c2)
+ grayDist := c.DistanceHSLuv(g2)
+
+ if colorDist <= grayDist {
+ return ansi.ExtendedColor(16 + ci) //nolint:gosec
+ }
+ return ansi.ExtendedColor(232 + grayIdx) //nolint:gosec
+}
+
+func ansi256ToANSIColor(c ansi.ExtendedColor) ansi.BasicColor {
+ var r int
+ md := math.MaxFloat64
+
+ h, _ := colorful.Hex(ansiHex[c])
+ for i := 0; i <= 15; i++ {
+ hb, _ := colorful.Hex(ansiHex[i])
+ d := h.DistanceHSLuv(hb)
+
+ if d < md {
+ md = d
+ r = i
+ }
+ }
+
+ return ansi.BasicColor(r) //nolint:gosec
+}
+
+// RGB values of ANSI colors (0-255).
+var ansiHex = []string{
+ "#000000",
+ "#800000",
+ "#008000",
+ "#808000",
+ "#000080",
+ "#800080",
+ "#008080",
+ "#c0c0c0",
+ "#808080",
+ "#ff0000",
+ "#00ff00",
+ "#ffff00",
+ "#0000ff",
+ "#ff00ff",
+ "#00ffff",
+ "#ffffff",
+ "#000000",
+ "#00005f",
+ "#000087",
+ "#0000af",
+ "#0000d7",
+ "#0000ff",
+ "#005f00",
+ "#005f5f",
+ "#005f87",
+ "#005faf",
+ "#005fd7",
+ "#005fff",
+ "#008700",
+ "#00875f",
+ "#008787",
+ "#0087af",
+ "#0087d7",
+ "#0087ff",
+ "#00af00",
+ "#00af5f",
+ "#00af87",
+ "#00afaf",
+ "#00afd7",
+ "#00afff",
+ "#00d700",
+ "#00d75f",
+ "#00d787",
+ "#00d7af",
+ "#00d7d7",
+ "#00d7ff",
+ "#00ff00",
+ "#00ff5f",
+ "#00ff87",
+ "#00ffaf",
+ "#00ffd7",
+ "#00ffff",
+ "#5f0000",
+ "#5f005f",
+ "#5f0087",
+ "#5f00af",
+ "#5f00d7",
+ "#5f00ff",
+ "#5f5f00",
+ "#5f5f5f",
+ "#5f5f87",
+ "#5f5faf",
+ "#5f5fd7",
+ "#5f5fff",
+ "#5f8700",
+ "#5f875f",
+ "#5f8787",
+ "#5f87af",
+ "#5f87d7",
+ "#5f87ff",
+ "#5faf00",
+ "#5faf5f",
+ "#5faf87",
+ "#5fafaf",
+ "#5fafd7",
+ "#5fafff",
+ "#5fd700",
+ "#5fd75f",
+ "#5fd787",
+ "#5fd7af",
+ "#5fd7d7",
+ "#5fd7ff",
+ "#5fff00",
+ "#5fff5f",
+ "#5fff87",
+ "#5fffaf",
+ "#5fffd7",
+ "#5fffff",
+ "#870000",
+ "#87005f",
+ "#870087",
+ "#8700af",
+ "#8700d7",
+ "#8700ff",
+ "#875f00",
+ "#875f5f",
+ "#875f87",
+ "#875faf",
+ "#875fd7",
+ "#875fff",
+ "#878700",
+ "#87875f",
+ "#878787",
+ "#8787af",
+ "#8787d7",
+ "#8787ff",
+ "#87af00",
+ "#87af5f",
+ "#87af87",
+ "#87afaf",
+ "#87afd7",
+ "#87afff",
+ "#87d700",
+ "#87d75f",
+ "#87d787",
+ "#87d7af",
+ "#87d7d7",
+ "#87d7ff",
+ "#87ff00",
+ "#87ff5f",
+ "#87ff87",
+ "#87ffaf",
+ "#87ffd7",
+ "#87ffff",
+ "#af0000",
+ "#af005f",
+ "#af0087",
+ "#af00af",
+ "#af00d7",
+ "#af00ff",
+ "#af5f00",
+ "#af5f5f",
+ "#af5f87",
+ "#af5faf",
+ "#af5fd7",
+ "#af5fff",
+ "#af8700",
+ "#af875f",
+ "#af8787",
+ "#af87af",
+ "#af87d7",
+ "#af87ff",
+ "#afaf00",
+ "#afaf5f",
+ "#afaf87",
+ "#afafaf",
+ "#afafd7",
+ "#afafff",
+ "#afd700",
+ "#afd75f",
+ "#afd787",
+ "#afd7af",
+ "#afd7d7",
+ "#afd7ff",
+ "#afff00",
+ "#afff5f",
+ "#afff87",
+ "#afffaf",
+ "#afffd7",
+ "#afffff",
+ "#d70000",
+ "#d7005f",
+ "#d70087",
+ "#d700af",
+ "#d700d7",
+ "#d700ff",
+ "#d75f00",
+ "#d75f5f",
+ "#d75f87",
+ "#d75faf",
+ "#d75fd7",
+ "#d75fff",
+ "#d78700",
+ "#d7875f",
+ "#d78787",
+ "#d787af",
+ "#d787d7",
+ "#d787ff",
+ "#d7af00",
+ "#d7af5f",
+ "#d7af87",
+ "#d7afaf",
+ "#d7afd7",
+ "#d7afff",
+ "#d7d700",
+ "#d7d75f",
+ "#d7d787",
+ "#d7d7af",
+ "#d7d7d7",
+ "#d7d7ff",
+ "#d7ff00",
+ "#d7ff5f",
+ "#d7ff87",
+ "#d7ffaf",
+ "#d7ffd7",
+ "#d7ffff",
+ "#ff0000",
+ "#ff005f",
+ "#ff0087",
+ "#ff00af",
+ "#ff00d7",
+ "#ff00ff",
+ "#ff5f00",
+ "#ff5f5f",
+ "#ff5f87",
+ "#ff5faf",
+ "#ff5fd7",
+ "#ff5fff",
+ "#ff8700",
+ "#ff875f",
+ "#ff8787",
+ "#ff87af",
+ "#ff87d7",
+ "#ff87ff",
+ "#ffaf00",
+ "#ffaf5f",
+ "#ffaf87",
+ "#ffafaf",
+ "#ffafd7",
+ "#ffafff",
+ "#ffd700",
+ "#ffd75f",
+ "#ffd787",
+ "#ffd7af",
+ "#ffd7d7",
+ "#ffd7ff",
+ "#ffff00",
+ "#ffff5f",
+ "#ffff87",
+ "#ffffaf",
+ "#ffffd7",
+ "#ffffff",
+ "#080808",
+ "#121212",
+ "#1c1c1c",
+ "#262626",
+ "#303030",
+ "#3a3a3a",
+ "#444444",
+ "#4e4e4e",
+ "#585858",
+ "#626262",
+ "#6c6c6c",
+ "#767676",
+ "#808080",
+ "#8a8a8a",
+ "#949494",
+ "#9e9e9e",
+ "#a8a8a8",
+ "#b2b2b2",
+ "#bcbcbc",
+ "#c6c6c6",
+ "#d0d0d0",
+ "#dadada",
+ "#e4e4e4",
+ "#eeeeee",
+}
diff --git a/vendor/github.com/charmbracelet/colorprofile/writer.go b/vendor/github.com/charmbracelet/colorprofile/writer.go
new file mode 100644
index 00000000..d04b3b99
--- /dev/null
+++ b/vendor/github.com/charmbracelet/colorprofile/writer.go
@@ -0,0 +1,166 @@
+package colorprofile
+
+import (
+ "bytes"
+ "image/color"
+ "io"
+ "strconv"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+// NewWriter creates a new color profile writer that downgrades color sequences
+// based on the detected color profile.
+//
+// If environ is nil, it will use os.Environ() to get the environment variables.
+//
+// It queries the given writer to determine if it supports ANSI escape codes.
+// If it does, along with the given environment variables, it will determine
+// the appropriate color profile to use for color formatting.
+//
+// This respects the NO_COLOR, CLICOLOR, and CLICOLOR_FORCE environment variables.
+func NewWriter(w io.Writer, environ []string) *Writer {
+ return &Writer{
+ Forward: w,
+ Profile: Detect(w, environ),
+ }
+}
+
+// Writer represents a color profile writer that writes ANSI sequences to the
+// underlying writer.
+type Writer struct {
+ Forward io.Writer
+ Profile Profile
+}
+
+// Write writes the given text to the underlying writer.
+func (w *Writer) Write(p []byte) (int, error) {
+ switch w.Profile {
+ case TrueColor:
+ return w.Forward.Write(p)
+ case NoTTY:
+ return io.WriteString(w.Forward, ansi.Strip(string(p)))
+ default:
+ return w.downsample(p)
+ }
+}
+
+// downsample downgrades the given text to the appropriate color profile.
+func (w *Writer) downsample(p []byte) (int, error) {
+ var buf bytes.Buffer
+ var state byte
+
+ parser := ansi.GetParser()
+ defer ansi.PutParser(parser)
+
+ for len(p) > 0 {
+ parser.Reset()
+ seq, _, read, newState := ansi.DecodeSequence(p, state, parser)
+
+ switch {
+ case ansi.HasCsiPrefix(seq) && parser.Command() == 'm':
+ handleSgr(w, parser, &buf)
+ default:
+ // If we're not a style SGR sequence, just write the bytes.
+ if n, err := buf.Write(seq); err != nil {
+ return n, err
+ }
+ }
+
+ p = p[read:]
+ state = newState
+ }
+
+ return w.Forward.Write(buf.Bytes())
+}
+
+// WriteString writes the given text to the underlying writer.
+func (w *Writer) WriteString(s string) (n int, err error) {
+ return w.Write([]byte(s))
+}
+
+func handleSgr(w *Writer, p *ansi.Parser, buf *bytes.Buffer) {
+ var style ansi.Style
+ params := p.Params()
+ for i := 0; i < len(params); i++ {
+ param := params[i]
+
+ switch param := param.Param(0); param {
+ case 0:
+ // SGR default parameter is 0. We use an empty string to reduce the
+ // number of bytes written to the buffer.
+ style = append(style, "")
+ case 30, 31, 32, 33, 34, 35, 36, 37: // 8-bit foreground color
+ if w.Profile < ANSI {
+ continue
+ }
+ style = style.ForegroundColor(
+ w.Profile.Convert(ansi.BasicColor(param - 30))) //nolint:gosec
+ case 38: // 16 or 24-bit foreground color
+ var c color.Color
+ if n := ansi.ReadStyleColor(params[i:], &c); n > 0 {
+ i += n - 1
+ }
+ if w.Profile < ANSI {
+ continue
+ }
+ style = style.ForegroundColor(w.Profile.Convert(c))
+ case 39: // default foreground color
+ if w.Profile < ANSI {
+ continue
+ }
+ style = style.DefaultForegroundColor()
+ case 40, 41, 42, 43, 44, 45, 46, 47: // 8-bit background color
+ if w.Profile < ANSI {
+ continue
+ }
+ style = style.BackgroundColor(
+ w.Profile.Convert(ansi.BasicColor(param - 40))) //nolint:gosec
+ case 48: // 16 or 24-bit background color
+ var c color.Color
+ if n := ansi.ReadStyleColor(params[i:], &c); n > 0 {
+ i += n - 1
+ }
+ if w.Profile < ANSI {
+ continue
+ }
+ style = style.BackgroundColor(w.Profile.Convert(c))
+ case 49: // default background color
+ if w.Profile < ANSI {
+ continue
+ }
+ style = style.DefaultBackgroundColor()
+ case 58: // 16 or 24-bit underline color
+ var c color.Color
+ if n := ansi.ReadStyleColor(params[i:], &c); n > 0 {
+ i += n - 1
+ }
+ if w.Profile < ANSI {
+ continue
+ }
+ style = style.UnderlineColor(w.Profile.Convert(c))
+ case 59: // default underline color
+ if w.Profile < ANSI {
+ continue
+ }
+ style = style.DefaultUnderlineColor()
+ case 90, 91, 92, 93, 94, 95, 96, 97: // 8-bit bright foreground color
+ if w.Profile < ANSI {
+ continue
+ }
+ style = style.ForegroundColor(
+ w.Profile.Convert(ansi.BasicColor(param - 90 + 8))) //nolint:gosec
+ case 100, 101, 102, 103, 104, 105, 106, 107: // 8-bit bright background color
+ if w.Profile < ANSI {
+ continue
+ }
+ style = style.BackgroundColor(
+ w.Profile.Convert(ansi.BasicColor(param - 100 + 8))) //nolint:gosec
+ default:
+ // If this is not a color attribute, just append it to the style.
+ style = append(style, strconv.Itoa(param))
+ }
+ }
+
+ _, _ = buf.WriteString(style.String())
+}
diff --git a/vendor/github.com/charmbracelet/glamour/.editorconfig b/vendor/github.com/charmbracelet/glamour/.editorconfig
new file mode 100644
index 00000000..5de2df8c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/.editorconfig
@@ -0,0 +1,18 @@
+# https://editorconfig.org/
+
+root = true
+
+[*]
+charset = utf-8
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 2
+
+[*.go]
+indent_style = tab
+indent_size = 8
+
+[*.golden]
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/vendor/github.com/charmbracelet/glamour/.gitattributes b/vendor/github.com/charmbracelet/glamour/.gitattributes
new file mode 100644
index 00000000..68e84c9b
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/.gitattributes
@@ -0,0 +1,2 @@
+*.golden linguist-generated=true -text
+*.png filter=lfs diff=lfs merge=lfs -text
diff --git a/vendor/github.com/charmbracelet/glamour/.gitignore b/vendor/github.com/charmbracelet/glamour/.gitignore
new file mode 100644
index 00000000..eafbdee6
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/.gitignore
@@ -0,0 +1,2 @@
+cmd/
+!*.test
diff --git a/vendor/github.com/charmbracelet/glamour/.golangci.yml b/vendor/github.com/charmbracelet/glamour/.golangci.yml
new file mode 100644
index 00000000..7c0a115e
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/.golangci.yml
@@ -0,0 +1,49 @@
+version: "2"
+run:
+ tests: false
+linters:
+ enable:
+ - bodyclose
+ - exhaustive
+ - goconst
+ - godot
+ - godox
+ - gomoddirectives
+ - goprintffuncname
+ - gosec
+ - misspell
+ - nakedret
+ - nestif
+ - nilerr
+ - noctx
+ - nolintlint
+ - prealloc
+ - revive
+ - rowserrcheck
+ - sqlclosecheck
+ - tparallel
+ - unconvert
+ - unparam
+ - whitespace
+ - wrapcheck
+ exclusions:
+ generated: lax
+ presets:
+ - common-false-positives
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
+issues:
+ max-issues-per-linter: 0
+ max-same-issues: 0
+formatters:
+ enable:
+ - gofumpt
+ - goimports
+ exclusions:
+ generated: lax
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
diff --git a/vendor/github.com/charmbracelet/glamour/.goreleaser.yml b/vendor/github.com/charmbracelet/glamour/.goreleaser.yml
new file mode 100644
index 00000000..40d9f298
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/.goreleaser.yml
@@ -0,0 +1,6 @@
+includes:
+ - from_url:
+ url: charmbracelet/meta/main/goreleaser-lib.yaml
+
+# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
+
diff --git a/vendor/github.com/charmbracelet/glamour/CONTRIBUTING.md b/vendor/github.com/charmbracelet/glamour/CONTRIBUTING.md
new file mode 100644
index 00000000..88e71e06
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/CONTRIBUTING.md
@@ -0,0 +1,23 @@
+# Contributing
+
+Contributions are welcome!
+
+Please submit a pull request for minor changes and submit issues for major changes for discussions.
+
+## Testing
+
+When providing a new feature or bug fix, please provide tests that demonstrate the issue along with your fix.
+
+### Golden Files
+
+If golden files need to be updated, you can do so with `-update`.
+
+Usage: `go test ./pkg/... -update`.
+
+## Themes
+
+New styles need to be implemented in `styles/.go`, and then `go generate
+./...` will create the JSON files from it.
+
+You can look up all references of another theme (e.g. Dracula), and add your
+theme accordingly.
diff --git a/vendor/github.com/charmbracelet/glamour/LICENSE b/vendor/github.com/charmbracelet/glamour/LICENSE
new file mode 100644
index 00000000..e5a29162
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019-2023 Charmbracelet, Inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/charmbracelet/glamour/README.md b/vendor/github.com/charmbracelet/glamour/README.md
new file mode 100644
index 00000000..db440e94
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/README.md
@@ -0,0 +1,96 @@
+# Glamour
+
+
+
+Stylesheet-based markdown rendering for your CLI apps.
+
+
+
+`glamour` lets you render [markdown](https://en.wikipedia.org/wiki/Markdown)
+documents & templates on [ANSI](https://en.wikipedia.org/wiki/ANSI_escape_code)
+compatible terminals. You can create your own stylesheet or simply use one of
+the stylish defaults.
+
+## Usage
+
+```go
+import "github.com/charmbracelet/glamour"
+
+in := `# Hello World
+
+This is a simple example of Markdown rendering with Glamour!
+Check out the [other examples](https://github.com/charmbracelet/glamour/tree/master/examples) too.
+
+Bye!
+`
+
+out, err := glamour.Render(in, "dark")
+fmt.Print(out)
+```
+
+
+
+### Custom Renderer
+
+```go
+import "github.com/charmbracelet/glamour"
+
+r, _ := glamour.NewTermRenderer(
+ // detect background color and pick either the default dark or light theme
+ glamour.WithAutoStyle(),
+ // wrap output at specific width (default is 80)
+ glamour.WithWordWrap(40),
+)
+
+out, err := r.Render(in)
+fmt.Print(out)
+```
+
+## Styles
+
+You can find all available default styles in our [gallery](https://github.com/charmbracelet/glamour/tree/master/styles/gallery).
+Want to create your own style? [Learn how!](https://github.com/charmbracelet/glamour/tree/master/styles)
+
+There are a few options for using a custom style:
+
+1. Call `glamour.Render(inputText, "desiredStyle")`
+1. Set the `GLAMOUR_STYLE` environment variable to your desired default style or a file location for a style and call `glamour.RenderWithEnvironmentConfig(inputText)`
+1. Set the `GLAMOUR_STYLE` environment variable and pass `glamour.WithEnvironmentConfig()` to your custom renderer
+
+## Glamourous Projects
+
+Check out these projects, which use `glamour`:
+
+- [Glow](https://github.com/charmbracelet/glow), a markdown renderer for
+ the command-line.
+- [GitHub CLI](https://github.com/cli/cli), GitHub’s official command line tool.
+- [GitLab CLI](https://gitlab.com/gitlab-org/cli), GitLab's official command line tool.
+- [Gitea CLI](https://gitea.com/gitea/tea), Gitea's official command line tool.
+- [Meteor](https://github.com/odpf/meteor), an easy-to-use, plugin-driven metadata collection framework.
+
+## Feedback
+
+We’d love to hear your thoughts on this project. Feel free to drop us a note!
+
+- [Twitter](https://twitter.com/charmcli)
+- [The Fediverse](https://mastodon.social/@charmcli)
+- [Discord](https://charm.sh/chat)
+
+## License
+
+[MIT](https://github.com/charmbracelet/glamour/raw/master/LICENSE)
+
+---
+
+Part of [Charm](https://charm.sh).
+
+
+
+Charm热爱开源 • Charm loves open source
diff --git a/vendor/github.com/charmbracelet/glamour/Taskfile.yml b/vendor/github.com/charmbracelet/glamour/Taskfile.yml
new file mode 100644
index 00000000..9019f78f
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/Taskfile.yml
@@ -0,0 +1,25 @@
+# https://taskfile.dev
+
+version: '3'
+
+tasks:
+ lint:all:
+ desc: Run all linters
+ cmds:
+ - task: lint
+ - task: lint:soft
+
+ lint:
+ desc: Run base linters
+ cmds:
+ - golangci-lint run
+
+ lint:soft:
+ desc: Run soft linters
+ cmds:
+ - golangci-lint run --config=.golangci-soft.yml
+
+ test:
+ desc: Run tests
+ cmds:
+ - go test ./... {{.CLI_ARGS}}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/ansi.go b/vendor/github.com/charmbracelet/glamour/ansi/ansi.go
new file mode 100644
index 00000000..fba93c14
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/ansi.go
@@ -0,0 +1,3 @@
+// Package ansi handle conversion of markdown to pretty ANSI output on the
+// terminal.
+package ansi
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/baseelement.go b/vendor/github.com/charmbracelet/glamour/ansi/baseelement.go
new file mode 100644
index 00000000..93c27b30
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/baseelement.go
@@ -0,0 +1,151 @@
+package ansi
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "strings"
+ "text/template"
+
+ "github.com/muesli/termenv"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+)
+
+// BaseElement renders a styled primitive element.
+type BaseElement struct {
+ Token string
+ Prefix string
+ Suffix string
+ Style StylePrimitive
+}
+
+func formatToken(format string, token string) (string, error) {
+ var b bytes.Buffer
+
+ v := make(map[string]interface{})
+ v["text"] = token
+
+ tmpl, err := template.New(format).Funcs(TemplateFuncMap).Parse(format)
+ if err != nil {
+ return "", fmt.Errorf("glamour: error parsing template: %w", err)
+ }
+
+ err = tmpl.Execute(&b, v)
+ return b.String(), err
+}
+
+func renderText(w io.Writer, p termenv.Profile, rules StylePrimitive, s string) {
+ if len(s) == 0 {
+ return
+ }
+
+ out := termenv.String(s)
+ if rules.Upper != nil && *rules.Upper {
+ out = termenv.String(cases.Upper(language.English).String(s))
+ }
+ if rules.Lower != nil && *rules.Lower {
+ out = termenv.String(cases.Lower(language.English).String(s))
+ }
+ if rules.Title != nil && *rules.Title {
+ out = termenv.String(cases.Title(language.English).String(s))
+ }
+ if rules.Color != nil {
+ out = out.Foreground(p.Color(*rules.Color))
+ }
+ if rules.BackgroundColor != nil {
+ out = out.Background(p.Color(*rules.BackgroundColor))
+ }
+ if rules.Underline != nil && *rules.Underline {
+ out = out.Underline()
+ }
+ if rules.Bold != nil && *rules.Bold {
+ out = out.Bold()
+ }
+ if rules.Italic != nil && *rules.Italic {
+ out = out.Italic()
+ }
+ if rules.CrossedOut != nil && *rules.CrossedOut {
+ out = out.CrossOut()
+ }
+ if rules.Overlined != nil && *rules.Overlined {
+ out = out.Overline()
+ }
+ if rules.Inverse != nil && *rules.Inverse {
+ out = out.Reverse()
+ }
+ if rules.Blink != nil && *rules.Blink {
+ out = out.Blink()
+ }
+
+ _, _ = io.WriteString(w, out.String())
+}
+
+// StyleOverrideRender renders a BaseElement with an overridden style.
+func (e *BaseElement) StyleOverrideRender(w io.Writer, ctx RenderContext, style StylePrimitive) error {
+ bs := ctx.blockStack
+ st1 := cascadeStylePrimitives(bs.Current().Style.StylePrimitive, style)
+ st2 := cascadeStylePrimitives(bs.With(e.Style), style)
+
+ return e.doRender(w, ctx.options.ColorProfile, st1, st2)
+}
+
+// Render renders a BaseElement.
+func (e *BaseElement) Render(w io.Writer, ctx RenderContext) error {
+ bs := ctx.blockStack
+ st1 := bs.Current().Style.StylePrimitive
+ st2 := bs.With(e.Style)
+ return e.doRender(w, ctx.options.ColorProfile, st1, st2)
+}
+
+func (e *BaseElement) doRender(w io.Writer, p termenv.Profile, st1, st2 StylePrimitive) error {
+ renderText(w, p, st1, e.Prefix)
+ defer func() {
+ renderText(w, p, st1, e.Suffix)
+ }()
+
+ // render unstyled prefix/suffix
+ renderText(w, p, st1, st2.BlockPrefix)
+ defer func() {
+ renderText(w, p, st1, st2.BlockSuffix)
+ }()
+
+ // render styled prefix/suffix
+ renderText(w, p, st2, st2.Prefix)
+ defer func() {
+ renderText(w, p, st2, st2.Suffix)
+ }()
+
+ s := e.Token
+ if len(st2.Format) > 0 {
+ var err error
+ s, err = formatToken(st2.Format, s)
+ if err != nil {
+ return err
+ }
+ }
+ renderText(w, p, st2, escapeReplacer.Replace(s))
+ return nil
+}
+
+// https://www.markdownguide.org/basic-syntax/#characters-you-can-escape
+var escapeReplacer = strings.NewReplacer(
+ "\\\\", "\\",
+ "\\`", "`",
+ "\\*", "*",
+ "\\_", "_",
+ "\\{", "{",
+ "\\}", "}",
+ "\\[", "[",
+ "\\]", "]",
+ "\\<", "<",
+ "\\>", ">",
+ "\\(", "(",
+ "\\)", ")",
+ "\\#", "#",
+ "\\+", "+",
+ "\\-", "-",
+ "\\.", ".",
+ "\\!", "!",
+ "\\|", "|",
+)
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/blockelement.go b/vendor/github.com/charmbracelet/glamour/ansi/blockelement.go
new file mode 100644
index 00000000..7fcf3990
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/blockelement.go
@@ -0,0 +1,65 @@
+package ansi
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+// BlockElement provides a render buffer for children of a block element.
+// After all children have been rendered into it, it applies indentation and
+// margins around them and writes everything to the parent rendering buffer.
+type BlockElement struct {
+ Block *bytes.Buffer
+ Style StyleBlock
+ Margin bool
+ Newline bool
+}
+
+// Render renders a BlockElement.
+func (e *BlockElement) Render(w io.Writer, ctx RenderContext) error {
+ bs := ctx.blockStack
+ bs.Push(*e)
+
+ renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, e.Style.BlockPrefix)
+ renderText(bs.Current().Block, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, e.Style.Prefix)
+ return nil
+}
+
+// Finish finishes rendering a BlockElement.
+func (e *BlockElement) Finish(w io.Writer, ctx RenderContext) error {
+ bs := ctx.blockStack
+
+ if e.Margin { //nolint: nestif
+ s := ansi.Wordwrap(
+ bs.Current().Block.String(),
+ int(bs.Width(ctx)), //nolint: gosec
+ " ,.;-+|",
+ )
+
+ mw := NewMarginWriter(ctx, w, bs.Current().Style)
+ if _, err := io.WriteString(mw, s); err != nil {
+ return fmt.Errorf("glamour: error writing to writer: %w", err)
+ }
+
+ if e.Newline {
+ if _, err := io.WriteString(mw, "\n"); err != nil {
+ return fmt.Errorf("glamour: error writing to writer: %w", err)
+ }
+ }
+ } else {
+ _, err := bs.Parent().Block.Write(bs.Current().Block.Bytes())
+ if err != nil {
+ return fmt.Errorf("glamour: error writing to writer: %w", err)
+ }
+ }
+
+ renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, e.Style.Suffix)
+ renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, e.Style.BlockSuffix)
+
+ bs.Current().Block.Reset()
+ bs.Pop()
+ return nil
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/blockstack.go b/vendor/github.com/charmbracelet/glamour/ansi/blockstack.go
new file mode 100644
index 00000000..8a5d77c7
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/blockstack.go
@@ -0,0 +1,95 @@
+package ansi
+
+import (
+ "bytes"
+)
+
+// BlockStack is a stack of block elements, used to calculate the current
+// indentation & margin level during the rendering process.
+type BlockStack []BlockElement
+
+// Len returns the length of the stack.
+func (s *BlockStack) Len() int {
+ return len(*s)
+}
+
+// Push appends an item to the stack.
+func (s *BlockStack) Push(e BlockElement) {
+ *s = append(*s, e)
+}
+
+// Pop removes the last item on the stack.
+func (s *BlockStack) Pop() {
+ stack := *s
+ if len(stack) == 0 {
+ return
+ }
+
+ stack = stack[0 : len(stack)-1]
+ *s = stack
+}
+
+// Indent returns the current indentation level of all elements in the stack.
+func (s BlockStack) Indent() uint {
+ var i uint
+
+ for _, v := range s {
+ if v.Style.Indent == nil {
+ continue
+ }
+ i += *v.Style.Indent
+ }
+
+ return i
+}
+
+// Margin returns the current margin level of all elements in the stack.
+func (s BlockStack) Margin() uint {
+ var i uint
+
+ for _, v := range s {
+ if v.Style.Margin == nil {
+ continue
+ }
+ i += *v.Style.Margin
+ }
+
+ return i
+}
+
+// Width returns the available rendering width.
+func (s BlockStack) Width(ctx RenderContext) uint {
+ if s.Indent()+s.Margin()*2 > uint(ctx.options.WordWrap) { //nolint: gosec
+ return 0
+ }
+ return uint(ctx.options.WordWrap) - s.Indent() - s.Margin()*2 //nolint: gosec
+}
+
+// Parent returns the current BlockElement's parent.
+func (s BlockStack) Parent() BlockElement {
+ if len(s) == 1 {
+ return BlockElement{
+ Block: &bytes.Buffer{},
+ }
+ }
+
+ return s[len(s)-2]
+}
+
+// Current returns the current BlockElement.
+func (s BlockStack) Current() BlockElement {
+ if len(s) == 0 {
+ return BlockElement{
+ Block: &bytes.Buffer{},
+ }
+ }
+
+ return s[len(s)-1]
+}
+
+// With returns a StylePrimitive that inherits the current BlockElement's style.
+func (s BlockStack) With(child StylePrimitive) StylePrimitive {
+ sb := StyleBlock{}
+ sb.StylePrimitive = child
+ return cascadeStyle(s.Current().Style, sb, false).StylePrimitive
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/codeblock.go b/vendor/github.com/charmbracelet/glamour/ansi/codeblock.go
new file mode 100644
index 00000000..90976e56
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/codeblock.go
@@ -0,0 +1,152 @@
+package ansi
+
+import (
+ "fmt"
+ "io"
+ "sync"
+
+ "github.com/alecthomas/chroma/v2"
+ "github.com/alecthomas/chroma/v2/quick"
+ "github.com/alecthomas/chroma/v2/styles"
+ "github.com/muesli/reflow/indent"
+ "github.com/muesli/termenv"
+)
+
+const (
+ // The chroma style theme name used for rendering.
+ chromaStyleTheme = "charm"
+
+ // The chroma formatter name used for rendering.
+ chromaFormatter = "terminal256"
+)
+
+// mutex for synchronizing access to the chroma style registry.
+// Related https://github.com/alecthomas/chroma/pull/650
+var mutex = sync.Mutex{}
+
+// A CodeBlockElement is used to render code blocks.
+type CodeBlockElement struct {
+ Code string
+ Language string
+}
+
+func chromaStyle(style StylePrimitive) string {
+ var s string
+
+ if style.Color != nil {
+ s = *style.Color
+ }
+ if style.BackgroundColor != nil {
+ if s != "" {
+ s += " "
+ }
+ s += "bg:" + *style.BackgroundColor
+ }
+ if style.Italic != nil && *style.Italic {
+ if s != "" {
+ s += " "
+ }
+ s += "italic"
+ }
+ if style.Bold != nil && *style.Bold {
+ if s != "" {
+ s += " "
+ }
+ s += "bold"
+ }
+ if style.Underline != nil && *style.Underline {
+ if s != "" {
+ s += " "
+ }
+ s += "underline"
+ }
+
+ return s
+}
+
+// Render renders a CodeBlockElement.
+func (e *CodeBlockElement) Render(w io.Writer, ctx RenderContext) error {
+ bs := ctx.blockStack
+
+ var indentation uint
+ var margin uint
+ formatter := chromaFormatter
+ rules := ctx.options.Styles.CodeBlock
+ if rules.Indent != nil {
+ indentation = *rules.Indent
+ }
+ if rules.Margin != nil {
+ margin = *rules.Margin
+ }
+ if len(ctx.options.ChromaFormatter) > 0 {
+ formatter = ctx.options.ChromaFormatter
+ }
+ theme := rules.Theme
+
+ if rules.Chroma != nil && ctx.options.ColorProfile != termenv.Ascii {
+ theme = chromaStyleTheme
+ mutex.Lock()
+ // Don't register the style if it's already registered.
+ _, ok := styles.Registry[theme]
+ if !ok {
+ styles.Register(chroma.MustNewStyle(theme,
+ chroma.StyleEntries{
+ chroma.Text: chromaStyle(rules.Chroma.Text),
+ chroma.Error: chromaStyle(rules.Chroma.Error),
+ chroma.Comment: chromaStyle(rules.Chroma.Comment),
+ chroma.CommentPreproc: chromaStyle(rules.Chroma.CommentPreproc),
+ chroma.Keyword: chromaStyle(rules.Chroma.Keyword),
+ chroma.KeywordReserved: chromaStyle(rules.Chroma.KeywordReserved),
+ chroma.KeywordNamespace: chromaStyle(rules.Chroma.KeywordNamespace),
+ chroma.KeywordType: chromaStyle(rules.Chroma.KeywordType),
+ chroma.Operator: chromaStyle(rules.Chroma.Operator),
+ chroma.Punctuation: chromaStyle(rules.Chroma.Punctuation),
+ chroma.Name: chromaStyle(rules.Chroma.Name),
+ chroma.NameBuiltin: chromaStyle(rules.Chroma.NameBuiltin),
+ chroma.NameTag: chromaStyle(rules.Chroma.NameTag),
+ chroma.NameAttribute: chromaStyle(rules.Chroma.NameAttribute),
+ chroma.NameClass: chromaStyle(rules.Chroma.NameClass),
+ chroma.NameConstant: chromaStyle(rules.Chroma.NameConstant),
+ chroma.NameDecorator: chromaStyle(rules.Chroma.NameDecorator),
+ chroma.NameException: chromaStyle(rules.Chroma.NameException),
+ chroma.NameFunction: chromaStyle(rules.Chroma.NameFunction),
+ chroma.NameOther: chromaStyle(rules.Chroma.NameOther),
+ chroma.Literal: chromaStyle(rules.Chroma.Literal),
+ chroma.LiteralNumber: chromaStyle(rules.Chroma.LiteralNumber),
+ chroma.LiteralDate: chromaStyle(rules.Chroma.LiteralDate),
+ chroma.LiteralString: chromaStyle(rules.Chroma.LiteralString),
+ chroma.LiteralStringEscape: chromaStyle(rules.Chroma.LiteralStringEscape),
+ chroma.GenericDeleted: chromaStyle(rules.Chroma.GenericDeleted),
+ chroma.GenericEmph: chromaStyle(rules.Chroma.GenericEmph),
+ chroma.GenericInserted: chromaStyle(rules.Chroma.GenericInserted),
+ chroma.GenericStrong: chromaStyle(rules.Chroma.GenericStrong),
+ chroma.GenericSubheading: chromaStyle(rules.Chroma.GenericSubheading),
+ chroma.Background: chromaStyle(rules.Chroma.Background),
+ }))
+ }
+ mutex.Unlock()
+ }
+
+ iw := indent.NewWriterPipe(w, indentation+margin, func(_ io.Writer) {
+ renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, " ")
+ })
+
+ if len(theme) > 0 {
+ renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockPrefix)
+
+ err := quick.Highlight(iw, e.Code, e.Language, formatter, theme)
+ if err != nil {
+ return fmt.Errorf("glamour: error highlighting code: %w", err)
+ }
+ renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockSuffix)
+ return nil
+ }
+
+ // fallback rendering
+ el := &BaseElement{
+ Token: e.Code,
+ Style: rules.StylePrimitive,
+ }
+
+ return el.Render(iw, ctx)
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/codespan.go b/vendor/github.com/charmbracelet/glamour/ansi/codespan.go
new file mode 100644
index 00000000..66a4d493
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/codespan.go
@@ -0,0 +1,15 @@
+package ansi
+
+import "io"
+
+// A CodeSpanElement is used to render codespan.
+type CodeSpanElement struct {
+ Text string
+ Style StylePrimitive
+}
+
+// Render renders a CodeSpanElement.
+func (e *CodeSpanElement) Render(w io.Writer, ctx RenderContext) error {
+ renderText(w, ctx.options.ColorProfile, e.Style, e.Style.Prefix+e.Text+e.Style.Suffix)
+ return nil
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/context.go b/vendor/github.com/charmbracelet/glamour/ansi/context.go
new file mode 100644
index 00000000..ee3c2eb0
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/context.go
@@ -0,0 +1,38 @@
+package ansi
+
+import (
+ "html"
+ "strings"
+
+ "github.com/microcosm-cc/bluemonday"
+)
+
+// RenderContext holds the current rendering options and state.
+type RenderContext struct {
+ options Options
+
+ blockStack *BlockStack
+ table *TableElement
+
+ stripper *bluemonday.Policy
+}
+
+// NewRenderContext returns a new RenderContext.
+func NewRenderContext(options Options) RenderContext {
+ return RenderContext{
+ options: options,
+ blockStack: &BlockStack{},
+ table: &TableElement{},
+ stripper: bluemonday.StrictPolicy(),
+ }
+}
+
+// SanitizeHTML sanitizes HTML content.
+func (ctx RenderContext) SanitizeHTML(s string, trimSpaces bool) string {
+ s = ctx.stripper.Sanitize(s)
+ if trimSpaces {
+ s = strings.TrimSpace(s)
+ }
+
+ return html.UnescapeString(s)
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/elements.go b/vendor/github.com/charmbracelet/glamour/ansi/elements.go
new file mode 100644
index 00000000..f8d0fef3
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/elements.go
@@ -0,0 +1,479 @@
+package ansi
+
+import (
+ "bytes"
+ "fmt"
+ "html"
+ "io"
+ "strings"
+
+ "github.com/charmbracelet/glamour/internal/autolink"
+ east "github.com/yuin/goldmark-emoji/ast"
+ "github.com/yuin/goldmark/ast"
+ astext "github.com/yuin/goldmark/extension/ast"
+)
+
+// ElementRenderer is called when entering a markdown node.
+type ElementRenderer interface {
+ Render(w io.Writer, ctx RenderContext) error
+}
+
+// StyleOverriderElementRenderer is called when entering a markdown node with a specific style.
+type StyleOverriderElementRenderer interface {
+ StyleOverrideRender(w io.Writer, ctx RenderContext, style StylePrimitive) error
+}
+
+// ElementFinisher is called when leaving a markdown node.
+type ElementFinisher interface {
+ Finish(w io.Writer, ctx RenderContext) error
+}
+
+// An Element is used to instruct the renderer how to handle individual markdown
+// nodes.
+type Element struct {
+ Entering string
+ Exiting string
+ Renderer ElementRenderer
+ Finisher ElementFinisher
+}
+
+// NewElement returns the appropriate render Element for a given node.
+func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {
+ ctx := tr.context
+
+ switch node.Kind() {
+ // Document
+ case ast.KindDocument:
+ e := &BlockElement{
+ Block: &bytes.Buffer{},
+ Style: ctx.options.Styles.Document,
+ Margin: true,
+ }
+ return Element{
+ Renderer: e,
+ Finisher: e,
+ }
+
+ // Heading
+ case ast.KindHeading:
+ n := node.(*ast.Heading)
+ he := &HeadingElement{
+ Level: n.Level,
+ First: node.PreviousSibling() == nil,
+ }
+ return Element{
+ Exiting: "",
+ Renderer: he,
+ Finisher: he,
+ }
+
+ // Paragraph
+ case ast.KindParagraph:
+ if node.Parent() != nil {
+ kind := node.Parent().Kind()
+ if kind == ast.KindListItem {
+ return Element{}
+ }
+ }
+ return Element{
+ Renderer: &ParagraphElement{
+ First: node.PreviousSibling() == nil,
+ },
+ Finisher: &ParagraphElement{},
+ }
+
+ // Blockquote
+ case ast.KindBlockquote:
+ e := &BlockElement{
+ Block: &bytes.Buffer{},
+ Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.BlockQuote, false),
+ Margin: true,
+ }
+ return Element{
+ Entering: "\n",
+ Renderer: e,
+ Finisher: e,
+ }
+
+ // Lists
+ case ast.KindList:
+ s := ctx.options.Styles.List.StyleBlock
+ if s.Indent == nil {
+ var i uint
+ s.Indent = &i
+ }
+ n := node.Parent()
+ for n != nil {
+ if n.Kind() == ast.KindList {
+ i := ctx.options.Styles.List.LevelIndent
+ s.Indent = &i
+ break
+ }
+ n = n.Parent()
+ }
+
+ e := &BlockElement{
+ Block: &bytes.Buffer{},
+ Style: cascadeStyle(ctx.blockStack.Current().Style, s, false),
+ Margin: true,
+ Newline: true,
+ }
+ return Element{
+ Entering: "\n",
+ Renderer: e,
+ Finisher: e,
+ }
+
+ case ast.KindListItem:
+ var l uint
+ var e uint
+ l = 1
+ n := node
+ for n.PreviousSibling() != nil && (n.PreviousSibling().Kind() == ast.KindListItem) {
+ l++
+ n = n.PreviousSibling()
+ }
+ if node.Parent().(*ast.List).IsOrdered() {
+ e = l
+ if node.Parent().(*ast.List).Start != 1 {
+ e += uint(node.Parent().(*ast.List).Start) - 1 //nolint: gosec
+ }
+ }
+
+ post := "\n"
+ if (node.LastChild() != nil && node.LastChild().Kind() == ast.KindList) ||
+ node.NextSibling() == nil {
+ post = ""
+ }
+
+ if node.FirstChild() != nil &&
+ node.FirstChild().FirstChild() != nil &&
+ node.FirstChild().FirstChild().Kind() == astext.KindTaskCheckBox {
+ nc := node.FirstChild().FirstChild().(*astext.TaskCheckBox)
+
+ return Element{
+ Exiting: post,
+ Renderer: &TaskElement{
+ Checked: nc.IsChecked,
+ },
+ }
+ }
+
+ return Element{
+ Exiting: post,
+ Renderer: &ItemElement{
+ IsOrdered: node.Parent().(*ast.List).IsOrdered(),
+ Enumeration: e,
+ },
+ }
+
+ // Text Elements
+ case ast.KindText:
+ n := node.(*ast.Text)
+ s := string(n.Segment.Value(source))
+
+ if n.HardLineBreak() || (n.SoftLineBreak()) {
+ s += "\n"
+ }
+ return Element{
+ Renderer: &BaseElement{
+ Token: html.UnescapeString(s),
+ Style: ctx.options.Styles.Text,
+ },
+ }
+
+ case ast.KindEmphasis:
+ n := node.(*ast.Emphasis)
+ var children []ElementRenderer
+ nn := n.FirstChild()
+ for nn != nil {
+ children = append(children, tr.NewElement(nn, source).Renderer)
+ nn = nn.NextSibling()
+ }
+ return Element{
+ Renderer: &EmphasisElement{
+ Level: n.Level,
+ Children: children,
+ },
+ }
+
+ case astext.KindStrikethrough:
+ n := node.(*astext.Strikethrough)
+ s := string(n.Text(source)) //nolint: staticcheck
+ style := ctx.options.Styles.Strikethrough
+
+ return Element{
+ Renderer: &BaseElement{
+ Token: html.UnescapeString(s),
+ Style: style,
+ },
+ }
+
+ case ast.KindThematicBreak:
+ return Element{
+ Entering: "",
+ Exiting: "",
+ Renderer: &BaseElement{
+ Style: ctx.options.Styles.HorizontalRule,
+ },
+ }
+
+ // Links
+ case ast.KindLink:
+ n := node.(*ast.Link)
+ isFooterLinks := !ctx.options.InlineTableLinks && isInsideTable(node)
+
+ var children []ElementRenderer
+ content, err := nodeContent(node, source)
+
+ if isFooterLinks && err == nil {
+ text := string(content)
+ tl := tableLink{
+ content: text,
+ href: string(n.Destination),
+ title: string(n.Title),
+ linkType: linkTypeRegular,
+ }
+ text = linkWithSuffix(tl, ctx.table.tableLinks)
+ children = []ElementRenderer{&BaseElement{Token: text}}
+ } else {
+ nn := n.FirstChild()
+ for nn != nil {
+ children = append(children, tr.NewElement(nn, source).Renderer)
+ nn = nn.NextSibling()
+ }
+ }
+
+ return Element{
+ Renderer: &LinkElement{
+ BaseURL: ctx.options.BaseURL,
+ URL: string(n.Destination),
+ Children: children,
+ SkipHref: isFooterLinks,
+ },
+ }
+ case ast.KindAutoLink:
+ n := node.(*ast.AutoLink)
+ u := string(n.URL(source))
+ isFooterLinks := !ctx.options.InlineTableLinks && isInsideTable(node)
+
+ var children []ElementRenderer
+ nn := n.FirstChild()
+ for nn != nil {
+ children = append(children, tr.NewElement(nn, source).Renderer)
+ nn = nn.NextSibling()
+ }
+
+ if len(children) == 0 {
+ children = append(children, &BaseElement{Token: u})
+ }
+
+ if n.AutoLinkType == ast.AutoLinkEmail && !strings.HasPrefix(strings.ToLower(u), "mailto:") {
+ u = "mailto:" + u
+ }
+
+ var renderer ElementRenderer
+ if isFooterLinks {
+ domain := linkDomain(u)
+ tl := tableLink{
+ content: domain,
+ href: u,
+ linkType: linkTypeAuto,
+ }
+ if shortned, ok := autolink.Detect(u); ok {
+ tl.content = shortned
+ }
+ text := linkWithSuffix(tl, ctx.table.tableLinks)
+
+ renderer = &LinkElement{
+ Children: []ElementRenderer{&BaseElement{Token: text}},
+ URL: u,
+ SkipHref: true,
+ }
+ } else {
+ renderer = &LinkElement{
+ Children: children,
+ URL: u,
+ SkipText: n.AutoLinkType != ast.AutoLinkEmail,
+ }
+ }
+ return Element{Renderer: renderer}
+
+ // Images
+ case ast.KindImage:
+ n := node.(*ast.Image)
+ text := string(n.Text(source)) //nolint: staticcheck
+ isFooterLinks := !ctx.options.InlineTableLinks && isInsideTable(node)
+
+ if isFooterLinks {
+ if text == "" {
+ text = linkDomain(string(n.Destination))
+ }
+ tl := tableLink{
+ title: string(n.Title),
+ content: text,
+ href: string(n.Destination),
+ linkType: linkTypeImage,
+ }
+ text = linkWithSuffix(tl, ctx.table.tableImages)
+ }
+
+ return Element{
+ Renderer: &ImageElement{
+ Text: text,
+ BaseURL: ctx.options.BaseURL,
+ URL: string(n.Destination),
+ TextOnly: isFooterLinks,
+ },
+ }
+
+ // Code
+ case ast.KindFencedCodeBlock:
+ n := node.(*ast.FencedCodeBlock)
+ l := n.Lines().Len()
+ s := ""
+ for i := 0; i < l; i++ {
+ line := n.Lines().At(i)
+ s += string(line.Value(source))
+ }
+ return Element{
+ Entering: "\n",
+ Renderer: &CodeBlockElement{
+ Code: s,
+ Language: string(n.Language(source)),
+ },
+ }
+
+ case ast.KindCodeBlock:
+ n := node.(*ast.CodeBlock)
+ l := n.Lines().Len()
+ s := ""
+ for i := 0; i < l; i++ {
+ line := n.Lines().At(i)
+ s += string(line.Value(source))
+ }
+ return Element{
+ Entering: "\n",
+ Renderer: &CodeBlockElement{
+ Code: s,
+ },
+ }
+
+ case ast.KindCodeSpan:
+ n := node.(*ast.CodeSpan)
+ s := string(n.Text(source)) //nolint: staticcheck
+ return Element{
+ Renderer: &CodeSpanElement{
+ Text: html.UnescapeString(s),
+ Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.Code, false).StylePrimitive,
+ },
+ }
+
+ // Tables
+ case astext.KindTable:
+ table := node.(*astext.Table)
+ te := &TableElement{
+ table: table,
+ source: source,
+ }
+ return Element{
+ Entering: "\n",
+ Exiting: "\n",
+ Renderer: te,
+ Finisher: te,
+ }
+
+ case astext.KindTableCell:
+ n := node.(*astext.TableCell)
+ var children []ElementRenderer
+ nn := n.FirstChild()
+ for nn != nil {
+ children = append(children, tr.NewElement(nn, source).Renderer)
+ nn = nn.NextSibling()
+ }
+
+ r := &TableCellElement{
+ Children: children,
+ Head: node.Parent().Kind() == astext.KindTableHeader,
+ }
+ return Element{
+ Renderer: r,
+ }
+
+ case astext.KindTableHeader:
+ return Element{
+ Finisher: &TableHeadElement{},
+ }
+ case astext.KindTableRow:
+ return Element{
+ Finisher: &TableRowElement{},
+ }
+
+ // HTML Elements
+ case ast.KindHTMLBlock:
+ n := node.(*ast.HTMLBlock)
+ return Element{
+ Renderer: &BaseElement{
+ Token: ctx.SanitizeHTML(string(n.Text(source)), true), //nolint: staticcheck
+ Style: ctx.options.Styles.HTMLBlock.StylePrimitive,
+ },
+ }
+ case ast.KindRawHTML:
+ n := node.(*ast.RawHTML)
+ return Element{
+ Renderer: &BaseElement{
+ Token: ctx.SanitizeHTML(string(n.Text(source)), true), //nolint: staticcheck
+ Style: ctx.options.Styles.HTMLSpan.StylePrimitive,
+ },
+ }
+
+ // Definition Lists
+ case astext.KindDefinitionList:
+ e := &BlockElement{
+ Block: &bytes.Buffer{},
+ Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.DefinitionList, false),
+ Margin: true,
+ Newline: true,
+ }
+ return Element{
+ Renderer: e,
+ Finisher: e,
+ }
+
+ case astext.KindDefinitionTerm:
+ return Element{
+ Entering: "\n",
+ Renderer: &BaseElement{
+ Style: ctx.options.Styles.DefinitionTerm,
+ },
+ }
+
+ case astext.KindDefinitionDescription:
+ return Element{
+ Exiting: "\n",
+ Renderer: &BaseElement{
+ Style: ctx.options.Styles.DefinitionDescription,
+ },
+ }
+
+ // Handled by parents
+ case astext.KindTaskCheckBox:
+ // handled by KindListItem
+ return Element{}
+ case ast.KindTextBlock:
+ return Element{}
+
+ case east.KindEmoji:
+ n := node.(*east.Emoji)
+ return Element{
+ Renderer: &BaseElement{
+ Token: string(n.Value.Unicode),
+ },
+ }
+
+ // Unknown case
+ default:
+ fmt.Println("Warning: unhandled element", node.Kind().String())
+ return Element{}
+ }
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/emphasis.go b/vendor/github.com/charmbracelet/glamour/ansi/emphasis.go
new file mode 100644
index 00000000..04ee4d0a
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/emphasis.go
@@ -0,0 +1,47 @@
+package ansi
+
+import (
+ "fmt"
+ "io"
+)
+
+// A EmphasisElement is used to render emphasis.
+type EmphasisElement struct {
+ Children []ElementRenderer
+ Level int
+}
+
+// Render renders a EmphasisElement.
+func (e *EmphasisElement) Render(w io.Writer, ctx RenderContext) error {
+ style := ctx.options.Styles.Emph
+ if e.Level > 1 {
+ style = ctx.options.Styles.Strong
+ }
+
+ return e.doRender(w, ctx, style)
+}
+
+// StyleOverrideRender renders a EmphasisElement with a given style.
+func (e *EmphasisElement) StyleOverrideRender(w io.Writer, ctx RenderContext, style StylePrimitive) error {
+ base := ctx.options.Styles.Emph
+ if e.Level > 1 {
+ base = ctx.options.Styles.Strong
+ }
+ return e.doRender(w, ctx, cascadeStylePrimitives(base, style))
+}
+
+func (e *EmphasisElement) doRender(w io.Writer, ctx RenderContext, style StylePrimitive) error {
+ for _, child := range e.Children {
+ if r, ok := child.(StyleOverriderElementRenderer); ok {
+ if err := r.StyleOverrideRender(w, ctx, style); err != nil {
+ return fmt.Errorf("glamour: error rendering with style: %w", err)
+ }
+ } else {
+ if err := child.Render(w, ctx); err != nil {
+ return fmt.Errorf("glamour: error rendering: %w", err)
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/heading.go b/vendor/github.com/charmbracelet/glamour/ansi/heading.go
new file mode 100644
index 00000000..b261ad7f
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/heading.go
@@ -0,0 +1,87 @@
+package ansi
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+
+ "github.com/muesli/reflow/wordwrap"
+)
+
+// A HeadingElement is used to render headings.
+type HeadingElement struct {
+ Level int
+ First bool
+}
+
+const (
+ h1 = iota + 1
+ h2
+ h3
+ h4
+ h5
+ h6
+)
+
+// Render renders a HeadingElement.
+func (e *HeadingElement) Render(w io.Writer, ctx RenderContext) error {
+ bs := ctx.blockStack
+ rules := ctx.options.Styles.Heading
+
+ switch e.Level {
+ case h1:
+ rules = cascadeStyles(rules, ctx.options.Styles.H1)
+ case h2:
+ rules = cascadeStyles(rules, ctx.options.Styles.H2)
+ case h3:
+ rules = cascadeStyles(rules, ctx.options.Styles.H3)
+ case h4:
+ rules = cascadeStyles(rules, ctx.options.Styles.H4)
+ case h5:
+ rules = cascadeStyles(rules, ctx.options.Styles.H5)
+ case h6:
+ rules = cascadeStyles(rules, ctx.options.Styles.H6)
+ }
+
+ if !e.First {
+ renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, "\n")
+ }
+
+ be := BlockElement{
+ Block: &bytes.Buffer{},
+ Style: cascadeStyle(bs.Current().Style, rules, false),
+ }
+ bs.Push(be)
+
+ renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, rules.BlockPrefix)
+ renderText(bs.Current().Block, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.Prefix)
+ return nil
+}
+
+// Finish finishes rendering a HeadingElement.
+func (e *HeadingElement) Finish(w io.Writer, ctx RenderContext) error {
+ bs := ctx.blockStack
+ rules := bs.Current().Style
+ mw := NewMarginWriter(ctx, w, rules)
+
+ flow := wordwrap.NewWriter(int(bs.Width(ctx))) //nolint: gosec
+ _, err := flow.Write(bs.Current().Block.Bytes())
+ if err != nil {
+ return fmt.Errorf("glamour: error writing bytes: %w", err)
+ }
+ if err := flow.Close(); err != nil {
+ return fmt.Errorf("glamour: error closing flow: %w", err)
+ }
+
+ _, err = mw.Write(flow.Bytes())
+ if err != nil {
+ return err
+ }
+
+ renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.Suffix)
+ renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, rules.BlockSuffix)
+
+ bs.Current().Block.Reset()
+ bs.Pop()
+ return nil
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/image.go b/vendor/github.com/charmbracelet/glamour/ansi/image.go
new file mode 100644
index 00000000..16df5d8a
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/image.go
@@ -0,0 +1,52 @@
+package ansi
+
+import (
+ "io"
+ "strings"
+)
+
+// An ImageElement is used to render images elements.
+type ImageElement struct {
+ Text string
+ BaseURL string
+ URL string
+ Child ElementRenderer
+ TextOnly bool
+}
+
+// Render renders an ImageElement.
+func (e *ImageElement) Render(w io.Writer, ctx RenderContext) error {
+ style := ctx.options.Styles.ImageText
+ if e.TextOnly {
+ style.Format = strings.TrimSuffix(style.Format, " →")
+ }
+
+ if len(e.Text) > 0 {
+ el := &BaseElement{
+ Token: e.Text,
+ Style: style,
+ }
+ err := el.Render(w, ctx)
+ if err != nil {
+ return err
+ }
+ }
+
+ if e.TextOnly {
+ return nil
+ }
+
+ if len(e.URL) > 0 {
+ el := &BaseElement{
+ Token: resolveRelativeURL(e.BaseURL, e.URL),
+ Prefix: " ",
+ Style: ctx.options.Styles.Image,
+ }
+ err := el.Render(w, ctx)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/link.go b/vendor/github.com/charmbracelet/glamour/ansi/link.go
new file mode 100644
index 00000000..efc383d5
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/link.go
@@ -0,0 +1,76 @@
+package ansi
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/url"
+)
+
+// A LinkElement is used to render hyperlinks.
+type LinkElement struct {
+ BaseURL string
+ URL string
+ Children []ElementRenderer
+ SkipText bool
+ SkipHref bool
+}
+
+// Render renders a LinkElement.
+func (e *LinkElement) Render(w io.Writer, ctx RenderContext) error {
+ if !e.SkipText {
+ if err := e.renderTextPart(w, ctx); err != nil {
+ return err
+ }
+ }
+ if !e.SkipHref {
+ if err := e.renderHrefPart(w, ctx); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (e *LinkElement) renderTextPart(w io.Writer, ctx RenderContext) error {
+ for _, child := range e.Children {
+ if r, ok := child.(StyleOverriderElementRenderer); ok {
+ st := ctx.options.Styles.LinkText
+ if err := r.StyleOverrideRender(w, ctx, st); err != nil {
+ return fmt.Errorf("glamour: error rendering with style: %w", err)
+ }
+ } else {
+ var b bytes.Buffer
+ if err := child.Render(&b, ctx); err != nil {
+ return fmt.Errorf("glamour: error rendering: %w", err)
+ }
+ el := &BaseElement{
+ Token: b.String(),
+ Style: ctx.options.Styles.LinkText,
+ }
+ if err := el.Render(w, ctx); err != nil {
+ return fmt.Errorf("glamour: error rendering: %w", err)
+ }
+ }
+ }
+ return nil
+}
+
+func (e *LinkElement) renderHrefPart(w io.Writer, ctx RenderContext) error {
+ prefix := ""
+ if !e.SkipText {
+ prefix = " "
+ }
+
+ u, err := url.Parse(e.URL)
+ if err == nil && "#"+u.Fragment != e.URL { // if the URL only consists of an anchor, ignore it
+ el := &BaseElement{
+ Token: resolveRelativeURL(e.BaseURL, e.URL),
+ Prefix: prefix,
+ Style: ctx.options.Styles.Link,
+ }
+ if err := el.Render(w, ctx); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/listitem.go b/vendor/github.com/charmbracelet/glamour/ansi/listitem.go
new file mode 100644
index 00000000..66738d4f
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/listitem.go
@@ -0,0 +1,29 @@
+package ansi
+
+import (
+ "io"
+ "strconv"
+)
+
+// An ItemElement is used to render items inside a list.
+type ItemElement struct {
+ IsOrdered bool
+ Enumeration uint
+}
+
+// Render renders an ItemElement.
+func (e *ItemElement) Render(w io.Writer, ctx RenderContext) error {
+ var el *BaseElement
+ if e.IsOrdered {
+ el = &BaseElement{
+ Style: ctx.options.Styles.Enumeration,
+ Prefix: strconv.FormatInt(int64(e.Enumeration), 10), //nolint: gosec
+ }
+ } else {
+ el = &BaseElement{
+ Style: ctx.options.Styles.Item,
+ }
+ }
+
+ return el.Render(w, ctx)
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/margin.go b/vendor/github.com/charmbracelet/glamour/ansi/margin.go
new file mode 100644
index 00000000..9de2ad06
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/margin.go
@@ -0,0 +1,57 @@
+package ansi
+
+import (
+ "fmt"
+ "io"
+
+ "github.com/muesli/reflow/indent"
+ "github.com/muesli/reflow/padding"
+)
+
+// MarginWriter is a Writer that applies indentation and padding around
+// whatever you write to it.
+type MarginWriter struct {
+ w io.Writer
+ pw *padding.Writer
+ iw *indent.Writer
+}
+
+// NewMarginWriter returns a new MarginWriter.
+func NewMarginWriter(ctx RenderContext, w io.Writer, rules StyleBlock) *MarginWriter {
+ bs := ctx.blockStack
+
+ var indentation uint
+ var margin uint
+ if rules.Indent != nil {
+ indentation = *rules.Indent
+ }
+ if rules.Margin != nil {
+ margin = *rules.Margin
+ }
+
+ pw := padding.NewWriterPipe(w, bs.Width(ctx), func(_ io.Writer) {
+ renderText(w, ctx.options.ColorProfile, rules.StylePrimitive, " ")
+ })
+
+ ic := " "
+ if rules.IndentToken != nil {
+ ic = *rules.IndentToken
+ }
+ iw := indent.NewWriterPipe(pw, indentation+margin, func(_ io.Writer) {
+ renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, ic)
+ })
+
+ return &MarginWriter{
+ w: w,
+ pw: pw,
+ iw: iw,
+ }
+}
+
+func (w *MarginWriter) Write(b []byte) (int, error) {
+ n, err := w.iw.Write(b)
+ if err != nil {
+ return 0, fmt.Errorf("glamour: error writing bytes: %w", err)
+ }
+ return n, nil
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/paragraph.go b/vendor/github.com/charmbracelet/glamour/ansi/paragraph.go
new file mode 100644
index 00000000..b76b8fed
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/paragraph.go
@@ -0,0 +1,63 @@
+package ansi
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "strings"
+
+ "github.com/muesli/reflow/wordwrap"
+)
+
+// A ParagraphElement is used to render individual paragraphs.
+type ParagraphElement struct {
+ First bool
+}
+
+// Render renders a ParagraphElement.
+func (e *ParagraphElement) Render(w io.Writer, ctx RenderContext) error {
+ bs := ctx.blockStack
+ rules := ctx.options.Styles.Paragraph
+
+ if !e.First {
+ _, _ = io.WriteString(w, "\n")
+ }
+ be := BlockElement{
+ Block: &bytes.Buffer{},
+ Style: cascadeStyle(bs.Current().Style, rules, false),
+ }
+ bs.Push(be)
+
+ renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, rules.BlockPrefix)
+ renderText(bs.Current().Block, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.Prefix)
+ return nil
+}
+
+// Finish finishes rendering a ParagraphElement.
+func (e *ParagraphElement) Finish(w io.Writer, ctx RenderContext) error {
+ bs := ctx.blockStack
+ rules := bs.Current().Style
+
+ mw := NewMarginWriter(ctx, w, rules)
+ if len(strings.TrimSpace(bs.Current().Block.String())) > 0 {
+ flow := wordwrap.NewWriter(int(bs.Width(ctx))) //nolint: gosec
+ flow.KeepNewlines = ctx.options.PreserveNewLines
+ _, _ = flow.Write(bs.Current().Block.Bytes())
+ if err := flow.Close(); err != nil {
+ return fmt.Errorf("glamour: error closing flow: %w", err)
+ }
+
+ _, err := mw.Write(flow.Bytes())
+ if err != nil {
+ return err
+ }
+ _, _ = io.WriteString(mw, "\n")
+ }
+
+ renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.Suffix)
+ renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, rules.BlockSuffix)
+
+ bs.Current().Block.Reset()
+ bs.Pop()
+ return nil
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/renderer.go b/vendor/github.com/charmbracelet/glamour/ansi/renderer.go
new file mode 100644
index 00000000..56ebfa50
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/renderer.go
@@ -0,0 +1,168 @@
+package ansi
+
+import (
+ "fmt"
+ "io"
+ "net/url"
+ "strings"
+
+ "github.com/muesli/termenv"
+ east "github.com/yuin/goldmark-emoji/ast"
+ "github.com/yuin/goldmark/ast"
+ astext "github.com/yuin/goldmark/extension/ast"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/util"
+)
+
+// Options is used to configure an ANSIRenderer.
+type Options struct {
+ BaseURL string
+ WordWrap int
+ TableWrap *bool
+ InlineTableLinks bool
+ PreserveNewLines bool
+ ColorProfile termenv.Profile
+ Styles StyleConfig
+ ChromaFormatter string
+}
+
+// ANSIRenderer renders markdown content as ANSI escaped sequences.
+type ANSIRenderer struct { //nolint: revive
+ context RenderContext
+}
+
+// NewRenderer returns a new ANSIRenderer with style and options set.
+func NewRenderer(options Options) *ANSIRenderer {
+ return &ANSIRenderer{
+ context: NewRenderContext(options),
+ }
+}
+
+// RegisterFuncs implements NodeRenderer.RegisterFuncs.
+func (r *ANSIRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ // blocks
+ reg.Register(ast.KindDocument, r.renderNode)
+ reg.Register(ast.KindHeading, r.renderNode)
+ reg.Register(ast.KindBlockquote, r.renderNode)
+ reg.Register(ast.KindCodeBlock, r.renderNode)
+ reg.Register(ast.KindFencedCodeBlock, r.renderNode)
+ reg.Register(ast.KindHTMLBlock, r.renderNode)
+ reg.Register(ast.KindList, r.renderNode)
+ reg.Register(ast.KindListItem, r.renderNode)
+ reg.Register(ast.KindParagraph, r.renderNode)
+ reg.Register(ast.KindTextBlock, r.renderNode)
+ reg.Register(ast.KindThematicBreak, r.renderNode)
+
+ // inlines
+ reg.Register(ast.KindAutoLink, r.renderNode)
+ reg.Register(ast.KindCodeSpan, r.renderNode)
+ reg.Register(ast.KindEmphasis, r.renderNode)
+ reg.Register(ast.KindImage, r.renderNode)
+ reg.Register(ast.KindLink, r.renderNode)
+ reg.Register(ast.KindRawHTML, r.renderNode)
+ reg.Register(ast.KindText, r.renderNode)
+ reg.Register(ast.KindString, r.renderNode)
+
+ // tables
+ reg.Register(astext.KindTable, r.renderNode)
+ reg.Register(astext.KindTableHeader, r.renderNode)
+ reg.Register(astext.KindTableRow, r.renderNode)
+ reg.Register(astext.KindTableCell, r.renderNode)
+
+ // definitions
+ reg.Register(astext.KindDefinitionList, r.renderNode)
+ reg.Register(astext.KindDefinitionTerm, r.renderNode)
+ reg.Register(astext.KindDefinitionDescription, r.renderNode)
+
+ // footnotes
+ reg.Register(astext.KindFootnote, r.renderNode)
+ reg.Register(astext.KindFootnoteList, r.renderNode)
+ reg.Register(astext.KindFootnoteLink, r.renderNode)
+ reg.Register(astext.KindFootnoteBacklink, r.renderNode)
+
+ // checkboxes
+ reg.Register(astext.KindTaskCheckBox, r.renderNode)
+
+ // strikethrough
+ reg.Register(astext.KindStrikethrough, r.renderNode)
+
+ // emoji
+ reg.Register(east.KindEmoji, r.renderNode)
+}
+
+func (r *ANSIRenderer) renderNode(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ writeTo := io.Writer(w)
+ bs := r.context.blockStack
+
+ // children get rendered by their parent
+ if isChild(node) {
+ return ast.WalkContinue, nil
+ }
+
+ e := r.NewElement(node, source)
+ if entering { //nolint: nestif
+ // everything below the Document element gets rendered into a block buffer
+ if bs.Len() > 0 {
+ writeTo = io.Writer(bs.Current().Block)
+ }
+
+ _, _ = io.WriteString(writeTo, e.Entering)
+ if e.Renderer != nil {
+ err := e.Renderer.Render(writeTo, r.context)
+ if err != nil {
+ return ast.WalkStop, fmt.Errorf("glamour: error rendering: %w", err)
+ }
+ }
+ } else {
+ // everything below the Document element gets rendered into a block buffer
+ if bs.Len() > 0 {
+ writeTo = io.Writer(bs.Parent().Block)
+ }
+
+ // if we're finished rendering the entire document,
+ // flush to the real writer
+ if node.Type() == ast.TypeDocument {
+ writeTo = w
+ }
+
+ if e.Finisher != nil {
+ err := e.Finisher.Finish(writeTo, r.context)
+ if err != nil {
+ return ast.WalkStop, fmt.Errorf("glamour: error finishing render: %w", err)
+ }
+ }
+
+ _, _ = io.WriteString(bs.Current().Block, e.Exiting)
+ }
+
+ return ast.WalkContinue, nil
+}
+
+func isChild(node ast.Node) bool {
+ for n := node.Parent(); n != nil; n = n.Parent() {
+ // These types are already rendered by their parent
+ switch n.Kind() {
+ case ast.KindCodeSpan, ast.KindAutoLink, ast.KindLink, ast.KindImage, ast.KindEmphasis, astext.KindStrikethrough, astext.KindTableCell:
+ return true
+ }
+ }
+
+ return false
+}
+
+func resolveRelativeURL(baseURL string, rel string) string {
+ u, err := url.Parse(rel)
+ if err != nil {
+ return rel
+ }
+ if u.IsAbs() {
+ return rel
+ }
+ u.Path = strings.TrimPrefix(u.Path, "/")
+
+ base, err := url.Parse(baseURL)
+ if err != nil {
+ return rel
+ }
+ return base.ResolveReference(u).String()
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/style.go b/vendor/github.com/charmbracelet/glamour/ansi/style.go
new file mode 100644
index 00000000..a660a455
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/style.go
@@ -0,0 +1,257 @@
+package ansi
+
+// Chroma holds all the chroma settings.
+type Chroma struct {
+ Text StylePrimitive `json:"text,omitempty"`
+ Error StylePrimitive `json:"error,omitempty"`
+ Comment StylePrimitive `json:"comment,omitempty"`
+ CommentPreproc StylePrimitive `json:"comment_preproc,omitempty"`
+ Keyword StylePrimitive `json:"keyword,omitempty"`
+ KeywordReserved StylePrimitive `json:"keyword_reserved,omitempty"`
+ KeywordNamespace StylePrimitive `json:"keyword_namespace,omitempty"`
+ KeywordType StylePrimitive `json:"keyword_type,omitempty"`
+ Operator StylePrimitive `json:"operator,omitempty"`
+ Punctuation StylePrimitive `json:"punctuation,omitempty"`
+ Name StylePrimitive `json:"name,omitempty"`
+ NameBuiltin StylePrimitive `json:"name_builtin,omitempty"`
+ NameTag StylePrimitive `json:"name_tag,omitempty"`
+ NameAttribute StylePrimitive `json:"name_attribute,omitempty"`
+ NameClass StylePrimitive `json:"name_class,omitempty"`
+ NameConstant StylePrimitive `json:"name_constant,omitempty"`
+ NameDecorator StylePrimitive `json:"name_decorator,omitempty"`
+ NameException StylePrimitive `json:"name_exception,omitempty"`
+ NameFunction StylePrimitive `json:"name_function,omitempty"`
+ NameOther StylePrimitive `json:"name_other,omitempty"`
+ Literal StylePrimitive `json:"literal,omitempty"`
+ LiteralNumber StylePrimitive `json:"literal_number,omitempty"`
+ LiteralDate StylePrimitive `json:"literal_date,omitempty"`
+ LiteralString StylePrimitive `json:"literal_string,omitempty"`
+ LiteralStringEscape StylePrimitive `json:"literal_string_escape,omitempty"`
+ GenericDeleted StylePrimitive `json:"generic_deleted,omitempty"`
+ GenericEmph StylePrimitive `json:"generic_emph,omitempty"`
+ GenericInserted StylePrimitive `json:"generic_inserted,omitempty"`
+ GenericStrong StylePrimitive `json:"generic_strong,omitempty"`
+ GenericSubheading StylePrimitive `json:"generic_subheading,omitempty"`
+ Background StylePrimitive `json:"background,omitempty"`
+}
+
+// StylePrimitive holds all the basic style settings.
+type StylePrimitive struct {
+ BlockPrefix string `json:"block_prefix,omitempty"`
+ BlockSuffix string `json:"block_suffix,omitempty"`
+ Prefix string `json:"prefix,omitempty"`
+ Suffix string `json:"suffix,omitempty"`
+ Color *string `json:"color,omitempty"`
+ BackgroundColor *string `json:"background_color,omitempty"`
+ Underline *bool `json:"underline,omitempty"`
+ Bold *bool `json:"bold,omitempty"`
+ Upper *bool `json:"upper,omitempty"`
+ Lower *bool `json:"lower,omitempty"`
+ Title *bool `json:"title,omitempty"`
+ Italic *bool `json:"italic,omitempty"`
+ CrossedOut *bool `json:"crossed_out,omitempty"`
+ Faint *bool `json:"faint,omitempty"`
+ Conceal *bool `json:"conceal,omitempty"`
+ Overlined *bool `json:"overlined,omitempty"`
+ Inverse *bool `json:"inverse,omitempty"`
+ Blink *bool `json:"blink,omitempty"`
+ Format string `json:"format,omitempty"`
+}
+
+// StyleTask holds the style settings for a task item.
+type StyleTask struct {
+ StylePrimitive
+ Ticked string `json:"ticked,omitempty"`
+ Unticked string `json:"unticked,omitempty"`
+}
+
+// StyleBlock holds the basic style settings for block elements.
+type StyleBlock struct {
+ StylePrimitive
+ Indent *uint `json:"indent,omitempty"`
+ IndentToken *string `json:"indent_token,omitempty"`
+ Margin *uint `json:"margin,omitempty"`
+}
+
+// StyleCodeBlock holds the style settings for a code block.
+type StyleCodeBlock struct {
+ StyleBlock
+ Theme string `json:"theme,omitempty"`
+ Chroma *Chroma `json:"chroma,omitempty"`
+}
+
+// StyleList holds the style settings for a list.
+type StyleList struct {
+ StyleBlock
+ LevelIndent uint `json:"level_indent,omitempty"`
+}
+
+// StyleTable holds the style settings for a table.
+type StyleTable struct {
+ StyleBlock
+ CenterSeparator *string `json:"center_separator,omitempty"`
+ ColumnSeparator *string `json:"column_separator,omitempty"`
+ RowSeparator *string `json:"row_separator,omitempty"`
+}
+
+// StyleConfig is used to configure the styling behavior of an ANSIRenderer.
+type StyleConfig struct {
+ Document StyleBlock `json:"document,omitempty"`
+ BlockQuote StyleBlock `json:"block_quote,omitempty"`
+ Paragraph StyleBlock `json:"paragraph,omitempty"`
+ List StyleList `json:"list,omitempty"`
+
+ Heading StyleBlock `json:"heading,omitempty"`
+ H1 StyleBlock `json:"h1,omitempty"`
+ H2 StyleBlock `json:"h2,omitempty"`
+ H3 StyleBlock `json:"h3,omitempty"`
+ H4 StyleBlock `json:"h4,omitempty"`
+ H5 StyleBlock `json:"h5,omitempty"`
+ H6 StyleBlock `json:"h6,omitempty"`
+
+ Text StylePrimitive `json:"text,omitempty"`
+ Strikethrough StylePrimitive `json:"strikethrough,omitempty"`
+ Emph StylePrimitive `json:"emph,omitempty"`
+ Strong StylePrimitive `json:"strong,omitempty"`
+ HorizontalRule StylePrimitive `json:"hr,omitempty"`
+
+ Item StylePrimitive `json:"item,omitempty"`
+ Enumeration StylePrimitive `json:"enumeration,omitempty"`
+ Task StyleTask `json:"task,omitempty"`
+
+ Link StylePrimitive `json:"link,omitempty"`
+ LinkText StylePrimitive `json:"link_text,omitempty"`
+
+ Image StylePrimitive `json:"image,omitempty"`
+ ImageText StylePrimitive `json:"image_text,omitempty"`
+
+ Code StyleBlock `json:"code,omitempty"`
+ CodeBlock StyleCodeBlock `json:"code_block,omitempty"`
+
+ Table StyleTable `json:"table,omitempty"`
+
+ DefinitionList StyleBlock `json:"definition_list,omitempty"`
+ DefinitionTerm StylePrimitive `json:"definition_term,omitempty"`
+ DefinitionDescription StylePrimitive `json:"definition_description,omitempty"`
+
+ HTMLBlock StyleBlock `json:"html_block,omitempty"`
+ HTMLSpan StyleBlock `json:"html_span,omitempty"`
+}
+
+func cascadeStyles(s ...StyleBlock) StyleBlock {
+ var r StyleBlock
+ for _, v := range s {
+ r = cascadeStyle(r, v, true)
+ }
+ return r
+}
+
+func cascadeStylePrimitives(s ...StylePrimitive) StylePrimitive {
+ var r StylePrimitive
+ for _, v := range s {
+ r = cascadeStylePrimitive(r, v, true)
+ }
+ return r
+}
+
+func cascadeStylePrimitive(parent, child StylePrimitive, toBlock bool) StylePrimitive {
+ s := child
+
+ s.Color = parent.Color
+ s.BackgroundColor = parent.BackgroundColor
+ s.Underline = parent.Underline
+ s.Bold = parent.Bold
+ s.Upper = parent.Upper
+ s.Title = parent.Title
+ s.Lower = parent.Lower
+ s.Italic = parent.Italic
+ s.CrossedOut = parent.CrossedOut
+ s.Faint = parent.Faint
+ s.Conceal = parent.Conceal
+ s.Overlined = parent.Overlined
+ s.Inverse = parent.Inverse
+ s.Blink = parent.Blink
+
+ if toBlock {
+ s.BlockPrefix = parent.BlockPrefix
+ s.BlockSuffix = parent.BlockSuffix
+ s.Prefix = parent.Prefix
+ s.Suffix = parent.Suffix
+ }
+
+ if child.Color != nil {
+ s.Color = child.Color
+ }
+ if child.BackgroundColor != nil {
+ s.BackgroundColor = child.BackgroundColor
+ }
+ if child.Underline != nil {
+ s.Underline = child.Underline
+ }
+ if child.Bold != nil {
+ s.Bold = child.Bold
+ }
+ if child.Upper != nil {
+ s.Upper = child.Upper
+ }
+ if child.Lower != nil {
+ s.Lower = child.Lower
+ }
+ if child.Title != nil {
+ s.Title = child.Title
+ }
+ if child.Italic != nil {
+ s.Italic = child.Italic
+ }
+ if child.CrossedOut != nil {
+ s.CrossedOut = child.CrossedOut
+ }
+ if child.Faint != nil {
+ s.Faint = child.Faint
+ }
+ if child.Conceal != nil {
+ s.Conceal = child.Conceal
+ }
+ if child.Overlined != nil {
+ s.Overlined = child.Overlined
+ }
+ if child.Inverse != nil {
+ s.Inverse = child.Inverse
+ }
+ if child.Blink != nil {
+ s.Blink = child.Blink
+ }
+ if child.BlockPrefix != "" {
+ s.BlockPrefix = child.BlockPrefix
+ }
+ if child.BlockSuffix != "" {
+ s.BlockSuffix = child.BlockSuffix
+ }
+ if child.Prefix != "" {
+ s.Prefix = child.Prefix
+ }
+ if child.Suffix != "" {
+ s.Suffix = child.Suffix
+ }
+ if child.Format != "" {
+ s.Format = child.Format
+ }
+
+ return s
+}
+
+func cascadeStyle(parent StyleBlock, child StyleBlock, toBlock bool) StyleBlock {
+ s := child
+ s.StylePrimitive = cascadeStylePrimitive(parent.StylePrimitive, child.StylePrimitive, toBlock)
+
+ if toBlock {
+ s.Indent = parent.Indent
+ s.Margin = parent.Margin
+ }
+
+ if child.Indent != nil {
+ s.Indent = child.Indent
+ }
+
+ return s
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/table.go b/vendor/github.com/charmbracelet/glamour/ansi/table.go
new file mode 100644
index 00000000..98c420ea
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/table.go
@@ -0,0 +1,199 @@
+package ansi
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+
+ "github.com/charmbracelet/lipgloss"
+ "github.com/charmbracelet/lipgloss/table"
+ "github.com/muesli/reflow/indent"
+ astext "github.com/yuin/goldmark/extension/ast"
+)
+
+// A TableElement is used to render tables.
+type TableElement struct {
+ lipgloss *table.Table
+ table *astext.Table
+ header []string
+ row []string
+ source []byte
+
+ tableImages []tableLink
+ tableLinks []tableLink
+}
+
+// A TableRowElement is used to render a single row in a table.
+type TableRowElement struct{}
+
+// A TableHeadElement is used to render a table's head element.
+type TableHeadElement struct{}
+
+// A TableCellElement is used to render a single cell in a row.
+type TableCellElement struct {
+ Children []ElementRenderer
+ Head bool
+}
+
+// Render renders a TableElement.
+func (e *TableElement) Render(w io.Writer, ctx RenderContext) error {
+ bs := ctx.blockStack
+
+ var indentation uint
+ var margin uint
+ rules := ctx.options.Styles.Table
+ if rules.Indent != nil {
+ indentation = *rules.Indent
+ }
+ if rules.Margin != nil {
+ margin = *rules.Margin
+ }
+
+ iw := indent.NewWriterPipe(w, indentation+margin, func(_ io.Writer) {
+ renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, " ")
+ })
+
+ style := bs.With(rules.StylePrimitive)
+
+ renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockPrefix)
+ renderText(iw, ctx.options.ColorProfile, style, rules.Prefix)
+ width := int(ctx.blockStack.Width(ctx)) //nolint: gosec
+
+ wrap := true
+ if ctx.options.TableWrap != nil {
+ wrap = *ctx.options.TableWrap
+ }
+ ctx.table.lipgloss = table.New().Width(width).Wrap(wrap)
+
+ if err := e.collectLinksAndImages(ctx); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (e *TableElement) setStyles(ctx RenderContext) {
+ ctx.table.lipgloss = ctx.table.lipgloss.StyleFunc(func(_, col int) lipgloss.Style {
+ st := lipgloss.NewStyle().Inline(false)
+ // Default Styles
+ st = st.Margin(0, 1)
+
+ // Override with custom styles
+ if m := ctx.options.Styles.Table.Margin; m != nil {
+ st = st.Padding(0, int(*m)) //nolint: gosec
+ }
+ switch e.table.Alignments[col] {
+ case astext.AlignLeft:
+ st = st.Align(lipgloss.Left).PaddingRight(0)
+ case astext.AlignCenter:
+ st = st.Align(lipgloss.Center)
+ case astext.AlignRight:
+ st = st.Align(lipgloss.Right).PaddingLeft(0)
+ case astext.AlignNone:
+ // do nothing
+ }
+
+ return st
+ })
+}
+
+func (e *TableElement) setBorders(ctx RenderContext) {
+ rules := ctx.options.Styles.Table
+ border := lipgloss.NormalBorder()
+
+ if rules.RowSeparator != nil && rules.ColumnSeparator != nil {
+ border = lipgloss.Border{
+ Top: *rules.RowSeparator,
+ Bottom: *rules.RowSeparator,
+ Left: *rules.ColumnSeparator,
+ Right: *rules.ColumnSeparator,
+ Middle: *rules.CenterSeparator,
+ }
+ }
+ ctx.table.lipgloss.Border(border)
+ ctx.table.lipgloss.BorderTop(false)
+ ctx.table.lipgloss.BorderLeft(false)
+ ctx.table.lipgloss.BorderRight(false)
+ ctx.table.lipgloss.BorderBottom(false)
+}
+
+// Finish finishes rendering a TableElement.
+func (e *TableElement) Finish(_ io.Writer, ctx RenderContext) error {
+ defer func() {
+ ctx.table.lipgloss = nil
+ ctx.table.tableImages = nil
+ ctx.table.tableLinks = nil
+ }()
+
+ rules := ctx.options.Styles.Table
+
+ e.setStyles(ctx)
+ e.setBorders(ctx)
+
+ ow := ctx.blockStack.Current().Block
+ if _, err := ow.WriteString(ctx.table.lipgloss.String()); err != nil {
+ return fmt.Errorf("glamour: error writing to buffer: %w", err)
+ }
+
+ renderText(ow, ctx.options.ColorProfile, ctx.blockStack.With(rules.StylePrimitive), rules.Suffix)
+ renderText(ow, ctx.options.ColorProfile, ctx.blockStack.Current().Style.StylePrimitive, rules.BlockSuffix)
+
+ e.printTableLinks(ctx)
+
+ return nil
+}
+
+// Finish finishes rendering a TableRowElement.
+func (e *TableRowElement) Finish(_ io.Writer, ctx RenderContext) error {
+ if ctx.table.lipgloss == nil {
+ return nil
+ }
+
+ ctx.table.lipgloss.Row(ctx.table.row...)
+ ctx.table.row = []string{}
+ return nil
+}
+
+// Finish finishes rendering a TableHeadElement.
+func (e *TableHeadElement) Finish(_ io.Writer, ctx RenderContext) error {
+ if ctx.table.lipgloss == nil {
+ return nil
+ }
+
+ ctx.table.lipgloss.Headers(ctx.table.header...)
+ ctx.table.header = []string{}
+ return nil
+}
+
+// Render renders a TableCellElement.
+func (e *TableCellElement) Render(_ io.Writer, ctx RenderContext) error {
+ var b bytes.Buffer
+ style := ctx.options.Styles.Table.StylePrimitive
+ for _, child := range e.Children {
+ if r, ok := child.(StyleOverriderElementRenderer); ok {
+ if err := r.StyleOverrideRender(&b, ctx, style); err != nil {
+ return fmt.Errorf("glamour: error rendering with style: %w", err)
+ }
+ } else {
+ var bb bytes.Buffer
+ if err := child.Render(&bb, ctx); err != nil {
+ return fmt.Errorf("glamour: error rendering: %w", err)
+ }
+ el := &BaseElement{
+ Token: bb.String(),
+ Style: style,
+ }
+ if err := el.Render(&b, ctx); err != nil {
+ return err
+ }
+ }
+ }
+
+ if e.Head {
+ ctx.table.header = append(ctx.table.header, b.String())
+ } else {
+ ctx.table.row = append(ctx.table.row, b.String())
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/table_links.go b/vendor/github.com/charmbracelet/glamour/ansi/table_links.go
new file mode 100644
index 00000000..ab7a4293
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/table_links.go
@@ -0,0 +1,235 @@
+package ansi
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/url"
+ "slices"
+ "strconv"
+ "strings"
+
+ "github.com/charmbracelet/glamour/internal/autolink"
+ xansi "github.com/charmbracelet/x/ansi"
+ "github.com/charmbracelet/x/exp/slice"
+ "github.com/yuin/goldmark/ast"
+ astext "github.com/yuin/goldmark/extension/ast"
+)
+
+type tableLink struct {
+ href string
+ title string
+ content string
+ linkType linkType
+}
+
+type linkType int
+
+const (
+ _ linkType = iota
+ linkTypeAuto
+ linkTypeImage
+ linkTypeRegular
+)
+
+func (e *TableElement) printTableLinks(ctx RenderContext) {
+ if !e.shouldPrintTableLinks(ctx) {
+ return
+ }
+
+ w := ctx.blockStack.Current().Block
+ termWidth := int(ctx.blockStack.Width(ctx)) //nolint: gosec
+
+ renderLinkText := func(link tableLink, position, padding int) string {
+ token := strings.Repeat(" ", padding)
+ style := ctx.options.Styles.LinkText
+
+ switch link.linkType {
+ case linkTypeAuto, linkTypeRegular:
+ token += fmt.Sprintf("[%d]: %s", position, link.content)
+ case linkTypeImage:
+ token += link.content
+ style = ctx.options.Styles.ImageText
+ style.Prefix = fmt.Sprintf("[%d]: %s", position, style.Prefix)
+ }
+
+ var b bytes.Buffer
+ el := &BaseElement{Token: token, Style: style}
+ _ = el.Render(io.MultiWriter(w, &b), ctx)
+ return b.String()
+ }
+
+ renderLinkHref := func(link tableLink, linkText string) {
+ style := ctx.options.Styles.Link
+ if link.linkType == linkTypeImage {
+ style = ctx.options.Styles.Image
+ }
+
+ // XXX(@andreynering): Once #411 is merged, use the hyperlink
+ // protocol to make the link work for the full URL even if we
+ // show it truncated.
+ linkMaxWidth := max(termWidth-xansi.StringWidth(linkText)-1, 0)
+ token := xansi.Truncate(link.href, linkMaxWidth, "…")
+
+ el := &BaseElement{Token: token, Style: style}
+ _ = el.Render(w, ctx)
+ }
+
+ renderString := func(str string) {
+ renderText(w, ctx.options.ColorProfile, ctx.blockStack.Current().Style.StylePrimitive, str)
+ }
+
+ paddingFor := func(total, position int) int {
+ totalSize := len(strconv.Itoa(total))
+ positionSize := len(strconv.Itoa(position))
+
+ return max(totalSize-positionSize, 0)
+ }
+
+ renderList := func(list []tableLink) {
+ for i, item := range list {
+ position := i + 1
+ padding := paddingFor(len(list), position)
+
+ renderString("\n")
+ linkText := renderLinkText(item, position, padding)
+ renderString(" ")
+ renderLinkHref(item, linkText)
+ }
+ }
+
+ if len(ctx.table.tableLinks) > 0 {
+ renderString("\n")
+ }
+ renderList(ctx.table.tableLinks)
+
+ if len(ctx.table.tableImages) > 0 {
+ renderString("\n")
+ }
+ renderList(ctx.table.tableImages)
+}
+
+func (e *TableElement) shouldPrintTableLinks(ctx RenderContext) bool {
+ if ctx.options.InlineTableLinks {
+ return false
+ }
+ if len(ctx.table.tableLinks) == 0 && len(ctx.table.tableImages) == 0 {
+ return false
+ }
+ return true
+}
+
+func (e *TableElement) collectLinksAndImages(ctx RenderContext) error {
+ images := make([]tableLink, 0)
+ links := make([]tableLink, 0)
+
+ err := ast.Walk(e.table, func(node ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+
+ switch n := node.(type) {
+ case *ast.AutoLink:
+ uri := string(n.URL(e.source))
+ autoLink := tableLink{
+ href: uri,
+ content: linkDomain(uri),
+ linkType: linkTypeAuto,
+ }
+ if shortned, ok := autolink.Detect(uri); ok {
+ autoLink.content = shortned
+ }
+ links = append(links, autoLink)
+ case *ast.Image:
+ content, err := nodeContent(node, e.source)
+ if err != nil {
+ return ast.WalkStop, err
+ }
+ image := tableLink{
+ href: string(n.Destination),
+ title: string(n.Title),
+ content: string(content),
+ linkType: linkTypeImage,
+ }
+ if image.content == "" {
+ image.content = linkDomain(image.href)
+ }
+ images = append(images, image)
+ case *ast.Link:
+ content, err := nodeContent(node, e.source)
+ if err != nil {
+ return ast.WalkStop, err
+ }
+ link := tableLink{
+ href: string(n.Destination),
+ title: string(n.Title),
+ content: string(content),
+ linkType: linkTypeRegular,
+ }
+ links = append(links, link)
+ }
+
+ return ast.WalkContinue, nil
+ })
+ if err != nil {
+ return fmt.Errorf("glamour: error collecting links: %w", err)
+ }
+
+ ctx.table.tableImages = slice.Uniq(images)
+ ctx.table.tableLinks = slice.Uniq(links)
+ return nil
+}
+
+func isInsideTable(node ast.Node) bool {
+ parent := node.Parent()
+ for parent != nil {
+ switch parent.Kind() {
+ case astext.KindTable, astext.KindTableHeader, astext.KindTableRow, astext.KindTableCell:
+ return true
+ default:
+ parent = parent.Parent()
+ }
+ }
+ return false
+}
+
+func nodeContent(node ast.Node, source []byte) ([]byte, error) {
+ var builder bytes.Buffer
+
+ var traverse func(node ast.Node) error
+ traverse = func(node ast.Node) error {
+ for n := node.FirstChild(); n != nil; n = n.NextSibling() {
+ switch nn := n.(type) {
+ case *ast.Text:
+ if _, err := builder.Write(nn.Segment.Value(source)); err != nil {
+ return fmt.Errorf("glamour: error writing text node: %w", err)
+ }
+ default:
+ if err := traverse(nn); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+ }
+ if err := traverse(node); err != nil {
+ return nil, err
+ }
+
+ return builder.Bytes(), nil
+}
+
+func linkDomain(href string) string {
+ if uri, err := url.Parse(href); err == nil {
+ return uri.Hostname()
+ }
+ return "link"
+}
+
+func linkWithSuffix(tl tableLink, list []tableLink) string {
+ index := slices.Index(list, tl)
+ if index == -1 {
+ return tl.content
+ }
+ return fmt.Sprintf("%s[%d]", tl.content, index+1)
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/task.go b/vendor/github.com/charmbracelet/glamour/ansi/task.go
new file mode 100644
index 00000000..61d2c838
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/task.go
@@ -0,0 +1,27 @@
+package ansi
+
+import (
+ "io"
+)
+
+// A TaskElement is used to render tasks inside a todo-list.
+type TaskElement struct {
+ Checked bool
+}
+
+// Render renders a TaskElement.
+func (e *TaskElement) Render(w io.Writer, ctx RenderContext) error {
+ var el *BaseElement
+
+ pre := ctx.options.Styles.Task.Unticked
+ if e.Checked {
+ pre = ctx.options.Styles.Task.Ticked
+ }
+
+ el = &BaseElement{
+ Prefix: pre,
+ Style: ctx.options.Styles.Task.StylePrimitive,
+ }
+
+ return el.Render(w, ctx)
+}
diff --git a/vendor/github.com/charmbracelet/glamour/ansi/templatehelper.go b/vendor/github.com/charmbracelet/glamour/ansi/templatehelper.go
new file mode 100644
index 00000000..bd31d376
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/ansi/templatehelper.go
@@ -0,0 +1,86 @@
+package ansi
+
+import (
+ "regexp"
+ "strings"
+ "text/template"
+
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+)
+
+// TemplateFuncMap contains a few useful template helpers.
+var (
+ TemplateFuncMap = template.FuncMap{
+ "Left": func(values ...interface{}) string {
+ s := values[0].(string)
+ n := values[1].(int)
+ if n > len(s) {
+ n = len(s)
+ }
+
+ return s[:n]
+ },
+ "Matches": func(values ...interface{}) bool {
+ ok, _ := regexp.MatchString(values[1].(string), values[0].(string))
+ return ok
+ },
+ "Mid": func(values ...interface{}) string {
+ s := values[0].(string)
+ l := values[1].(int)
+ if l > len(s) {
+ l = len(s)
+ }
+
+ if len(values) > 2 { //nolint:mnd
+ r := values[2].(int)
+ if r > len(s) {
+ r = len(s)
+ }
+ return s[l:r]
+ }
+ return s[l:]
+ },
+ "Right": func(values ...interface{}) string {
+ s := values[0].(string)
+ n := len(s) - values[1].(int)
+ if n < 0 {
+ n = 0
+ }
+
+ return s[n:]
+ },
+ "Last": func(values ...interface{}) string {
+ return values[0].([]string)[len(values[0].([]string))-1]
+ },
+ // strings functions
+ "Compare": strings.Compare, // 1.5+ only
+ "Contains": strings.Contains,
+ "ContainsAny": strings.ContainsAny,
+ "Count": strings.Count,
+ "EqualFold": strings.EqualFold,
+ "HasPrefix": strings.HasPrefix,
+ "HasSuffix": strings.HasSuffix,
+ "Index": strings.Index,
+ "IndexAny": strings.IndexAny,
+ "Join": strings.Join,
+ "LastIndex": strings.LastIndex,
+ "LastIndexAny": strings.LastIndexAny,
+ "Repeat": strings.Repeat,
+ "Replace": strings.Replace,
+ "Split": strings.Split,
+ "SplitAfter": strings.SplitAfter,
+ "SplitAfterN": strings.SplitAfterN,
+ "SplitN": strings.SplitN,
+ "Title": cases.Title(language.English).String,
+ "ToLower": cases.Lower(language.English).String,
+ "ToTitle": cases.Upper(language.English).String,
+ "ToUpper": strings.ToUpper,
+ "Trim": strings.Trim,
+ "TrimLeft": strings.TrimLeft,
+ "TrimPrefix": strings.TrimPrefix,
+ "TrimRight": strings.TrimRight,
+ "TrimSpace": strings.TrimSpace,
+ "TrimSuffix": strings.TrimSuffix,
+ }
+)
diff --git a/vendor/github.com/charmbracelet/glamour/examples.sh b/vendor/github.com/charmbracelet/glamour/examples.sh
new file mode 100644
index 00000000..48aecf69
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/examples.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -e
+
+for element in ./styles/examples/*.md; do
+ echo "Generating screenshot for element ${element}"
+ basename="`basename -s .md ${element}`"
+ stylename="${basename}.style"
+ filename="${basename}.png"
+
+ # take screenshot
+ ./termshot -o ./styles/examples/ -f "$filename" glow -s ./styles/examples/${stylename} ${element}
+
+ # add border
+ convert -bordercolor black -border 16x16 "./styles/examples/$filename" "./styles/examples/$filename"
+
+ # optimize filesize
+ pngcrush -ow "./styles/examples/$filename"
+done
diff --git a/vendor/github.com/charmbracelet/glamour/gallery.sh b/vendor/github.com/charmbracelet/glamour/gallery.sh
new file mode 100644
index 00000000..36d95008
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/gallery.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+if ! command -v freeze &> /dev/null; then
+ echo "freeze not found. Please install freeze to capture images."
+ echo "https://github.com/charmbracelet/freeze/"
+ exit 1
+fi
+
+defaultStyles=("ascii" "auto" "dark" "dracula" "light" "notty" "pink")
+
+for style in "${defaultStyles[@]}"; do
+ echo "Generating screenshot for ${style}"
+ # take screenshot
+ if [[ $style == *"light"* ]]; then
+ # Provide a light background to images
+ freeze -x "go run ./examples/artichokes ${style}" -b "#FAFAFA" -o "./styles/gallery/${style}.png"
+ else
+ freeze -x "go run ./examples/artichokes ${style}" -o "./styles/gallery/${style}.png"
+ fi
+
+ # optimize filesize
+ pngcrush -ow "./styles/gallery/$style.png"
+done
diff --git a/vendor/github.com/charmbracelet/glamour/glamour.go b/vendor/github.com/charmbracelet/glamour/glamour.go
new file mode 100644
index 00000000..bc1870cc
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/glamour.go
@@ -0,0 +1,322 @@
+// Package glamour lets you render markdown documents & templates on ANSI
+// compatible terminals. You can create your own stylesheet or simply use one of
+// the stylish defaults
+package glamour
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+
+ "github.com/muesli/termenv"
+ "github.com/yuin/goldmark"
+ emoji "github.com/yuin/goldmark-emoji"
+ "github.com/yuin/goldmark/extension"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/util"
+ "golang.org/x/term"
+
+ "github.com/charmbracelet/glamour/ansi"
+ styles "github.com/charmbracelet/glamour/styles"
+)
+
+const (
+ defaultWidth = 80
+ highPriority = 1000
+)
+
+// A TermRendererOption sets an option on a TermRenderer.
+type TermRendererOption func(*TermRenderer) error
+
+// TermRenderer can be used to render markdown content, posing a depth of
+// customization and styles to fit your needs.
+type TermRenderer struct {
+ md goldmark.Markdown
+ ansiOptions ansi.Options
+ buf bytes.Buffer
+ renderBuf bytes.Buffer
+}
+
+// Render initializes a new TermRenderer and renders a markdown with a specific
+// style.
+func Render(in string, stylePath string) (string, error) {
+ b, err := RenderBytes([]byte(in), stylePath)
+ return string(b), err
+}
+
+// RenderWithEnvironmentConfig initializes a new TermRenderer and renders a
+// markdown with a specific style defined by the GLAMOUR_STYLE environment variable.
+func RenderWithEnvironmentConfig(in string) (string, error) {
+ b, err := RenderBytes([]byte(in), getEnvironmentStyle())
+ return string(b), err
+}
+
+// RenderBytes initializes a new TermRenderer and renders a markdown with a
+// specific style.
+func RenderBytes(in []byte, stylePath string) ([]byte, error) {
+ r, err := NewTermRenderer(
+ WithStylePath(stylePath),
+ )
+ if err != nil {
+ return nil, err
+ }
+ return r.RenderBytes(in)
+}
+
+// NewTermRenderer returns a new TermRenderer the given options.
+func NewTermRenderer(options ...TermRendererOption) (*TermRenderer, error) {
+ tr := &TermRenderer{
+ md: goldmark.New(
+ goldmark.WithExtensions(
+ extension.GFM,
+ extension.DefinitionList,
+ ),
+ goldmark.WithParserOptions(
+ parser.WithAutoHeadingID(),
+ ),
+ ),
+ ansiOptions: ansi.Options{
+ WordWrap: defaultWidth,
+ ColorProfile: termenv.TrueColor,
+ },
+ }
+ for _, o := range options {
+ if err := o(tr); err != nil {
+ return nil, err
+ }
+ }
+ ar := ansi.NewRenderer(tr.ansiOptions)
+ tr.md.SetRenderer(
+ renderer.NewRenderer(
+ renderer.WithNodeRenderers(
+ util.Prioritized(ar, highPriority),
+ ),
+ ),
+ )
+ return tr, nil
+}
+
+// WithBaseURL sets a TermRenderer's base URL.
+func WithBaseURL(baseURL string) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ tr.ansiOptions.BaseURL = baseURL
+ return nil
+ }
+}
+
+// WithColorProfile sets the TermRenderer's color profile
+// (TrueColor / ANSI256 / ANSI).
+func WithColorProfile(profile termenv.Profile) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ tr.ansiOptions.ColorProfile = profile
+ return nil
+ }
+}
+
+// WithStandardStyle sets a TermRenderer's styles with a standard (builtin)
+// style.
+func WithStandardStyle(style string) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ styles, err := getDefaultStyle(style)
+ if err != nil {
+ return err
+ }
+ tr.ansiOptions.Styles = *styles
+ return nil
+ }
+}
+
+// WithAutoStyle sets a TermRenderer's styles with either the standard dark
+// or light style, depending on the terminal's background color at run-time.
+func WithAutoStyle() TermRendererOption {
+ return WithStandardStyle(styles.AutoStyle)
+}
+
+// WithEnvironmentConfig sets a TermRenderer's styles based on the
+// GLAMOUR_STYLE environment variable.
+func WithEnvironmentConfig() TermRendererOption {
+ return WithStylePath(getEnvironmentStyle())
+}
+
+// WithStylePath sets a TermRenderer's style from stylePath. stylePath is first
+// interpreted as a filename. If no such file exists, it is re-interpreted as a
+// standard style.
+func WithStylePath(stylePath string) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ styles, err := getDefaultStyle(stylePath)
+ if err != nil {
+ jsonBytes, err := os.ReadFile(stylePath)
+ if err != nil {
+ return fmt.Errorf("glamour: error reading file: %w", err)
+ }
+
+ return json.Unmarshal(jsonBytes, &tr.ansiOptions.Styles)
+ }
+ tr.ansiOptions.Styles = *styles
+ return nil
+ }
+}
+
+// WithStyles sets a TermRenderer's styles.
+func WithStyles(styles ansi.StyleConfig) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ tr.ansiOptions.Styles = styles
+ return nil
+ }
+}
+
+// WithStylesFromJSONBytes sets a TermRenderer's styles by parsing styles from
+// jsonBytes.
+func WithStylesFromJSONBytes(jsonBytes []byte) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ return json.Unmarshal(jsonBytes, &tr.ansiOptions.Styles)
+ }
+}
+
+// WithStylesFromJSONFile sets a TermRenderer's styles from a JSON file.
+func WithStylesFromJSONFile(filename string) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ jsonBytes, err := os.ReadFile(filename)
+ if err != nil {
+ return fmt.Errorf("glamour: error reading file: %w", err)
+ }
+ return json.Unmarshal(jsonBytes, &tr.ansiOptions.Styles)
+ }
+}
+
+// WithWordWrap sets a TermRenderer's word wrap.
+func WithWordWrap(wordWrap int) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ tr.ansiOptions.WordWrap = wordWrap
+ return nil
+ }
+}
+
+// WithTableWrap controls whether table content will wrap if too long.
+// This is true by default. If false, table content will be truncated with an
+// ellipsis if too long to fit.
+func WithTableWrap(tableWrap bool) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ tr.ansiOptions.TableWrap = &tableWrap
+ return nil
+ }
+}
+
+// WithInlineTableLinks forces tables to render links inline. By default,links
+// are rendered as a list of links at the bottom of the table.
+func WithInlineTableLinks(inlineTableLinks bool) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ tr.ansiOptions.InlineTableLinks = inlineTableLinks
+ return nil
+ }
+}
+
+// WithPreservedNewLines preserves newlines from being replaced.
+func WithPreservedNewLines() TermRendererOption {
+ return func(tr *TermRenderer) error {
+ tr.ansiOptions.PreserveNewLines = true
+ return nil
+ }
+}
+
+// WithEmoji sets a TermRenderer's emoji rendering.
+func WithEmoji() TermRendererOption {
+ return func(tr *TermRenderer) error {
+ emoji.New().Extend(tr.md)
+ return nil
+ }
+}
+
+// WithChromaFormatter sets a TermRenderer's chroma formatter used for code blocks.
+func WithChromaFormatter(formatter string) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ tr.ansiOptions.ChromaFormatter = formatter
+ return nil
+ }
+}
+
+// WithOptions sets multiple TermRenderer options within a single TermRendererOption.
+func WithOptions(options ...TermRendererOption) TermRendererOption {
+ return func(tr *TermRenderer) error {
+ for _, o := range options {
+ if err := o(tr); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+}
+
+func (tr *TermRenderer) Read(b []byte) (int, error) {
+ n, err := tr.renderBuf.Read(b)
+ if err == io.EOF {
+ return n, io.EOF
+ }
+ if err != nil {
+ return 0, fmt.Errorf("glamour: error reading from buffer: %w", err)
+ }
+ return n, nil
+}
+
+func (tr *TermRenderer) Write(b []byte) (int, error) {
+ n, err := tr.buf.Write(b)
+ if err != nil {
+ return 0, fmt.Errorf("glamour: error writing bytes: %w", err)
+ }
+ return n, nil
+}
+
+// Close must be called after writing to TermRenderer. You can then retrieve
+// the rendered markdown by calling Read.
+func (tr *TermRenderer) Close() error {
+ err := tr.md.Convert(tr.buf.Bytes(), &tr.renderBuf)
+ if err != nil {
+ return fmt.Errorf("glamour: error converting markdown: %w", err)
+ }
+
+ tr.buf.Reset()
+ return nil
+}
+
+// Render returns the markdown rendered into a string.
+func (tr *TermRenderer) Render(in string) (string, error) {
+ b, err := tr.RenderBytes([]byte(in))
+ return string(b), err
+}
+
+// RenderBytes returns the markdown rendered into a byte slice.
+func (tr *TermRenderer) RenderBytes(in []byte) ([]byte, error) {
+ var buf bytes.Buffer
+ err := tr.md.Convert(in, &buf)
+ return buf.Bytes(), err
+}
+
+func getEnvironmentStyle() string {
+ glamourStyle := os.Getenv("GLAMOUR_STYLE")
+ if len(glamourStyle) == 0 {
+ glamourStyle = styles.AutoStyle
+ }
+
+ return glamourStyle
+}
+
+func getDefaultStyle(style string) (*ansi.StyleConfig, error) {
+ if style == styles.AutoStyle {
+ if !term.IsTerminal(int(os.Stdout.Fd())) {
+ return &styles.NoTTYStyleConfig, nil
+ }
+ if termenv.HasDarkBackground() {
+ return &styles.DarkStyleConfig, nil
+ }
+ return &styles.LightStyleConfig, nil
+ }
+
+ styles, ok := styles.DefaultStyles[style]
+ if !ok {
+ return nil, fmt.Errorf("%s: style not found", style)
+ }
+ return styles, nil
+}
diff --git a/vendor/github.com/charmbracelet/glamour/internal/autolink/autolink.go b/vendor/github.com/charmbracelet/glamour/internal/autolink/autolink.go
new file mode 100644
index 00000000..99d0501b
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/internal/autolink/autolink.go
@@ -0,0 +1,55 @@
+// Package autolink provides a function to detect and format GitHub links into
+// a more readable manner.
+package autolink
+
+import (
+ "fmt"
+ "regexp"
+)
+
+type pattern struct {
+ pattern *regexp.Regexp
+ yield func(m []string) string
+}
+
+var patterns = []pattern{
+ {
+ regexp.MustCompile(`^https?://github\.com/([A-z0-9_-]+)/([A-z0-9_-]+)/(issues?|pulls?|discussions?)/([0-9]+)$`),
+ func(m []string) string { return fmt.Sprintf("%s/%s#%s", m[1], m[2], m[4]) },
+ },
+ {
+ regexp.MustCompile(`^https?://github\.com/([A-z0-9_-]+)/([A-z0-9_-]+)/(issues?|pulls?|discussions?)/([0-9]+)#issuecomment-[0-9]+$`),
+ func(m []string) string { return fmt.Sprintf("%s/%s#%s (comment)", m[1], m[2], m[4]) },
+ },
+ {
+ regexp.MustCompile(`^https?://github\.com/([A-z0-9_-]+)/([A-z0-9_-]+)/pulls?/([0-9]+)#discussion_r[0-9]+$`),
+ func(m []string) string { return fmt.Sprintf("%s/%s#%s (comment)", m[1], m[2], m[3]) },
+ },
+ {
+ regexp.MustCompile(`^https?://github\.com/([A-z0-9_-]+)/([A-z0-9_-]+)/pulls?/([0-9]+)#pullrequestreview-[0-9]+$`),
+ func(m []string) string { return fmt.Sprintf("%s/%s#%s (review)", m[1], m[2], m[3]) },
+ },
+ {
+ regexp.MustCompile(`^https?://github\.com/([A-z0-9_-]+)/([A-z0-9_-]+)/discussions/([0-9]+)#discussioncomment-[0-9]+$`),
+ func(m []string) string { return fmt.Sprintf("%s/%s#%s (comment)", m[1], m[2], m[3]) },
+ },
+ {
+ regexp.MustCompile(`^https?://github\.com/([A-z0-9_-]+)/([A-z0-9_-]+)/commit/([A-z0-9]{7,})(#.*)?$`),
+ func(m []string) string { return fmt.Sprintf("%s/%s@%s", m[1], m[2], m[3][:7]) },
+ },
+ {
+ regexp.MustCompile(`^https?://github\.com/([A-z0-9_-]+)/([A-z0-9_-]+)/pulls?/[0-9]+/commits/([A-z0-9]{7,})(#.*)?$`),
+ func(m []string) string { return fmt.Sprintf("%s/%s@%s", m[1], m[2], m[3][:7]) },
+ },
+}
+
+// Detect checks if the given URL matches any of the known patterns and
+// returns a human-readable formatted string if a match is found.
+func Detect(u string) (string, bool) {
+ for _, p := range patterns {
+ if m := p.pattern.FindStringSubmatch(u); len(m) > 0 {
+ return p.yield(m), true
+ }
+ }
+ return "", false
+}
diff --git a/vendor/github.com/charmbracelet/glamour/styles/README.md b/vendor/github.com/charmbracelet/glamour/styles/README.md
new file mode 100644
index 00000000..350d4305
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/styles/README.md
@@ -0,0 +1,565 @@
+# Glamour Style Guide
+
+The JSON files in this directory are generated from the default styles. To
+re-generate them, run:
+
+ go generate ..
+
+## Block Elements
+
+Block elements contain other elements and are rendered around them. All block
+elements support the following style settings:
+
+| Attribute | Value | Description |
+| ---------------- | ------ | ------------------------------------------------------------ |
+| block_prefix | string | Printed before the block's first element (in parent's style) |
+| block_suffix | string | Printed after the block's last element (in parent's style) |
+| prefix | string | Printed before the block's first element |
+| suffix | string | Printed after the block's last element |
+| indent | number | Specifies the indentation of the block |
+| indent_token | string | Specifies the indentation format |
+| margin | number | Specifies the margin around the block |
+| color | color | Defines the default text color for the block |
+| background_color | color | Defines the default background color for the block |
+
+Elements inside a block inherit the block's following style settings:
+
+| Attribute | Value | Description |
+| ---------------- | ----- | -------------------------------------------------- |
+| color | color | Defines the default text color for the block |
+| background_color | color | Defines the default background color for the block |
+| bold | bool | Increases text intensity |
+| faint | bool | Decreases text intensity |
+| italic | bool | Prints the text in italic |
+| crossed_out | bool | Enables strikethrough as text decoration |
+| underline | bool | Enables underline as text decoration |
+| overlined | bool | Enables overline as text decoration |
+| blink | bool | Enables blinking text |
+| conceal | bool | Conceals / hides the text |
+| inverse | bool | Swaps fore- & background colors |
+
+### document
+
+The `document` element represents the markdown's body.
+
+#### Example
+
+Style:
+
+```json
+"document": {
+ "indent": 2,
+ "background_color": "234",
+ "block_prefix": "\n",
+ "block_suffix": "\n"
+}
+```
+
+---
+
+### paragraph
+
+The `paragraph` element represents a paragraph in the document.
+
+#### Example
+
+Style:
+
+```json
+"paragraph": {
+ "margin": 4,
+ "color": "15",
+ "background_color": "235"
+}
+```
+
+---
+
+### heading
+
+The `heading` element represents a heading.
+
+### h1 - h6
+
+The `h1` to `h6` elements represent headings. `h1` defines the most important
+heading, `h6` the least important heading. Undefined attributes are inherited
+from the `heading` element.
+
+#### Example
+
+Markdown:
+
+```markdown
+# h1
+
+## h2
+
+### h3
+```
+
+Style:
+
+```json
+"heading": {
+ "color": "15",
+ "background_color": "57"
+},
+"h1": {
+ "prefix": "=> ",
+ "suffix": " <=",
+ "margin": 2,
+ "bold": true,
+ "background_color": "69"
+},
+"h2": {
+ "prefix": "## ",
+ "margin": 4
+},
+"h3": {
+ "prefix": "### ",
+ "margin": 6
+}
+```
+
+Output:
+
+
+
+---
+
+### block_quote
+
+The `block_quote` element represents a quote.
+
+#### Example
+
+Style:
+
+```json
+"block_quote": {
+ "color": "200",
+ "indent": 1,
+ "indent_token": "=> "
+}
+```
+
+Output:
+
+
+
+---
+
+### list
+
+The `list` element represents a list in the document.
+
+| Attribute | Value | Description |
+| ------------ | ------ | ------------------------------------------ |
+| level_indent | number | Specifies the indentation for nested lists |
+
+#### Example
+
+Style:
+
+```json
+"list": {
+ "color": "15",
+ "background_color": "52",
+ "level_indent": 4
+}
+```
+
+---
+
+### code_block
+
+The `code_block` element represents a block of code.
+
+| Attribute | Value | Description |
+| --------- | ------ | --------------------------------------------------------------- |
+| theme | string | Defines the [Chroma][chroma] theme used for syntax highlighting |
+
+[chroma]: https://github.com/alecthomas/chroma
+
+#### Example
+
+Style:
+
+```json
+"code_block": {
+ "color": "200",
+ "theme": "solarized-dark"
+}
+```
+
+Output:
+
+
+
+---
+
+### table
+
+The `table` element represents a table of data.
+
+#### Example
+
+Markdown:
+
+```markdown
+| Label | Value |
+| ------ | ----- |
+| First | foo |
+| Second | bar |
+```
+
+Style:
+
+```json
+"table": {
+ "margin": 4
+}
+```
+
+Output:
+
+
+
+## Inline Elements
+
+All inline elements support the following style settings:
+
+| Attribute | Value | Description |
+| ---------------- | ------ | ----------------------------------------------------- |
+| block_prefix | string | Printed before the element (in parent's style) |
+| block_suffix | string | Printed after the element (in parent's style) |
+| prefix | string | Printed before the element |
+| suffix | string | Printed after the element |
+| color | color | Defines the default text color for the document |
+| background_color | color | Defines the default background color for the document |
+| bold | bool | Increases text intensity |
+| faint | bool | Decreases text intensity |
+| italic | bool | Prints the text in italic |
+| crossed_out | bool | Enables strikethrough as text decoration |
+| underline | bool | Enables underline as text decoration |
+| overlined | bool | Enables overline as text decoration |
+| blink | bool | Enables blinking text |
+| conceal | bool | Conceals / hides the text |
+| inverse | bool | Swaps fore- & background colors |
+
+### text
+
+The `text` element represents a block of text.
+
+#### Example
+
+Style:
+
+```json
+"text": {
+ "bold": true,
+ "color": "15",
+ "background_color": "57"
+}
+```
+
+---
+
+### item
+
+The `item` element represents an item in a list.
+
+#### Example
+
+Markdown:
+
+```markdown
+- First Item
+ - Nested List Item
+- Second Item
+```
+
+Style:
+
+```json
+"item": {
+ "block_prefix": "• "
+}
+```
+
+Output:
+
+
+
+---
+
+### enumeration
+
+The `enumeration` element represents an item in an ordered list.
+
+#### Example
+
+Markdown:
+
+```markdown
+1. First Item
+2. Second Item
+```
+
+Style:
+
+```json
+"enumeration": {
+ "block_prefix": ". "
+}
+```
+
+Output:
+
+
+
+---
+
+### task
+
+The `task` element represents a task item.
+
+| Attribute | Value | Description |
+| --------- | ------ | --------------------------- |
+| ticked | string | Prefix for finished tasks |
+| unticked | string | Prefix for unfinished tasks |
+
+#### Example
+
+Markdown:
+
+```markdown
+- [x] Finished Task
+- [ ] Outstanding Task
+```
+
+Style:
+
+```json
+"task": {
+ "ticked": "✓ ",
+ "unticked": "✗ "
+}
+```
+
+Output:
+
+
+
+---
+
+### link
+
+The `link` element represents a link.
+
+#### Example
+
+Markdown:
+
+```markdown
+This is a [link](https://charm.sh).
+```
+
+Style:
+
+```json
+"link": {
+ "color": "123",
+ "underline": true,
+ "block_prefix": "(",
+ "block_suffix": ")"
+}
+```
+
+Output:
+
+
+
+---
+
+### link_text
+
+The `link_text` element represents the text associated with a link.
+
+#### Example
+
+Style:
+
+```json
+"link_text": {
+ "color": "123",
+ "bold": true
+}
+```
+
+---
+
+### image
+
+The `image` element represents an image.
+
+#### Example
+
+Markdown:
+
+```markdown
+.
+```
+
+Style:
+
+```json
+"image": {
+ "color": "123",
+ "block_prefix": "[Image: ",
+ "block_suffix": "]"
+}
+```
+
+Output:
+
+
+
+---
+
+### image_text
+
+The `image_text` element represents the text associated with an image.
+
+#### Example
+
+Style:
+
+```json
+"image_text": {
+ "color": "8"
+}
+```
+
+---
+
+### code
+
+The `code` element represents an inline code segment.
+
+#### Example
+
+Style:
+
+```json
+"code": {
+ "color": "200"
+}
+```
+
+Output:
+
+
+
+---
+
+### emph
+
+The `emph` element represents an emphasized text.
+
+#### Example
+
+Markdown:
+
+```markdown
+This text is *emphasized*.
+```
+
+Style:
+
+```json
+"emph": {
+ "italic": true
+}
+```
+
+Output:
+
+
+
+---
+
+### strong
+
+The `strong` element represents important text.
+
+#### Example
+
+Markdown:
+
+```markdown
+This text is **strong**.
+```
+
+Style:
+
+```json
+"strong": {
+ "bold": true
+}
+```
+
+Output:
+
+
+
+---
+
+### strikethrough
+
+The `strikethrough` element represents strikethrough text.
+
+#### Example
+
+Markdown:
+
+```markdown
+~~Scratch this~~.
+```
+
+Style:
+
+```json
+"strikethrough": {
+ "crossed_out": true
+}
+```
+
+Output:
+
+
+
+---
+
+### hr
+
+The `hr` element represents a horizontal rule.
+
+#### Example
+
+Markdown:
+
+```markdown
+---
+```
+
+Style:
+
+```json
+"hr": {
+ "block_prefix": "---"
+}
+```
+
+## html_block
+## html_span
diff --git a/vendor/github.com/charmbracelet/glamour/styles/ascii.json b/vendor/github.com/charmbracelet/glamour/styles/ascii.json
new file mode 100644
index 00000000..be13b917
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/styles/ascii.json
@@ -0,0 +1,83 @@
+{
+ "document": {
+ "block_prefix": "\n",
+ "block_suffix": "\n",
+ "margin": 2
+ },
+ "block_quote": {
+ "indent": 1,
+ "indent_token": "| "
+ },
+ "paragraph": {},
+ "list": {
+ "level_indent": 4
+ },
+ "heading": {
+ "block_suffix": "\n"
+ },
+ "h1": {
+ "prefix": "# "
+ },
+ "h2": {
+ "prefix": "## "
+ },
+ "h3": {
+ "prefix": "### "
+ },
+ "h4": {
+ "prefix": "#### "
+ },
+ "h5": {
+ "prefix": "##### "
+ },
+ "h6": {
+ "prefix": "###### "
+ },
+ "text": {},
+ "strikethrough": {
+ "block_prefix": "~~",
+ "block_suffix": "~~"
+ },
+ "emph": {
+ "block_prefix": "*",
+ "block_suffix": "*"
+ },
+ "strong": {
+ "block_prefix": "**",
+ "block_suffix": "**"
+ },
+ "hr": {
+ "format": "\n--------\n"
+ },
+ "item": {
+ "block_prefix": "• "
+ },
+ "enumeration": {
+ "block_prefix": ". "
+ },
+ "task": {
+ "ticked": "[x] ",
+ "unticked": "[ ] "
+ },
+ "link": {},
+ "link_text": {},
+ "image": {},
+ "image_text": {
+ "format": "Image: {{.text}} →"
+ },
+ "code": {
+ "block_prefix": "`",
+ "block_suffix": "`"
+ },
+ "code_block": {
+ "margin": 2
+ },
+ "table": {},
+ "definition_list": {},
+ "definition_term": {},
+ "definition_description": {
+ "block_prefix": "\n* "
+ },
+ "html_block": {},
+ "html_span": {}
+}
diff --git a/vendor/github.com/charmbracelet/glamour/styles/dark.json b/vendor/github.com/charmbracelet/glamour/styles/dark.json
new file mode 100644
index 00000000..47bf0afc
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/styles/dark.json
@@ -0,0 +1,191 @@
+{
+ "document": {
+ "block_prefix": "\n",
+ "block_suffix": "\n",
+ "color": "252",
+ "margin": 2
+ },
+ "block_quote": {
+ "indent": 1,
+ "indent_token": "│ "
+ },
+ "paragraph": {},
+ "list": {
+ "level_indent": 2
+ },
+ "heading": {
+ "block_suffix": "\n",
+ "color": "39",
+ "bold": true
+ },
+ "h1": {
+ "prefix": " ",
+ "suffix": " ",
+ "color": "228",
+ "background_color": "63",
+ "bold": true
+ },
+ "h2": {
+ "prefix": "## "
+ },
+ "h3": {
+ "prefix": "### "
+ },
+ "h4": {
+ "prefix": "#### "
+ },
+ "h5": {
+ "prefix": "##### "
+ },
+ "h6": {
+ "prefix": "###### ",
+ "color": "35",
+ "bold": false
+ },
+ "text": {},
+ "strikethrough": {
+ "crossed_out": true
+ },
+ "emph": {
+ "italic": true
+ },
+ "strong": {
+ "bold": true
+ },
+ "hr": {
+ "color": "240",
+ "format": "\n--------\n"
+ },
+ "item": {
+ "block_prefix": "• "
+ },
+ "enumeration": {
+ "block_prefix": ". "
+ },
+ "task": {
+ "ticked": "[✓] ",
+ "unticked": "[ ] "
+ },
+ "link": {
+ "color": "30",
+ "underline": true
+ },
+ "link_text": {
+ "color": "35",
+ "bold": true
+ },
+ "image": {
+ "color": "212",
+ "underline": true
+ },
+ "image_text": {
+ "color": "243",
+ "format": "Image: {{.text}} →"
+ },
+ "code": {
+ "prefix": " ",
+ "suffix": " ",
+ "color": "203",
+ "background_color": "236"
+ },
+ "code_block": {
+ "color": "244",
+ "margin": 2,
+ "chroma": {
+ "text": {
+ "color": "#C4C4C4"
+ },
+ "error": {
+ "color": "#F1F1F1",
+ "background_color": "#F05B5B"
+ },
+ "comment": {
+ "color": "#676767"
+ },
+ "comment_preproc": {
+ "color": "#FF875F"
+ },
+ "keyword": {
+ "color": "#00AAFF"
+ },
+ "keyword_reserved": {
+ "color": "#FF5FD2"
+ },
+ "keyword_namespace": {
+ "color": "#FF5F87"
+ },
+ "keyword_type": {
+ "color": "#6E6ED8"
+ },
+ "operator": {
+ "color": "#EF8080"
+ },
+ "punctuation": {
+ "color": "#E8E8A8"
+ },
+ "name": {
+ "color": "#C4C4C4"
+ },
+ "name_builtin": {
+ "color": "#FF8EC7"
+ },
+ "name_tag": {
+ "color": "#B083EA"
+ },
+ "name_attribute": {
+ "color": "#7A7AE6"
+ },
+ "name_class": {
+ "color": "#F1F1F1",
+ "underline": true,
+ "bold": true
+ },
+ "name_constant": {},
+ "name_decorator": {
+ "color": "#FFFF87"
+ },
+ "name_exception": {},
+ "name_function": {
+ "color": "#00D787"
+ },
+ "name_other": {},
+ "literal": {},
+ "literal_number": {
+ "color": "#6EEFC0"
+ },
+ "literal_date": {},
+ "literal_string": {
+ "color": "#C69669"
+ },
+ "literal_string_escape": {
+ "color": "#AFFFD7"
+ },
+ "generic_deleted": {
+ "color": "#FD5B5B"
+ },
+ "generic_emph": {
+ "italic": true
+ },
+ "generic_inserted": {
+ "color": "#00D787"
+ },
+ "generic_strong": {
+ "bold": true
+ },
+ "generic_subheading": {
+ "color": "#777777"
+ },
+ "background": {
+ "background_color": "#373737"
+ }
+ }
+ },
+ "table": {},
+ "definition_list": {},
+ "definition_term": {},
+ "definition_description": {
+ "block_prefix": "\n🠶 "
+ },
+ "html_block": {},
+ "html_span": {}
+}
diff --git a/vendor/github.com/charmbracelet/glamour/styles/dracula.go b/vendor/github.com/charmbracelet/glamour/styles/dracula.go
new file mode 100644
index 00000000..12eaf9d4
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/styles/dracula.go
@@ -0,0 +1,216 @@
+package styles
+
+import "github.com/charmbracelet/glamour/ansi"
+
+// DraculaStyleConfig is the dracula style.
+var DraculaStyleConfig = ansi.StyleConfig{
+ Document: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockPrefix: "\n",
+ BlockSuffix: "\n",
+ Color: stringPtr("#f8f8f2"),
+ },
+ Margin: uintPtr(defaultMargin),
+ },
+ BlockQuote: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Color: stringPtr("#f1fa8c"),
+ Italic: boolPtr(true),
+ },
+ Indent: uintPtr(defaultMargin),
+ },
+ List: ansi.StyleList{
+ LevelIndent: defaultMargin,
+ StyleBlock: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Color: stringPtr("#f8f8f2"),
+ },
+ },
+ },
+ Heading: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockSuffix: "\n",
+ Color: stringPtr("#bd93f9"),
+ Bold: boolPtr(true),
+ },
+ },
+ H1: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "# ",
+ },
+ },
+ H2: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "## ",
+ },
+ },
+ H3: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "### ",
+ },
+ },
+ H4: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "#### ",
+ },
+ },
+ H5: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "##### ",
+ },
+ },
+ H6: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "###### ",
+ },
+ },
+ Strikethrough: ansi.StylePrimitive{
+ CrossedOut: boolPtr(true),
+ },
+ Emph: ansi.StylePrimitive{
+ Color: stringPtr("#f1fa8c"),
+ Italic: boolPtr(true),
+ },
+ Strong: ansi.StylePrimitive{
+ Bold: boolPtr(true),
+ Color: stringPtr("#ffb86c"),
+ },
+ HorizontalRule: ansi.StylePrimitive{
+ Color: stringPtr("#6272A4"),
+ Format: "\n--------\n",
+ },
+ Item: ansi.StylePrimitive{
+ BlockPrefix: "• ",
+ },
+ Enumeration: ansi.StylePrimitive{
+ BlockPrefix: ". ",
+ Color: stringPtr("#8be9fd"),
+ },
+ Task: ansi.StyleTask{
+ StylePrimitive: ansi.StylePrimitive{},
+ Ticked: "[✓] ",
+ Unticked: "[ ] ",
+ },
+ Link: ansi.StylePrimitive{
+ Color: stringPtr("#8be9fd"),
+ Underline: boolPtr(true),
+ },
+ LinkText: ansi.StylePrimitive{
+ Color: stringPtr("#ff79c6"),
+ },
+ Image: ansi.StylePrimitive{
+ Color: stringPtr("#8be9fd"),
+ Underline: boolPtr(true),
+ },
+ ImageText: ansi.StylePrimitive{
+ Color: stringPtr("#ff79c6"),
+ Format: "Image: {{.text}} →",
+ },
+ Code: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Color: stringPtr("#50fa7b"),
+ },
+ },
+ CodeBlock: ansi.StyleCodeBlock{
+ StyleBlock: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Color: stringPtr("#ffb86c"),
+ },
+ Margin: uintPtr(defaultMargin),
+ },
+ Chroma: &ansi.Chroma{
+ Text: ansi.StylePrimitive{
+ Color: stringPtr("#f8f8f2"),
+ },
+ Error: ansi.StylePrimitive{
+ Color: stringPtr("#f8f8f2"),
+ BackgroundColor: stringPtr("#ff5555"),
+ },
+ Comment: ansi.StylePrimitive{
+ Color: stringPtr("#6272A4"),
+ },
+ CommentPreproc: ansi.StylePrimitive{
+ Color: stringPtr("#ff79c6"),
+ },
+ Keyword: ansi.StylePrimitive{
+ Color: stringPtr("#ff79c6"),
+ },
+ KeywordReserved: ansi.StylePrimitive{
+ Color: stringPtr("#ff79c6"),
+ },
+ KeywordNamespace: ansi.StylePrimitive{
+ Color: stringPtr("#ff79c6"),
+ },
+ KeywordType: ansi.StylePrimitive{
+ Color: stringPtr("#8be9fd"),
+ },
+ Operator: ansi.StylePrimitive{
+ Color: stringPtr("#ff79c6"),
+ },
+ Punctuation: ansi.StylePrimitive{
+ Color: stringPtr("#f8f8f2"),
+ },
+ Name: ansi.StylePrimitive{
+ Color: stringPtr("#8be9fd"),
+ },
+ NameBuiltin: ansi.StylePrimitive{
+ Color: stringPtr("#8be9fd"),
+ },
+ NameTag: ansi.StylePrimitive{
+ Color: stringPtr("#ff79c6"),
+ },
+ NameAttribute: ansi.StylePrimitive{
+ Color: stringPtr("#50fa7b"),
+ },
+ NameClass: ansi.StylePrimitive{
+ Color: stringPtr("#8be9fd"),
+ },
+ NameConstant: ansi.StylePrimitive{
+ Color: stringPtr("#bd93f9"),
+ },
+ NameDecorator: ansi.StylePrimitive{
+ Color: stringPtr("#50fa7b"),
+ },
+ NameFunction: ansi.StylePrimitive{
+ Color: stringPtr("#50fa7b"),
+ },
+ LiteralNumber: ansi.StylePrimitive{
+ Color: stringPtr("#6EEFC0"),
+ },
+ LiteralString: ansi.StylePrimitive{
+ Color: stringPtr("#f1fa8c"),
+ },
+ LiteralStringEscape: ansi.StylePrimitive{
+ Color: stringPtr("#ff79c6"),
+ },
+ GenericDeleted: ansi.StylePrimitive{
+ Color: stringPtr("#ff5555"),
+ },
+ GenericEmph: ansi.StylePrimitive{
+ Color: stringPtr("#f1fa8c"),
+ Italic: boolPtr(true),
+ },
+ GenericInserted: ansi.StylePrimitive{
+ Color: stringPtr("#50fa7b"),
+ },
+ GenericStrong: ansi.StylePrimitive{
+ Color: stringPtr("#ffb86c"),
+ Bold: boolPtr(true),
+ },
+ GenericSubheading: ansi.StylePrimitive{
+ Color: stringPtr("#bd93f9"),
+ },
+ Background: ansi.StylePrimitive{
+ BackgroundColor: stringPtr("#282a36"),
+ },
+ },
+ },
+ Table: ansi.StyleTable{
+ StyleBlock: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{},
+ },
+ },
+ DefinitionDescription: ansi.StylePrimitive{
+ BlockPrefix: "\n🠶 ",
+ },
+}
diff --git a/vendor/github.com/charmbracelet/glamour/styles/dracula.json b/vendor/github.com/charmbracelet/glamour/styles/dracula.json
new file mode 100644
index 00000000..4bbcd497
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/styles/dracula.json
@@ -0,0 +1,188 @@
+{
+ "document": {
+ "block_prefix": "\n",
+ "block_suffix": "\n",
+ "color": "#f8f8f2",
+ "margin": 2
+ },
+ "block_quote": {
+ "color": "#f1fa8c",
+ "italic": true,
+ "indent": 2
+ },
+ "paragraph": {},
+ "list": {
+ "color": "#f8f8f2",
+ "level_indent": 2
+ },
+ "heading": {
+ "block_suffix": "\n",
+ "color": "#bd93f9",
+ "bold": true
+ },
+ "h1": {
+ "prefix": "# "
+ },
+ "h2": {
+ "prefix": "## "
+ },
+ "h3": {
+ "prefix": "### "
+ },
+ "h4": {
+ "prefix": "#### "
+ },
+ "h5": {
+ "prefix": "##### "
+ },
+ "h6": {
+ "prefix": "###### "
+ },
+ "text": {},
+ "strikethrough": {
+ "crossed_out": true
+ },
+ "emph": {
+ "color": "#f1fa8c",
+ "italic": true
+ },
+ "strong": {
+ "color": "#ffb86c",
+ "bold": true
+ },
+ "hr": {
+ "color": "#6272A4",
+ "format": "\n--------\n"
+ },
+ "item": {
+ "block_prefix": "• "
+ },
+ "enumeration": {
+ "block_prefix": ". ",
+ "color": "#8be9fd"
+ },
+ "task": {
+ "ticked": "[✓] ",
+ "unticked": "[ ] "
+ },
+ "link": {
+ "color": "#8be9fd",
+ "underline": true
+ },
+ "link_text": {
+ "color": "#ff79c6"
+ },
+ "image": {
+ "color": "#8be9fd",
+ "underline": true
+ },
+ "image_text": {
+ "color": "#ff79c6",
+ "format": "Image: {{.text}} →"
+ },
+ "code": {
+ "color": "#50fa7b"
+ },
+ "code_block": {
+ "color": "#ffb86c",
+ "margin": 2,
+ "chroma": {
+ "text": {
+ "color": "#f8f8f2"
+ },
+ "error": {
+ "color": "#f8f8f2",
+ "background_color": "#ff5555"
+ },
+ "comment": {
+ "color": "#6272A4"
+ },
+ "comment_preproc": {
+ "color": "#ff79c6"
+ },
+ "keyword": {
+ "color": "#ff79c6"
+ },
+ "keyword_reserved": {
+ "color": "#ff79c6"
+ },
+ "keyword_namespace": {
+ "color": "#ff79c6"
+ },
+ "keyword_type": {
+ "color": "#8be9fd"
+ },
+ "operator": {
+ "color": "#ff79c6"
+ },
+ "punctuation": {
+ "color": "#f8f8f2"
+ },
+ "name": {
+ "color": "#8be9fd"
+ },
+ "name_builtin": {
+ "color": "#8be9fd"
+ },
+ "name_tag": {
+ "color": "#ff79c6"
+ },
+ "name_attribute": {
+ "color": "#50fa7b"
+ },
+ "name_class": {
+ "color": "#8be9fd"
+ },
+ "name_constant": {
+ "color": "#bd93f9"
+ },
+ "name_decorator": {
+ "color": "#50fa7b"
+ },
+ "name_exception": {},
+ "name_function": {
+ "color": "#50fa7b"
+ },
+ "name_other": {},
+ "literal": {},
+ "literal_number": {
+ "color": "#6EEFC0"
+ },
+ "literal_date": {},
+ "literal_string": {
+ "color": "#f1fa8c"
+ },
+ "literal_string_escape": {
+ "color": "#ff79c6"
+ },
+ "generic_deleted": {
+ "color": "#ff5555"
+ },
+ "generic_emph": {
+ "color": "#f1fa8c",
+ "italic": true
+ },
+ "generic_inserted": {
+ "color": "#50fa7b"
+ },
+ "generic_strong": {
+ "color": "#ffb86c",
+ "bold": true
+ },
+ "generic_subheading": {
+ "color": "#bd93f9"
+ },
+ "background": {
+ "background_color": "#282a36"
+ }
+ }
+ },
+ "table": {},
+ "definition_list": {},
+ "definition_term": {},
+ "definition_description": {
+ "block_prefix": "\n🠶 "
+ },
+ "html_block": {},
+ "html_span": {}
+}
diff --git a/vendor/github.com/charmbracelet/glamour/styles/light.json b/vendor/github.com/charmbracelet/glamour/styles/light.json
new file mode 100644
index 00000000..08533592
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/styles/light.json
@@ -0,0 +1,190 @@
+{
+ "document": {
+ "block_prefix": "\n",
+ "block_suffix": "\n",
+ "color": "234",
+ "margin": 2
+ },
+ "block_quote": {
+ "indent": 1,
+ "indent_token": "│ "
+ },
+ "paragraph": {},
+ "list": {
+ "level_indent": 2
+ },
+ "heading": {
+ "block_suffix": "\n",
+ "color": "27",
+ "bold": true
+ },
+ "h1": {
+ "prefix": " ",
+ "suffix": " ",
+ "color": "228",
+ "background_color": "63",
+ "bold": true
+ },
+ "h2": {
+ "prefix": "## "
+ },
+ "h3": {
+ "prefix": "### "
+ },
+ "h4": {
+ "prefix": "#### "
+ },
+ "h5": {
+ "prefix": "##### "
+ },
+ "h6": {
+ "prefix": "###### ",
+ "bold": false
+ },
+ "text": {},
+ "strikethrough": {
+ "crossed_out": true
+ },
+ "emph": {
+ "italic": true
+ },
+ "strong": {
+ "bold": true
+ },
+ "hr": {
+ "color": "249",
+ "format": "\n--------\n"
+ },
+ "item": {
+ "block_prefix": "• "
+ },
+ "enumeration": {
+ "block_prefix": ". "
+ },
+ "task": {
+ "ticked": "[✓] ",
+ "unticked": "[ ] "
+ },
+ "link": {
+ "color": "36",
+ "underline": true
+ },
+ "link_text": {
+ "color": "29",
+ "bold": true
+ },
+ "image": {
+ "color": "205",
+ "underline": true
+ },
+ "image_text": {
+ "color": "243",
+ "format": "Image: {{.text}} →"
+ },
+ "code": {
+ "prefix": " ",
+ "suffix": " ",
+ "color": "203",
+ "background_color": "254"
+ },
+ "code_block": {
+ "color": "242",
+ "margin": 2,
+ "chroma": {
+ "text": {
+ "color": "#2A2A2A"
+ },
+ "error": {
+ "color": "#F1F1F1",
+ "background_color": "#FF5555"
+ },
+ "comment": {
+ "color": "#8D8D8D"
+ },
+ "comment_preproc": {
+ "color": "#FF875F"
+ },
+ "keyword": {
+ "color": "#279EFC"
+ },
+ "keyword_reserved": {
+ "color": "#FF5FD2"
+ },
+ "keyword_namespace": {
+ "color": "#FB406F"
+ },
+ "keyword_type": {
+ "color": "#7049C2"
+ },
+ "operator": {
+ "color": "#FF2626"
+ },
+ "punctuation": {
+ "color": "#FA7878"
+ },
+ "name": {},
+ "name_builtin": {
+ "color": "#0A1BB1"
+ },
+ "name_tag": {
+ "color": "#581290"
+ },
+ "name_attribute": {
+ "color": "#8362CB"
+ },
+ "name_class": {
+ "color": "#212121",
+ "underline": true,
+ "bold": true
+ },
+ "name_constant": {
+ "color": "#581290"
+ },
+ "name_decorator": {
+ "color": "#A3A322"
+ },
+ "name_exception": {},
+ "name_function": {
+ "color": "#019F57"
+ },
+ "name_other": {},
+ "literal": {},
+ "literal_number": {
+ "color": "#22CCAE"
+ },
+ "literal_date": {},
+ "literal_string": {
+ "color": "#7E5B38"
+ },
+ "literal_string_escape": {
+ "color": "#00AEAE"
+ },
+ "generic_deleted": {
+ "color": "#FD5B5B"
+ },
+ "generic_emph": {
+ "italic": true
+ },
+ "generic_inserted": {
+ "color": "#00D787"
+ },
+ "generic_strong": {
+ "bold": true
+ },
+ "generic_subheading": {
+ "color": "#777777"
+ },
+ "background": {
+ "background_color": "#373737"
+ }
+ }
+ },
+ "table": {},
+ "definition_list": {},
+ "definition_term": {},
+ "definition_description": {
+ "block_prefix": "\n🠶 "
+ },
+ "html_block": {},
+ "html_span": {}
+}
diff --git a/vendor/github.com/charmbracelet/glamour/styles/notty.json b/vendor/github.com/charmbracelet/glamour/styles/notty.json
new file mode 100644
index 00000000..be13b917
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/styles/notty.json
@@ -0,0 +1,83 @@
+{
+ "document": {
+ "block_prefix": "\n",
+ "block_suffix": "\n",
+ "margin": 2
+ },
+ "block_quote": {
+ "indent": 1,
+ "indent_token": "| "
+ },
+ "paragraph": {},
+ "list": {
+ "level_indent": 4
+ },
+ "heading": {
+ "block_suffix": "\n"
+ },
+ "h1": {
+ "prefix": "# "
+ },
+ "h2": {
+ "prefix": "## "
+ },
+ "h3": {
+ "prefix": "### "
+ },
+ "h4": {
+ "prefix": "#### "
+ },
+ "h5": {
+ "prefix": "##### "
+ },
+ "h6": {
+ "prefix": "###### "
+ },
+ "text": {},
+ "strikethrough": {
+ "block_prefix": "~~",
+ "block_suffix": "~~"
+ },
+ "emph": {
+ "block_prefix": "*",
+ "block_suffix": "*"
+ },
+ "strong": {
+ "block_prefix": "**",
+ "block_suffix": "**"
+ },
+ "hr": {
+ "format": "\n--------\n"
+ },
+ "item": {
+ "block_prefix": "• "
+ },
+ "enumeration": {
+ "block_prefix": ". "
+ },
+ "task": {
+ "ticked": "[x] ",
+ "unticked": "[ ] "
+ },
+ "link": {},
+ "link_text": {},
+ "image": {},
+ "image_text": {
+ "format": "Image: {{.text}} →"
+ },
+ "code": {
+ "block_prefix": "`",
+ "block_suffix": "`"
+ },
+ "code_block": {
+ "margin": 2
+ },
+ "table": {},
+ "definition_list": {},
+ "definition_term": {},
+ "definition_description": {
+ "block_prefix": "\n* "
+ },
+ "html_block": {},
+ "html_span": {}
+}
diff --git a/vendor/github.com/charmbracelet/glamour/styles/pink.json b/vendor/github.com/charmbracelet/glamour/styles/pink.json
new file mode 100644
index 00000000..39a7f9ec
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/styles/pink.json
@@ -0,0 +1,90 @@
+{
+ "document": {
+ "margin": 2
+ },
+ "block_quote": {
+ "indent": 1,
+ "indent_token": "│ "
+ },
+ "paragraph": {},
+ "list": {
+ "level_indent": 2
+ },
+ "heading": {
+ "block_suffix": "\n",
+ "color": "212",
+ "bold": true
+ },
+ "h1": {
+ "block_prefix": "\n",
+ "block_suffix": "\n"
+ },
+ "h2": {
+ "prefix": "▌ "
+ },
+ "h3": {
+ "prefix": "┃ "
+ },
+ "h4": {
+ "prefix": "│ "
+ },
+ "h5": {
+ "prefix": "┆ "
+ },
+ "h6": {
+ "prefix": "┊ ",
+ "bold": false
+ },
+ "text": {},
+ "strikethrough": {
+ "crossed_out": true
+ },
+ "emph": {
+ "italic": true
+ },
+ "strong": {
+ "bold": true
+ },
+ "hr": {
+ "color": "212",
+ "format": "\n──────\n"
+ },
+ "item": {
+ "block_prefix": "• "
+ },
+ "enumeration": {
+ "block_prefix": ". "
+ },
+ "task": {
+ "ticked": "[✓] ",
+ "unticked": "[ ] "
+ },
+ "link": {
+ "color": "99",
+ "underline": true
+ },
+ "link_text": {
+ "bold": true
+ },
+ "image": {
+ "underline": true
+ },
+ "image_text": {
+ "format": "Image: {{.text}}"
+ },
+ "code": {
+ "prefix": " ",
+ "suffix": " ",
+ "color": "212",
+ "background_color": "236"
+ },
+ "code_block": {},
+ "table": {},
+ "definition_list": {},
+ "definition_term": {},
+ "definition_description": {
+ "block_prefix": "\n🠶 "
+ },
+ "html_block": {},
+ "html_span": {}
+}
diff --git a/vendor/github.com/charmbracelet/glamour/styles/styles.go b/vendor/github.com/charmbracelet/glamour/styles/styles.go
new file mode 100644
index 00000000..72ee1a55
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/styles/styles.go
@@ -0,0 +1,681 @@
+// Package styles provides default styles for the glamour package.
+package styles
+
+//go:generate go run ../internal/generate-style-json
+
+import (
+ "github.com/charmbracelet/glamour/ansi"
+)
+
+const (
+ defaultListIndent = 2
+ defaultListLevelIndent = 4
+ defaultMargin = 2
+)
+
+// Default styles.
+const (
+ AsciiStyle = "ascii" //nolint: revive
+ AutoStyle = "auto"
+ DarkStyle = "dark"
+ DraculaStyle = "dracula"
+ TokyoNightStyle = "tokyo-night"
+ LightStyle = "light"
+ NoTTYStyle = "notty"
+ PinkStyle = "pink"
+)
+
+var (
+ // ASCIIStyleConfig uses only ASCII characters.
+ ASCIIStyleConfig = ansi.StyleConfig{
+ Document: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockPrefix: "\n",
+ BlockSuffix: "\n",
+ },
+ Margin: uintPtr(defaultMargin),
+ },
+ BlockQuote: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{},
+ Indent: uintPtr(1),
+ IndentToken: stringPtr("| "),
+ },
+ Paragraph: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{},
+ },
+ List: ansi.StyleList{
+ StyleBlock: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{},
+ },
+ LevelIndent: defaultListLevelIndent,
+ },
+ Heading: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockSuffix: "\n",
+ },
+ },
+ H1: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "# ",
+ },
+ },
+ H2: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "## ",
+ },
+ },
+ H3: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "### ",
+ },
+ },
+ H4: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "#### ",
+ },
+ },
+ H5: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "##### ",
+ },
+ },
+ H6: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "###### ",
+ },
+ },
+ Strikethrough: ansi.StylePrimitive{
+ BlockPrefix: "~~",
+ BlockSuffix: "~~",
+ },
+ Emph: ansi.StylePrimitive{
+ BlockPrefix: "*",
+ BlockSuffix: "*",
+ },
+ Strong: ansi.StylePrimitive{
+ BlockPrefix: "**",
+ BlockSuffix: "**",
+ },
+ HorizontalRule: ansi.StylePrimitive{
+ Format: "\n--------\n",
+ },
+ Item: ansi.StylePrimitive{
+ BlockPrefix: "• ",
+ },
+ Enumeration: ansi.StylePrimitive{
+ BlockPrefix: ". ",
+ },
+ Task: ansi.StyleTask{
+ Ticked: "[x] ",
+ Unticked: "[ ] ",
+ },
+ ImageText: ansi.StylePrimitive{
+ Format: "Image: {{.text}} →",
+ },
+ Code: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockPrefix: "`",
+ BlockSuffix: "`",
+ },
+ },
+ CodeBlock: ansi.StyleCodeBlock{
+ StyleBlock: ansi.StyleBlock{
+ Margin: uintPtr(defaultMargin),
+ },
+ },
+ Table: ansi.StyleTable{
+ CenterSeparator: stringPtr("|"),
+ ColumnSeparator: stringPtr("|"),
+ RowSeparator: stringPtr("-"),
+ },
+ DefinitionDescription: ansi.StylePrimitive{
+ BlockPrefix: "\n* ",
+ },
+ }
+
+ // DarkStyleConfig is the default dark style.
+ DarkStyleConfig = ansi.StyleConfig{
+ Document: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockPrefix: "\n",
+ BlockSuffix: "\n",
+ Color: stringPtr("252"),
+ },
+ Margin: uintPtr(defaultMargin),
+ },
+ BlockQuote: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{},
+ Indent: uintPtr(1),
+ IndentToken: stringPtr("│ "),
+ },
+ List: ansi.StyleList{
+ LevelIndent: defaultListIndent,
+ },
+ Heading: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockSuffix: "\n",
+ Color: stringPtr("39"),
+ Bold: boolPtr(true),
+ },
+ },
+ H1: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: " ",
+ Suffix: " ",
+ Color: stringPtr("228"),
+ BackgroundColor: stringPtr("63"),
+ Bold: boolPtr(true),
+ },
+ },
+ H2: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "## ",
+ },
+ },
+ H3: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "### ",
+ },
+ },
+ H4: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "#### ",
+ },
+ },
+ H5: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "##### ",
+ },
+ },
+ H6: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "###### ",
+ Color: stringPtr("35"),
+ Bold: boolPtr(false),
+ },
+ },
+ Strikethrough: ansi.StylePrimitive{
+ CrossedOut: boolPtr(true),
+ },
+ Emph: ansi.StylePrimitive{
+ Italic: boolPtr(true),
+ },
+ Strong: ansi.StylePrimitive{
+ Bold: boolPtr(true),
+ },
+ HorizontalRule: ansi.StylePrimitive{
+ Color: stringPtr("240"),
+ Format: "\n--------\n",
+ },
+ Item: ansi.StylePrimitive{
+ BlockPrefix: "• ",
+ },
+ Enumeration: ansi.StylePrimitive{
+ BlockPrefix: ". ",
+ },
+ Task: ansi.StyleTask{
+ StylePrimitive: ansi.StylePrimitive{},
+ Ticked: "[✓] ",
+ Unticked: "[ ] ",
+ },
+ Link: ansi.StylePrimitive{
+ Color: stringPtr("30"),
+ Underline: boolPtr(true),
+ },
+ LinkText: ansi.StylePrimitive{
+ Color: stringPtr("35"),
+ Bold: boolPtr(true),
+ },
+ Image: ansi.StylePrimitive{
+ Color: stringPtr("212"),
+ Underline: boolPtr(true),
+ },
+ ImageText: ansi.StylePrimitive{
+ Color: stringPtr("243"),
+ Format: "Image: {{.text}} →",
+ },
+ Code: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: " ",
+ Suffix: " ",
+ Color: stringPtr("203"),
+ BackgroundColor: stringPtr("236"),
+ },
+ },
+ CodeBlock: ansi.StyleCodeBlock{
+ StyleBlock: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Color: stringPtr("244"),
+ },
+ Margin: uintPtr(defaultMargin),
+ },
+ Chroma: &ansi.Chroma{
+ Text: ansi.StylePrimitive{
+ Color: stringPtr("#C4C4C4"),
+ },
+ Error: ansi.StylePrimitive{
+ Color: stringPtr("#F1F1F1"),
+ BackgroundColor: stringPtr("#F05B5B"),
+ },
+ Comment: ansi.StylePrimitive{
+ Color: stringPtr("#676767"),
+ },
+ CommentPreproc: ansi.StylePrimitive{
+ Color: stringPtr("#FF875F"),
+ },
+ Keyword: ansi.StylePrimitive{
+ Color: stringPtr("#00AAFF"),
+ },
+ KeywordReserved: ansi.StylePrimitive{
+ Color: stringPtr("#FF5FD2"),
+ },
+ KeywordNamespace: ansi.StylePrimitive{
+ Color: stringPtr("#FF5F87"),
+ },
+ KeywordType: ansi.StylePrimitive{
+ Color: stringPtr("#6E6ED8"),
+ },
+ Operator: ansi.StylePrimitive{
+ Color: stringPtr("#EF8080"),
+ },
+ Punctuation: ansi.StylePrimitive{
+ Color: stringPtr("#E8E8A8"),
+ },
+ Name: ansi.StylePrimitive{
+ Color: stringPtr("#C4C4C4"),
+ },
+ NameBuiltin: ansi.StylePrimitive{
+ Color: stringPtr("#FF8EC7"),
+ },
+ NameTag: ansi.StylePrimitive{
+ Color: stringPtr("#B083EA"),
+ },
+ NameAttribute: ansi.StylePrimitive{
+ Color: stringPtr("#7A7AE6"),
+ },
+ NameClass: ansi.StylePrimitive{
+ Color: stringPtr("#F1F1F1"),
+ Underline: boolPtr(true),
+ Bold: boolPtr(true),
+ },
+ NameDecorator: ansi.StylePrimitive{
+ Color: stringPtr("#FFFF87"),
+ },
+ NameFunction: ansi.StylePrimitive{
+ Color: stringPtr("#00D787"),
+ },
+ LiteralNumber: ansi.StylePrimitive{
+ Color: stringPtr("#6EEFC0"),
+ },
+ LiteralString: ansi.StylePrimitive{
+ Color: stringPtr("#C69669"),
+ },
+ LiteralStringEscape: ansi.StylePrimitive{
+ Color: stringPtr("#AFFFD7"),
+ },
+ GenericDeleted: ansi.StylePrimitive{
+ Color: stringPtr("#FD5B5B"),
+ },
+ GenericEmph: ansi.StylePrimitive{
+ Italic: boolPtr(true),
+ },
+ GenericInserted: ansi.StylePrimitive{
+ Color: stringPtr("#00D787"),
+ },
+ GenericStrong: ansi.StylePrimitive{
+ Bold: boolPtr(true),
+ },
+ GenericSubheading: ansi.StylePrimitive{
+ Color: stringPtr("#777777"),
+ },
+ Background: ansi.StylePrimitive{
+ BackgroundColor: stringPtr("#373737"),
+ },
+ },
+ },
+ Table: ansi.StyleTable{
+ StyleBlock: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{},
+ },
+ },
+ DefinitionDescription: ansi.StylePrimitive{
+ BlockPrefix: "\n🠶 ",
+ },
+ }
+
+ // LightStyleConfig is the default light style.
+ LightStyleConfig = ansi.StyleConfig{
+ Document: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockPrefix: "\n",
+ BlockSuffix: "\n",
+ Color: stringPtr("234"),
+ },
+ Margin: uintPtr(defaultMargin),
+ },
+ BlockQuote: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{},
+ Indent: uintPtr(1),
+ IndentToken: stringPtr("│ "),
+ },
+ List: ansi.StyleList{
+ LevelIndent: defaultListIndent,
+ },
+ Heading: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockSuffix: "\n",
+ Color: stringPtr("27"),
+ Bold: boolPtr(true),
+ },
+ },
+ H1: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: " ",
+ Suffix: " ",
+ Color: stringPtr("228"),
+ BackgroundColor: stringPtr("63"),
+ Bold: boolPtr(true),
+ },
+ },
+ H2: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "## ",
+ },
+ },
+ H3: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "### ",
+ },
+ },
+ H4: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "#### ",
+ },
+ },
+ H5: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "##### ",
+ },
+ },
+ H6: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "###### ",
+ Bold: boolPtr(false),
+ },
+ },
+ Strikethrough: ansi.StylePrimitive{
+ CrossedOut: boolPtr(true),
+ },
+ Emph: ansi.StylePrimitive{
+ Italic: boolPtr(true),
+ },
+ Strong: ansi.StylePrimitive{
+ Bold: boolPtr(true),
+ },
+ HorizontalRule: ansi.StylePrimitive{
+ Color: stringPtr("249"),
+ Format: "\n--------\n",
+ },
+ Item: ansi.StylePrimitive{
+ BlockPrefix: "• ",
+ },
+ Enumeration: ansi.StylePrimitive{
+ BlockPrefix: ". ",
+ },
+ Task: ansi.StyleTask{
+ StylePrimitive: ansi.StylePrimitive{},
+ Ticked: "[✓] ",
+ Unticked: "[ ] ",
+ },
+ Link: ansi.StylePrimitive{
+ Color: stringPtr("36"),
+ Underline: boolPtr(true),
+ },
+ LinkText: ansi.StylePrimitive{
+ Color: stringPtr("29"),
+ Bold: boolPtr(true),
+ },
+ Image: ansi.StylePrimitive{
+ Color: stringPtr("205"),
+ Underline: boolPtr(true),
+ },
+ ImageText: ansi.StylePrimitive{
+ Color: stringPtr("243"),
+ Format: "Image: {{.text}} →",
+ },
+ Code: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: " ",
+ Suffix: " ",
+ Color: stringPtr("203"),
+ BackgroundColor: stringPtr("254"),
+ },
+ },
+ CodeBlock: ansi.StyleCodeBlock{
+ StyleBlock: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Color: stringPtr("242"),
+ },
+ Margin: uintPtr(defaultMargin),
+ },
+ Chroma: &ansi.Chroma{
+ Text: ansi.StylePrimitive{
+ Color: stringPtr("#2A2A2A"),
+ },
+ Error: ansi.StylePrimitive{
+ Color: stringPtr("#F1F1F1"),
+ BackgroundColor: stringPtr("#FF5555"),
+ },
+ Comment: ansi.StylePrimitive{
+ Color: stringPtr("#8D8D8D"),
+ },
+ CommentPreproc: ansi.StylePrimitive{
+ Color: stringPtr("#FF875F"),
+ },
+ Keyword: ansi.StylePrimitive{
+ Color: stringPtr("#279EFC"),
+ },
+ KeywordReserved: ansi.StylePrimitive{
+ Color: stringPtr("#FF5FD2"),
+ },
+ KeywordNamespace: ansi.StylePrimitive{
+ Color: stringPtr("#FB406F"),
+ },
+ KeywordType: ansi.StylePrimitive{
+ Color: stringPtr("#7049C2"),
+ },
+ Operator: ansi.StylePrimitive{
+ Color: stringPtr("#FF2626"),
+ },
+ Punctuation: ansi.StylePrimitive{
+ Color: stringPtr("#FA7878"),
+ },
+ NameBuiltin: ansi.StylePrimitive{
+ Color: stringPtr("#0A1BB1"),
+ },
+ NameTag: ansi.StylePrimitive{
+ Color: stringPtr("#581290"),
+ },
+ NameAttribute: ansi.StylePrimitive{
+ Color: stringPtr("#8362CB"),
+ },
+ NameClass: ansi.StylePrimitive{
+ Color: stringPtr("#212121"),
+ Underline: boolPtr(true),
+ Bold: boolPtr(true),
+ },
+ NameConstant: ansi.StylePrimitive{
+ Color: stringPtr("#581290"),
+ },
+ NameDecorator: ansi.StylePrimitive{
+ Color: stringPtr("#A3A322"),
+ },
+ NameFunction: ansi.StylePrimitive{
+ Color: stringPtr("#019F57"),
+ },
+ LiteralNumber: ansi.StylePrimitive{
+ Color: stringPtr("#22CCAE"),
+ },
+ LiteralString: ansi.StylePrimitive{
+ Color: stringPtr("#7E5B38"),
+ },
+ LiteralStringEscape: ansi.StylePrimitive{
+ Color: stringPtr("#00AEAE"),
+ },
+ GenericDeleted: ansi.StylePrimitive{
+ Color: stringPtr("#FD5B5B"),
+ },
+ GenericEmph: ansi.StylePrimitive{
+ Italic: boolPtr(true),
+ },
+ GenericInserted: ansi.StylePrimitive{
+ Color: stringPtr("#00D787"),
+ },
+ GenericStrong: ansi.StylePrimitive{
+ Bold: boolPtr(true),
+ },
+ GenericSubheading: ansi.StylePrimitive{
+ Color: stringPtr("#777777"),
+ },
+ Background: ansi.StylePrimitive{
+ BackgroundColor: stringPtr("#373737"),
+ },
+ },
+ },
+ Table: ansi.StyleTable{
+ StyleBlock: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{},
+ },
+ },
+ DefinitionDescription: ansi.StylePrimitive{
+ BlockPrefix: "\n🠶 ",
+ },
+ }
+
+ // PinkStyleConfig is the default pink style.
+ PinkStyleConfig = ansi.StyleConfig{
+ Document: ansi.StyleBlock{
+ Margin: uintPtr(defaultMargin),
+ },
+ BlockQuote: ansi.StyleBlock{
+ Indent: uintPtr(1),
+ IndentToken: stringPtr("│ "),
+ },
+ List: ansi.StyleList{
+ LevelIndent: defaultListIndent,
+ },
+ Heading: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockSuffix: "\n",
+ Color: stringPtr("212"),
+ Bold: boolPtr(true),
+ },
+ },
+ H1: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockSuffix: "\n",
+ BlockPrefix: "\n",
+ Prefix: "",
+ },
+ },
+ H2: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "▌ ",
+ },
+ },
+ H3: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "┃ ",
+ },
+ },
+ H4: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "│ ",
+ },
+ },
+ H5: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "┆ ",
+ },
+ },
+ H6: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "┊ ",
+ Bold: boolPtr(false),
+ },
+ },
+ Text: ansi.StylePrimitive{},
+ Strikethrough: ansi.StylePrimitive{
+ CrossedOut: boolPtr(true),
+ },
+ Emph: ansi.StylePrimitive{
+ Italic: boolPtr(true),
+ },
+ Strong: ansi.StylePrimitive{
+ Bold: boolPtr(true),
+ },
+ HorizontalRule: ansi.StylePrimitive{
+ Color: stringPtr("212"),
+ Format: "\n──────\n",
+ },
+ Item: ansi.StylePrimitive{
+ BlockPrefix: "• ",
+ },
+ Enumeration: ansi.StylePrimitive{
+ BlockPrefix: ". ",
+ },
+ Task: ansi.StyleTask{
+ Ticked: "[✓] ",
+ Unticked: "[ ] ",
+ },
+ Link: ansi.StylePrimitive{
+ Color: stringPtr("99"),
+ Underline: boolPtr(true),
+ },
+ LinkText: ansi.StylePrimitive{
+ Bold: boolPtr(true),
+ },
+ Image: ansi.StylePrimitive{
+ Underline: boolPtr(true),
+ },
+ ImageText: ansi.StylePrimitive{
+ Format: "Image: {{.text}}",
+ },
+ Code: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Color: stringPtr("212"),
+ BackgroundColor: stringPtr("236"),
+ Prefix: " ",
+ Suffix: " ",
+ },
+ },
+ Table: ansi.StyleTable{},
+ DefinitionList: ansi.StyleBlock{},
+ DefinitionTerm: ansi.StylePrimitive{},
+ DefinitionDescription: ansi.StylePrimitive{
+ BlockPrefix: "\n🠶 ",
+ },
+ HTMLBlock: ansi.StyleBlock{},
+ HTMLSpan: ansi.StyleBlock{},
+ }
+
+ // NoTTYStyleConfig is the default notty style.
+ NoTTYStyleConfig = ASCIIStyleConfig
+
+ // DefaultStyles are the default styles.
+ DefaultStyles = map[string]*ansi.StyleConfig{
+ AsciiStyle: &ASCIIStyleConfig,
+ DarkStyle: &DarkStyleConfig,
+ LightStyle: &LightStyleConfig,
+ NoTTYStyle: &NoTTYStyleConfig,
+ PinkStyle: &PinkStyleConfig,
+
+ // Popular themes
+ DraculaStyle: &DraculaStyleConfig,
+ TokyoNightStyle: &TokyoNightStyleConfig,
+ }
+)
+
+func boolPtr(b bool) *bool { return &b }
+func stringPtr(s string) *string { return &s }
+func uintPtr(u uint) *uint { return &u }
diff --git a/vendor/github.com/charmbracelet/glamour/styles/tokyo-night.go b/vendor/github.com/charmbracelet/glamour/styles/tokyo-night.go
new file mode 100644
index 00000000..a7c1d793
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/styles/tokyo-night.go
@@ -0,0 +1,209 @@
+package styles
+
+import "github.com/charmbracelet/glamour/ansi"
+
+// TokyoNightStyleConfig is the tokyo night style.
+var TokyoNightStyleConfig = ansi.StyleConfig{
+ Document: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockPrefix: "\n",
+ BlockSuffix: "\n",
+ Color: stringPtr("#a9b1d6"),
+ },
+ Margin: uintPtr(defaultMargin),
+ },
+ BlockQuote: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{},
+ Indent: uintPtr(1),
+ IndentToken: stringPtr("│ "),
+ },
+ List: ansi.StyleList{
+ StyleBlock: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Color: stringPtr("#a9b1d6"),
+ },
+ },
+ LevelIndent: defaultListIndent,
+ },
+ Heading: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ BlockSuffix: "\n",
+ Color: stringPtr("#bb9af7"),
+ Bold: boolPtr(true),
+ },
+ },
+ H1: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "# ",
+ Bold: boolPtr(true),
+ },
+ },
+ H2: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "## ",
+ },
+ },
+ H3: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "### ",
+ },
+ },
+ H4: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "#### ",
+ },
+ },
+ H5: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "##### ",
+ },
+ },
+ H6: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Prefix: "###### ",
+ },
+ },
+ Strikethrough: ansi.StylePrimitive{
+ CrossedOut: boolPtr(true),
+ },
+ Emph: ansi.StylePrimitive{
+ Italic: boolPtr(true),
+ },
+ Strong: ansi.StylePrimitive{
+ Bold: boolPtr(true),
+ },
+ HorizontalRule: ansi.StylePrimitive{
+ Color: stringPtr("#565f89"),
+ Format: "\n--------\n",
+ },
+ Item: ansi.StylePrimitive{
+ BlockPrefix: "• ",
+ },
+ Enumeration: ansi.StylePrimitive{
+ BlockPrefix: ". ",
+ Color: stringPtr("#7aa2f7"),
+ },
+ Task: ansi.StyleTask{
+ StylePrimitive: ansi.StylePrimitive{},
+ Ticked: "[✓] ",
+ Unticked: "[ ] ",
+ },
+ Link: ansi.StylePrimitive{
+ Color: stringPtr("#7aa2f7"),
+ Underline: boolPtr(true),
+ },
+ LinkText: ansi.StylePrimitive{
+ Color: stringPtr("#2ac3de"),
+ },
+ Image: ansi.StylePrimitive{
+ Color: stringPtr("#7aa2f7"),
+ Underline: boolPtr(true),
+ },
+ ImageText: ansi.StylePrimitive{
+ Color: stringPtr("#2ac3de"),
+ Format: "Image: {{.text}} →",
+ },
+ Code: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Color: stringPtr("#9ece6a"),
+ },
+ },
+ CodeBlock: ansi.StyleCodeBlock{
+ StyleBlock: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{
+ Color: stringPtr("#ff9e64"),
+ },
+ Margin: uintPtr(defaultMargin),
+ },
+ Chroma: &ansi.Chroma{
+ Text: ansi.StylePrimitive{
+ Color: stringPtr("#a9b1d6"),
+ },
+ Error: ansi.StylePrimitive{
+ Color: stringPtr("#a9b1d6"),
+ BackgroundColor: stringPtr("#f7768e"),
+ },
+ Comment: ansi.StylePrimitive{
+ Color: stringPtr("#565f89"),
+ },
+ CommentPreproc: ansi.StylePrimitive{
+ Color: stringPtr("#2ac3de"),
+ },
+ Keyword: ansi.StylePrimitive{
+ Color: stringPtr("#2ac3de"),
+ },
+ KeywordReserved: ansi.StylePrimitive{
+ Color: stringPtr("#2ac3de"),
+ },
+ KeywordNamespace: ansi.StylePrimitive{
+ Color: stringPtr("#2ac3de"),
+ },
+ KeywordType: ansi.StylePrimitive{
+ Color: stringPtr("#7aa2f7"),
+ },
+ Operator: ansi.StylePrimitive{
+ Color: stringPtr("#2ac3de"),
+ },
+ Punctuation: ansi.StylePrimitive{
+ Color: stringPtr("#a9b1d6"),
+ },
+ Name: ansi.StylePrimitive{
+ Color: stringPtr("#7aa2f7"),
+ },
+ NameConstant: ansi.StylePrimitive{
+ Color: stringPtr("#bb9af7"),
+ },
+ NameBuiltin: ansi.StylePrimitive{
+ Color: stringPtr("#7aa2f7"),
+ },
+ NameTag: ansi.StylePrimitive{
+ Color: stringPtr("#2ac3de"),
+ },
+ NameAttribute: ansi.StylePrimitive{
+ Color: stringPtr("#9ece6a"),
+ },
+ NameClass: ansi.StylePrimitive{
+ Color: stringPtr("#7aa2f7"),
+ },
+ NameDecorator: ansi.StylePrimitive{
+ Color: stringPtr("#9ece6a"),
+ },
+ NameFunction: ansi.StylePrimitive{
+ Color: stringPtr("#9ece6a"),
+ },
+ LiteralNumber: ansi.StylePrimitive{},
+ LiteralString: ansi.StylePrimitive{
+ Color: stringPtr("#e0af68"),
+ },
+ LiteralStringEscape: ansi.StylePrimitive{
+ Color: stringPtr("#2ac3de"),
+ },
+ GenericDeleted: ansi.StylePrimitive{
+ Color: stringPtr("#f7768e"),
+ },
+ GenericEmph: ansi.StylePrimitive{
+ Italic: boolPtr(true),
+ },
+ GenericInserted: ansi.StylePrimitive{
+ Color: stringPtr("#9ece6a"),
+ },
+ GenericStrong: ansi.StylePrimitive{
+ Bold: boolPtr(true),
+ },
+ GenericSubheading: ansi.StylePrimitive{
+ Color: stringPtr("#bb9af7"),
+ },
+ Background: ansi.StylePrimitive{
+ BackgroundColor: stringPtr("#1a1b26"),
+ },
+ },
+ },
+ Table: ansi.StyleTable{
+ StyleBlock: ansi.StyleBlock{
+ StylePrimitive: ansi.StylePrimitive{},
+ },
+ },
+ DefinitionDescription: ansi.StylePrimitive{
+ BlockPrefix: "\n🠶 ",
+ },
+}
diff --git a/vendor/github.com/charmbracelet/glamour/styles/tokyo_night.json b/vendor/github.com/charmbracelet/glamour/styles/tokyo_night.json
new file mode 100644
index 00000000..8016b096
--- /dev/null
+++ b/vendor/github.com/charmbracelet/glamour/styles/tokyo_night.json
@@ -0,0 +1,182 @@
+{
+ "document": {
+ "block_prefix": "\n",
+ "block_suffix": "\n",
+ "color": "#a9b1d6",
+ "margin": 2
+ },
+ "block_quote": {
+ "indent": 1,
+ "indent_token": "│ "
+ },
+ "paragraph": {},
+ "list": {
+ "color": "#a9b1d6",
+ "level_indent": 2
+ },
+ "heading": {
+ "block_suffix": "\n",
+ "color": "#bb9af7",
+ "bold": true
+ },
+ "h1": {
+ "prefix": "# ",
+ "bold": true
+ },
+ "h2": {
+ "prefix": "## "
+ },
+ "h3": {
+ "prefix": "### "
+ },
+ "h4": {
+ "prefix": "#### "
+ },
+ "h5": {
+ "prefix": "##### "
+ },
+ "h6": {
+ "prefix": "###### "
+ },
+ "text": {},
+ "strikethrough": {
+ "crossed_out": true
+ },
+ "emph": {
+ "italic": true
+ },
+ "strong": {
+ "bold": true
+ },
+ "hr": {
+ "color": "#565f89",
+ "format": "\n--------\n"
+ },
+ "item": {
+ "block_prefix": "• "
+ },
+ "enumeration": {
+ "block_prefix": ". ",
+ "color": "#7aa2f7"
+ },
+ "task": {
+ "ticked": "[✓] ",
+ "unticked": "[ ] "
+ },
+ "link": {
+ "color": "#7aa2f7",
+ "underline": true
+ },
+ "link_text": {
+ "color": "#2ac3de"
+ },
+ "image": {
+ "color": "#7aa2f7",
+ "underline": true
+ },
+ "image_text": {
+ "color": "#2ac3de",
+ "format": "Image: {{.text}} →"
+ },
+ "code": {
+ "color": "#9ece6a"
+ },
+ "code_block": {
+ "color": "#ff9e64",
+ "margin": 2,
+ "chroma": {
+ "text": {
+ "color": "#a9b1d6"
+ },
+ "error": {
+ "color": "#a9b1d6",
+ "background_color": "#f7768e"
+ },
+ "comment": {
+ "color": "#565f89"
+ },
+ "comment_preproc": {
+ "color": "#2ac3de"
+ },
+ "keyword": {
+ "color": "#2ac3de"
+ },
+ "keyword_reserved": {
+ "color": "#2ac3de"
+ },
+ "keyword_namespace": {
+ "color": "#2ac3de"
+ },
+ "keyword_type": {
+ "color": "#7aa2f7"
+ },
+ "operator": {
+ "color": "#2ac3de"
+ },
+ "punctuation": {
+ "color": "#a9b1d6"
+ },
+ "name": {
+ "color": "#7aa2f7"
+ },
+ "name_builtin": {
+ "color": "#7aa2f7"
+ },
+ "name_tag": {
+ "color": "#2ac3de"
+ },
+ "name_attribute": {
+ "color": "#9ece6a"
+ },
+ "name_class": {
+ "color": "#7aa2f7"
+ },
+ "name_constant": {
+ "color": "#bb9af7"
+ },
+ "name_decorator": {
+ "color": "#9ece6a"
+ },
+ "name_exception": {},
+ "name_function": {
+ "color": "#9ece6a"
+ },
+ "name_other": {},
+ "literal": {},
+ "literal_number": {},
+ "literal_date": {},
+ "literal_string": {
+ "color": "#e0af68"
+ },
+ "literal_string_escape": {
+ "color": "#2ac3de"
+ },
+ "generic_deleted": {
+ "color": "#f7768e"
+ },
+ "generic_emph": {
+ "italic": true
+ },
+ "generic_inserted": {
+ "color": "#9ece6a"
+ },
+ "generic_strong": {
+ "bold": true
+ },
+ "generic_subheading": {
+ "color": "#bb9af7"
+ },
+ "background": {
+ "background_color": "#1a1b26"
+ }
+ }
+ },
+ "table": {},
+ "definition_list": {},
+ "definition_term": {},
+ "definition_description": {
+ "block_prefix": "\n🠶 "
+ },
+ "html_block": {},
+ "html_span": {}
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/.editorconfig b/vendor/github.com/charmbracelet/lipgloss/.editorconfig
new file mode 100644
index 00000000..5de2df8c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/.editorconfig
@@ -0,0 +1,18 @@
+# https://editorconfig.org/
+
+root = true
+
+[*]
+charset = utf-8
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 2
+
+[*.go]
+indent_style = tab
+indent_size = 8
+
+[*.golden]
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/vendor/github.com/charmbracelet/lipgloss/.gitattributes b/vendor/github.com/charmbracelet/lipgloss/.gitattributes
new file mode 100644
index 00000000..d5273520
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/.gitattributes
@@ -0,0 +1 @@
+*.golden linguist-generated=true -text
diff --git a/vendor/github.com/charmbracelet/lipgloss/.gitignore b/vendor/github.com/charmbracelet/lipgloss/.gitignore
new file mode 100644
index 00000000..db482015
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/.gitignore
@@ -0,0 +1,2 @@
+ssh_example_ed25519*
+dist/
diff --git a/vendor/github.com/charmbracelet/lipgloss/.golangci.yml b/vendor/github.com/charmbracelet/lipgloss/.golangci.yml
new file mode 100644
index 00000000..7c0a115e
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/.golangci.yml
@@ -0,0 +1,49 @@
+version: "2"
+run:
+ tests: false
+linters:
+ enable:
+ - bodyclose
+ - exhaustive
+ - goconst
+ - godot
+ - godox
+ - gomoddirectives
+ - goprintffuncname
+ - gosec
+ - misspell
+ - nakedret
+ - nestif
+ - nilerr
+ - noctx
+ - nolintlint
+ - prealloc
+ - revive
+ - rowserrcheck
+ - sqlclosecheck
+ - tparallel
+ - unconvert
+ - unparam
+ - whitespace
+ - wrapcheck
+ exclusions:
+ generated: lax
+ presets:
+ - common-false-positives
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
+issues:
+ max-issues-per-linter: 0
+ max-same-issues: 0
+formatters:
+ enable:
+ - gofumpt
+ - goimports
+ exclusions:
+ generated: lax
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
diff --git a/vendor/github.com/charmbracelet/lipgloss/.goreleaser.yml b/vendor/github.com/charmbracelet/lipgloss/.goreleaser.yml
new file mode 100644
index 00000000..3353d020
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/.goreleaser.yml
@@ -0,0 +1,5 @@
+# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
+version: 2
+includes:
+ - from_url:
+ url: charmbracelet/meta/main/goreleaser-lib.yaml
diff --git a/vendor/github.com/charmbracelet/lipgloss/LICENSE b/vendor/github.com/charmbracelet/lipgloss/LICENSE
new file mode 100644
index 00000000..6f5b1fa6
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021-2023 Charmbracelet, Inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/charmbracelet/lipgloss/README.md b/vendor/github.com/charmbracelet/lipgloss/README.md
new file mode 100644
index 00000000..cee2371c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/README.md
@@ -0,0 +1,815 @@
+# Lip Gloss
+
+
+
+Style definitions for nice terminal layouts. Built with TUIs in mind.
+
+
+
+Lip Gloss takes an expressive, declarative approach to terminal rendering.
+Users familiar with CSS will feel at home with Lip Gloss.
+
+```go
+
+import "github.com/charmbracelet/lipgloss"
+
+var style = lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color("#FAFAFA")).
+ Background(lipgloss.Color("#7D56F4")).
+ PaddingTop(2).
+ PaddingLeft(4).
+ Width(22)
+
+fmt.Println(style.Render("Hello, kitty"))
+```
+
+## Colors
+
+Lip Gloss supports the following color profiles:
+
+### ANSI 16 colors (4-bit)
+
+```go
+lipgloss.Color("5") // magenta
+lipgloss.Color("9") // red
+lipgloss.Color("12") // light blue
+```
+
+### ANSI 256 Colors (8-bit)
+
+```go
+lipgloss.Color("86") // aqua
+lipgloss.Color("201") // hot pink
+lipgloss.Color("202") // orange
+```
+
+### True Color (16,777,216 colors; 24-bit)
+
+```go
+lipgloss.Color("#0000FF") // good ol' 100% blue
+lipgloss.Color("#04B575") // a green
+lipgloss.Color("#3C3C3C") // a dark gray
+```
+
+...as well as a 1-bit ASCII profile, which is black and white only.
+
+The terminal's color profile will be automatically detected, and colors outside
+the gamut of the current palette will be automatically coerced to their closest
+available value.
+
+### Adaptive Colors
+
+You can also specify color options for light and dark backgrounds:
+
+```go
+lipgloss.AdaptiveColor{Light: "236", Dark: "248"}
+```
+
+The terminal's background color will automatically be detected and the
+appropriate color will be chosen at runtime.
+
+### Complete Colors
+
+CompleteColor specifies exact values for True Color, ANSI256, and ANSI color
+profiles.
+
+```go
+lipgloss.CompleteColor{TrueColor: "#0000FF", ANSI256: "86", ANSI: "5"}
+```
+
+Automatic color degradation will not be performed in this case and it will be
+based on the color specified.
+
+### Complete Adaptive Colors
+
+You can use `CompleteColor` with `AdaptiveColor` to specify the exact values for
+light and dark backgrounds without automatic color degradation.
+
+```go
+lipgloss.CompleteAdaptiveColor{
+ Light: CompleteColor{TrueColor: "#d7ffae", ANSI256: "193", ANSI: "11"},
+ Dark: CompleteColor{TrueColor: "#d75fee", ANSI256: "163", ANSI: "5"},
+}
+```
+
+## Inline Formatting
+
+Lip Gloss supports the usual ANSI text formatting options:
+
+```go
+var style = lipgloss.NewStyle().
+ Bold(true).
+ Italic(true).
+ Faint(true).
+ Blink(true).
+ Strikethrough(true).
+ Underline(true).
+ Reverse(true)
+```
+
+## Block-Level Formatting
+
+Lip Gloss also supports rules for block-level formatting:
+
+```go
+// Padding
+var style = lipgloss.NewStyle().
+ PaddingTop(2).
+ PaddingRight(4).
+ PaddingBottom(2).
+ PaddingLeft(4)
+
+// Margins
+var style = lipgloss.NewStyle().
+ MarginTop(2).
+ MarginRight(4).
+ MarginBottom(2).
+ MarginLeft(4)
+```
+
+There is also shorthand syntax for margins and padding, which follows the same
+format as CSS:
+
+```go
+// 2 cells on all sides
+lipgloss.NewStyle().Padding(2)
+
+// 2 cells on the top and bottom, 4 cells on the left and right
+lipgloss.NewStyle().Margin(2, 4)
+
+// 1 cell on the top, 4 cells on the sides, 2 cells on the bottom
+lipgloss.NewStyle().Padding(1, 4, 2)
+
+// Clockwise, starting from the top: 2 cells on the top, 4 on the right, 3 on
+// the bottom, and 1 on the left
+lipgloss.NewStyle().Margin(2, 4, 3, 1)
+```
+
+## Aligning Text
+
+You can align paragraphs of text to the left, right, or center.
+
+```go
+var style = lipgloss.NewStyle().
+ Width(24).
+ Align(lipgloss.Left). // align it left
+ Align(lipgloss.Right). // no wait, align it right
+ Align(lipgloss.Center) // just kidding, align it in the center
+```
+
+## Width and Height
+
+Setting a minimum width and height is simple and straightforward.
+
+```go
+var style = lipgloss.NewStyle().
+ SetString("What’s for lunch?").
+ Width(24).
+ Height(32).
+ Foreground(lipgloss.Color("63"))
+```
+
+## Borders
+
+Adding borders is easy:
+
+```go
+// Add a purple, rectangular border
+var style = lipgloss.NewStyle().
+ BorderStyle(lipgloss.NormalBorder()).
+ BorderForeground(lipgloss.Color("63"))
+
+// Set a rounded, yellow-on-purple border to the top and left
+var anotherStyle = lipgloss.NewStyle().
+ BorderStyle(lipgloss.RoundedBorder()).
+ BorderForeground(lipgloss.Color("228")).
+ BorderBackground(lipgloss.Color("63")).
+ BorderTop(true).
+ BorderLeft(true)
+
+// Make your own border
+var myCuteBorder = lipgloss.Border{
+ Top: "._.:*:",
+ Bottom: "._.:*:",
+ Left: "|*",
+ Right: "|*",
+ TopLeft: "*",
+ TopRight: "*",
+ BottomLeft: "*",
+ BottomRight: "*",
+}
+```
+
+There are also shorthand functions for defining borders, which follow a similar
+pattern to the margin and padding shorthand functions.
+
+```go
+// Add a thick border to the top and bottom
+lipgloss.NewStyle().
+ Border(lipgloss.ThickBorder(), true, false)
+
+// Add a double border to the top and left sides. Rules are set clockwise
+// from top.
+lipgloss.NewStyle().
+ Border(lipgloss.DoubleBorder(), true, false, false, true)
+```
+
+For more on borders see [the docs][docs].
+
+## Copying Styles
+
+Just use assignment:
+
+```go
+style := lipgloss.NewStyle().Foreground(lipgloss.Color("219"))
+
+copiedStyle := style // this is a true copy
+
+wildStyle := style.Blink(true) // this is also true copy, with blink added
+
+```
+
+Since `Style` data structures contains only primitive types, assigning a style
+to another effectively creates a new copy of the style without mutating the
+original.
+
+## Inheritance
+
+Styles can inherit rules from other styles. When inheriting, only unset rules
+on the receiver are inherited.
+
+```go
+var styleA = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("229")).
+ Background(lipgloss.Color("63"))
+
+// Only the background color will be inherited here, because the foreground
+// color will have been already set:
+var styleB = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("201")).
+ Inherit(styleA)
+```
+
+## Unsetting Rules
+
+All rules can be unset:
+
+```go
+var style = lipgloss.NewStyle().
+ Bold(true). // make it bold
+ UnsetBold(). // jk don't make it bold
+ Background(lipgloss.Color("227")). // yellow background
+ UnsetBackground() // never mind
+```
+
+When a rule is unset, it won't be inherited or copied.
+
+## Enforcing Rules
+
+Sometimes, such as when developing a component, you want to make sure style
+definitions respect their intended purpose in the UI. This is where `Inline`
+and `MaxWidth`, and `MaxHeight` come in:
+
+```go
+// Force rendering onto a single line, ignoring margins, padding, and borders.
+someStyle.Inline(true).Render("yadda yadda")
+
+// Also limit rendering to five cells
+someStyle.Inline(true).MaxWidth(5).Render("yadda yadda")
+
+// Limit rendering to a 5x5 cell block
+someStyle.MaxWidth(5).MaxHeight(5).Render("yadda yadda")
+```
+
+## Tabs
+
+The tab character (`\t`) is rendered differently in different terminals (often
+as 8 spaces, sometimes 4). Because of this inconsistency, Lip Gloss converts
+tabs to 4 spaces at render time. This behavior can be changed on a per-style
+basis, however:
+
+```go
+style := lipgloss.NewStyle() // tabs will render as 4 spaces, the default
+style = style.TabWidth(2) // render tabs as 2 spaces
+style = style.TabWidth(0) // remove tabs entirely
+style = style.TabWidth(lipgloss.NoTabConversion) // leave tabs intact
+```
+
+## Rendering
+
+Generally, you just call the `Render(string...)` method on a `lipgloss.Style`:
+
+```go
+style := lipgloss.NewStyle().Bold(true).SetString("Hello,")
+fmt.Println(style.Render("kitty.")) // Hello, kitty.
+fmt.Println(style.Render("puppy.")) // Hello, puppy.
+```
+
+But you could also use the Stringer interface:
+
+```go
+var style = lipgloss.NewStyle().SetString("你好,猫咪。").Bold(true)
+fmt.Println(style) // 你好,猫咪。
+```
+
+### Custom Renderers
+
+Custom renderers allow you to render to a specific outputs. This is
+particularly important when you want to render to different outputs and
+correctly detect the color profile and dark background status for each, such as
+in a server-client situation.
+
+```go
+func myLittleHandler(sess ssh.Session) {
+ // Create a renderer for the client.
+ renderer := lipgloss.NewRenderer(sess)
+
+ // Create a new style on the renderer.
+ style := renderer.NewStyle().Background(lipgloss.AdaptiveColor{Light: "63", Dark: "228"})
+
+ // Render. The color profile and dark background state will be correctly detected.
+ io.WriteString(sess, style.Render("Heyyyyyyy"))
+}
+```
+
+For an example on using a custom renderer over SSH with [Wish][wish] see the
+[SSH example][ssh-example].
+
+## Utilities
+
+In addition to pure styling, Lip Gloss also ships with some utilities to help
+assemble your layouts.
+
+### Joining Paragraphs
+
+Horizontally and vertically joining paragraphs is a cinch.
+
+```go
+// Horizontally join three paragraphs along their bottom edges
+lipgloss.JoinHorizontal(lipgloss.Bottom, paragraphA, paragraphB, paragraphC)
+
+// Vertically join two paragraphs along their center axes
+lipgloss.JoinVertical(lipgloss.Center, paragraphA, paragraphB)
+
+// Horizontally join three paragraphs, with the shorter ones aligning 20%
+// from the top of the tallest
+lipgloss.JoinHorizontal(0.2, paragraphA, paragraphB, paragraphC)
+```
+
+### Measuring Width and Height
+
+Sometimes you’ll want to know the width and height of text blocks when building
+your layouts.
+
+```go
+// Render a block of text.
+var style = lipgloss.NewStyle().
+ Width(40).
+ Padding(2)
+var block string = style.Render(someLongString)
+
+// Get the actual, physical dimensions of the text block.
+width := lipgloss.Width(block)
+height := lipgloss.Height(block)
+
+// Here's a shorthand function.
+w, h := lipgloss.Size(block)
+```
+
+### Placing Text in Whitespace
+
+Sometimes you’ll simply want to place a block of text in whitespace.
+
+```go
+// Center a paragraph horizontally in a space 80 cells wide. The height of
+// the block returned will be as tall as the input paragraph.
+block := lipgloss.PlaceHorizontal(80, lipgloss.Center, fancyStyledParagraph)
+
+// Place a paragraph at the bottom of a space 30 cells tall. The width of
+// the text block returned will be as wide as the input paragraph.
+block := lipgloss.PlaceVertical(30, lipgloss.Bottom, fancyStyledParagraph)
+
+// Place a paragraph in the bottom right corner of a 30x80 cell space.
+block := lipgloss.Place(30, 80, lipgloss.Right, lipgloss.Bottom, fancyStyledParagraph)
+```
+
+You can also style the whitespace. For details, see [the docs][docs].
+
+## Rendering Tables
+
+Lip Gloss ships with a table rendering sub-package.
+
+```go
+import "github.com/charmbracelet/lipgloss/table"
+```
+
+Define some rows of data.
+
+```go
+rows := [][]string{
+ {"Chinese", "您好", "你好"},
+ {"Japanese", "こんにちは", "やあ"},
+ {"Arabic", "أهلين", "أهلا"},
+ {"Russian", "Здравствуйте", "Привет"},
+ {"Spanish", "Hola", "¿Qué tal?"},
+}
+```
+
+Use the table package to style and render the table.
+
+```go
+var (
+ purple = lipgloss.Color("99")
+ gray = lipgloss.Color("245")
+ lightGray = lipgloss.Color("241")
+
+ headerStyle = lipgloss.NewStyle().Foreground(purple).Bold(true).Align(lipgloss.Center)
+ cellStyle = lipgloss.NewStyle().Padding(0, 1).Width(14)
+ oddRowStyle = cellStyle.Foreground(gray)
+ evenRowStyle = cellStyle.Foreground(lightGray)
+)
+
+t := table.New().
+ Border(lipgloss.NormalBorder()).
+ BorderStyle(lipgloss.NewStyle().Foreground(purple)).
+ StyleFunc(func(row, col int) lipgloss.Style {
+ switch {
+ case row == table.HeaderRow:
+ return headerStyle
+ case row%2 == 0:
+ return evenRowStyle
+ default:
+ return oddRowStyle
+ }
+ }).
+ Headers("LANGUAGE", "FORMAL", "INFORMAL").
+ Rows(rows...)
+
+// You can also add tables row-by-row
+t.Row("English", "You look absolutely fabulous.", "How's it going?")
+```
+
+Print the table.
+
+```go
+fmt.Println(t)
+```
+
+
+
+> [!WARNING]
+> Table `Rows` need to be declared before `Offset` otherwise it does nothing.
+
+### Table Borders
+
+There are helpers to generate tables in markdown or ASCII style:
+
+#### Markdown Table
+
+```go
+table.New().Border(lipgloss.MarkdownBorder()).BorderTop(false).BorderBottom(false)
+```
+
+```
+| LANGUAGE | FORMAL | INFORMAL |
+|----------|--------------|-----------|
+| Chinese | Nǐn hǎo | Nǐ hǎo |
+| French | Bonjour | Salut |
+| Russian | Zdravstvuyte | Privet |
+| Spanish | Hola | ¿Qué tal? |
+```
+
+#### ASCII Table
+
+```go
+table.New().Border(lipgloss.ASCIIBorder())
+```
+
+```
++----------+--------------+-----------+
+| LANGUAGE | FORMAL | INFORMAL |
++----------+--------------+-----------+
+| Chinese | Nǐn hǎo | Nǐ hǎo |
+| French | Bonjour | Salut |
+| Russian | Zdravstvuyte | Privet |
+| Spanish | Hola | ¿Qué tal? |
++----------+--------------+-----------+
+```
+
+For more on tables see [the docs](https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc) and [examples](https://github.com/charmbracelet/lipgloss/tree/master/examples/table).
+
+## Rendering Lists
+
+Lip Gloss ships with a list rendering sub-package.
+
+```go
+import "github.com/charmbracelet/lipgloss/list"
+```
+
+Define a new list.
+
+```go
+l := list.New("A", "B", "C")
+```
+
+Print the list.
+
+```go
+fmt.Println(l)
+
+// • A
+// • B
+// • C
+```
+
+Lists have the ability to nest.
+
+```go
+l := list.New(
+ "A", list.New("Artichoke"),
+ "B", list.New("Baking Flour", "Bananas", "Barley", "Bean Sprouts"),
+ "C", list.New("Cashew Apple", "Cashews", "Coconut Milk", "Curry Paste", "Currywurst"),
+ "D", list.New("Dill", "Dragonfruit", "Dried Shrimp"),
+ "E", list.New("Eggs"),
+ "F", list.New("Fish Cake", "Furikake"),
+ "J", list.New("Jicama"),
+ "K", list.New("Kohlrabi"),
+ "L", list.New("Leeks", "Lentils", "Licorice Root"),
+)
+```
+
+Print the list.
+
+```go
+fmt.Println(l)
+```
+
+
+
+
+
+Lists can be customized via their enumeration function as well as using
+`lipgloss.Style`s.
+
+```go
+enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("99")).MarginRight(1)
+itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212")).MarginRight(1)
+
+l := list.New(
+ "Glossier",
+ "Claire’s Boutique",
+ "Nyx",
+ "Mac",
+ "Milk",
+ ).
+ Enumerator(list.Roman).
+ EnumeratorStyle(enumeratorStyle).
+ ItemStyle(itemStyle)
+```
+
+Print the list.
+
+
+
+
+
+In addition to the predefined enumerators (`Arabic`, `Alphabet`, `Roman`, `Bullet`, `Tree`),
+you may also define your own custom enumerator:
+
+```go
+l := list.New("Duck", "Duck", "Duck", "Duck", "Goose", "Duck", "Duck")
+
+func DuckDuckGooseEnumerator(l list.Items, i int) string {
+ if l.At(i).Value() == "Goose" {
+ return "Honk →"
+ }
+ return ""
+}
+
+l = l.Enumerator(DuckDuckGooseEnumerator)
+```
+
+Print the list:
+
+
+
+
+
+If you need, you can also build lists incrementally:
+
+```go
+l := list.New()
+
+for i := 0; i < repeat; i++ {
+ l.Item("Lip Gloss")
+}
+```
+
+## Rendering Trees
+
+Lip Gloss ships with a tree rendering sub-package.
+
+```go
+import "github.com/charmbracelet/lipgloss/tree"
+```
+
+Define a new tree.
+
+```go
+t := tree.Root(".").
+ Child("A", "B", "C")
+```
+
+Print the tree.
+
+```go
+fmt.Println(t)
+
+// .
+// ├── A
+// ├── B
+// └── C
+```
+
+Trees have the ability to nest.
+
+```go
+t := tree.Root(".").
+ Child("macOS").
+ Child(
+ tree.New().
+ Root("Linux").
+ Child("NixOS").
+ Child("Arch Linux (btw)").
+ Child("Void Linux"),
+ ).
+ Child(
+ tree.New().
+ Root("BSD").
+ Child("FreeBSD").
+ Child("OpenBSD"),
+ )
+```
+
+Print the tree.
+
+```go
+fmt.Println(t)
+```
+
+
+
+
+
+Trees can be customized via their enumeration function as well as using
+`lipgloss.Style`s.
+
+```go
+enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("63")).MarginRight(1)
+rootStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("35"))
+itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212"))
+
+t := tree.
+ Root("⁜ Makeup").
+ Child(
+ "Glossier",
+ "Fenty Beauty",
+ tree.New().Child(
+ "Gloss Bomb Universal Lip Luminizer",
+ "Hot Cheeks Velour Blushlighter",
+ ),
+ "Nyx",
+ "Mac",
+ "Milk",
+ ).
+ Enumerator(tree.RoundedEnumerator).
+ EnumeratorStyle(enumeratorStyle).
+ RootStyle(rootStyle).
+ ItemStyle(itemStyle)
+```
+
+Print the tree.
+
+
+
+
+
+The predefined enumerators for trees are `DefaultEnumerator` and `RoundedEnumerator`.
+
+If you need, you can also build trees incrementally:
+
+```go
+t := tree.New()
+
+for i := 0; i < repeat; i++ {
+ t.Child("Lip Gloss")
+}
+```
+
+---
+
+## FAQ
+
+
+
+Why are things misaligning? Why are borders at the wrong widths?
+
+This is most likely due to your locale and encoding, particularly with
+regard to Chinese, Japanese, and Korean (for example, zh_CN.UTF-8
+or ja_JP.UTF-8). The most direct way to fix this is to set
+RUNEWIDTH_EASTASIAN=0 in your environment.
+
+For details see https://github.com/charmbracelet/lipgloss/issues/40.
+
+
+
+
+Why isn't Lip Gloss displaying colors?
+
+Lip Gloss automatically degrades colors to the best available option in the
+given terminal, and if output's not a TTY it will remove color output entirely.
+This is common when running tests, CI, or when piping output elsewhere.
+
+If necessary, you can force a color profile in your tests with
+SetColorProfile.
+
+```go
+import (
+ "github.com/charmbracelet/lipgloss"
+ "github.com/muesli/termenv"
+)
+
+lipgloss.SetColorProfile(termenv.TrueColor)
+```
+
+_Note:_ this option limits the flexibility of your application and can cause
+ANSI escape codes to be output in cases where that might not be desired. Take
+careful note of your use case and environment before choosing to force a color
+profile.
+
+
+
+## What about [Bubble Tea][tea]?
+
+Lip Gloss doesn’t replace Bubble Tea. Rather, it is an excellent Bubble Tea
+companion. It was designed to make assembling terminal user interface views as
+simple and fun as possible so that you can focus on building your application
+instead of concerning yourself with low-level layout details.
+
+In simple terms, you can use Lip Gloss to help build your Bubble Tea views.
+
+[tea]: https://github.com/charmbracelet/tea
+
+## Under the Hood
+
+Lip Gloss is built on the excellent [Termenv][termenv] and [Reflow][reflow]
+libraries which deal with color and ANSI-aware text operations, respectively.
+For many use cases Termenv and Reflow will be sufficient for your needs.
+
+[termenv]: https://github.com/muesli/termenv
+[reflow]: https://github.com/muesli/reflow
+
+## Rendering Markdown
+
+For a more document-centric rendering solution with support for things like
+lists, tables, and syntax-highlighted code have a look at [Glamour][glamour],
+the stylesheet-based Markdown renderer.
+
+[glamour]: https://github.com/charmbracelet/glamour
+
+## Contributing
+
+See [contributing][contribute].
+
+[contribute]: https://github.com/charmbracelet/lipgloss/contribute
+
+## Feedback
+
+We’d love to hear your thoughts on this project. Feel free to drop us a note!
+
+- [Twitter](https://twitter.com/charmcli)
+- [The Fediverse](https://mastodon.social/@charmcli)
+- [Discord](https://charm.sh/chat)
+
+## License
+
+[MIT](https://github.com/charmbracelet/lipgloss/raw/master/LICENSE)
+
+---
+
+Part of [Charm](https://charm.sh).
+
+
+
+Charm热爱开源 • Charm loves open source
+
+[docs]: https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc
+[wish]: https://github.com/charmbracelet/wish
+[ssh-example]: examples/ssh
diff --git a/vendor/github.com/charmbracelet/lipgloss/Taskfile.yaml b/vendor/github.com/charmbracelet/lipgloss/Taskfile.yaml
new file mode 100644
index 00000000..0b4a7711
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/Taskfile.yaml
@@ -0,0 +1,19 @@
+# https://taskfile.dev
+
+version: '3'
+
+tasks:
+ lint:
+ desc: Run base linters
+ cmds:
+ - golangci-lint run
+
+ test:
+ desc: Run tests
+ cmds:
+ - go test ./... {{.CLI_ARGS}}
+
+ test:table:
+ desc: Run table tests
+ cmds:
+ - go test ./table {{.CLI_ARGS}}
diff --git a/vendor/github.com/charmbracelet/lipgloss/align.go b/vendor/github.com/charmbracelet/lipgloss/align.go
new file mode 100644
index 00000000..ce654b23
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/align.go
@@ -0,0 +1,83 @@
+package lipgloss
+
+import (
+ "strings"
+
+ "github.com/charmbracelet/x/ansi"
+ "github.com/muesli/termenv"
+)
+
+// Perform text alignment. If the string is multi-lined, we also make all lines
+// the same width by padding them with spaces. If a termenv style is passed,
+// use that to style the spaces added.
+func alignTextHorizontal(str string, pos Position, width int, style *termenv.Style) string {
+ lines, widestLine := getLines(str)
+ var b strings.Builder
+
+ for i, l := range lines {
+ lineWidth := ansi.StringWidth(l)
+
+ shortAmount := widestLine - lineWidth // difference from the widest line
+ shortAmount += max(0, width-(shortAmount+lineWidth)) // difference from the total width, if set
+
+ if shortAmount > 0 {
+ switch pos { //nolint:exhaustive
+ case Right:
+ s := strings.Repeat(" ", shortAmount)
+ if style != nil {
+ s = style.Styled(s)
+ }
+ l = s + l
+ case Center:
+ // Note: remainder goes on the right.
+ left := shortAmount / 2 //nolint:mnd
+ right := left + shortAmount%2 //nolint:mnd
+
+ leftSpaces := strings.Repeat(" ", left)
+ rightSpaces := strings.Repeat(" ", right)
+
+ if style != nil {
+ leftSpaces = style.Styled(leftSpaces)
+ rightSpaces = style.Styled(rightSpaces)
+ }
+ l = leftSpaces + l + rightSpaces
+ default: // Left
+ s := strings.Repeat(" ", shortAmount)
+ if style != nil {
+ s = style.Styled(s)
+ }
+ l += s
+ }
+ }
+
+ b.WriteString(l)
+ if i < len(lines)-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ return b.String()
+}
+
+func alignTextVertical(str string, pos Position, height int, _ *termenv.Style) string {
+ strHeight := strings.Count(str, "\n") + 1
+ if height < strHeight {
+ return str
+ }
+
+ switch pos {
+ case Top:
+ return str + strings.Repeat("\n", height-strHeight)
+ case Center:
+ topPadding, bottomPadding := (height-strHeight)/2, (height-strHeight)/2 //nolint:mnd
+ if strHeight+topPadding+bottomPadding > height {
+ topPadding--
+ } else if strHeight+topPadding+bottomPadding < height {
+ bottomPadding++
+ }
+ return strings.Repeat("\n", topPadding) + str + strings.Repeat("\n", bottomPadding)
+ case Bottom:
+ return strings.Repeat("\n", height-strHeight) + str
+ }
+ return str
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/ansi_unix.go b/vendor/github.com/charmbracelet/lipgloss/ansi_unix.go
new file mode 100644
index 00000000..d416b8c9
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/ansi_unix.go
@@ -0,0 +1,7 @@
+//go:build !windows
+// +build !windows
+
+package lipgloss
+
+// enableLegacyWindowsANSI is only needed on Windows.
+func enableLegacyWindowsANSI() {}
diff --git a/vendor/github.com/charmbracelet/lipgloss/ansi_windows.go b/vendor/github.com/charmbracelet/lipgloss/ansi_windows.go
new file mode 100644
index 00000000..0cf56e4c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/ansi_windows.go
@@ -0,0 +1,22 @@
+//go:build windows
+// +build windows
+
+package lipgloss
+
+import (
+ "sync"
+
+ "github.com/muesli/termenv"
+)
+
+var enableANSI sync.Once
+
+// enableANSIColors enables support for ANSI color sequences in the Windows
+// default console (cmd.exe and the PowerShell application). Note that this
+// only works with Windows 10. Also note that Windows Terminal supports colors
+// by default.
+func enableLegacyWindowsANSI() {
+ enableANSI.Do(func() {
+ _, _ = termenv.EnableWindowsANSIConsole()
+ })
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/borders.go b/vendor/github.com/charmbracelet/lipgloss/borders.go
new file mode 100644
index 00000000..b36f8743
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/borders.go
@@ -0,0 +1,490 @@
+package lipgloss
+
+import (
+ "strings"
+
+ "github.com/charmbracelet/x/ansi"
+ "github.com/muesli/termenv"
+ "github.com/rivo/uniseg"
+)
+
+// Border contains a series of values which comprise the various parts of a
+// border.
+type Border struct {
+ Top string
+ Bottom string
+ Left string
+ Right string
+ TopLeft string
+ TopRight string
+ BottomLeft string
+ BottomRight string
+ MiddleLeft string
+ MiddleRight string
+ Middle string
+ MiddleTop string
+ MiddleBottom string
+}
+
+// GetTopSize returns the width of the top border. If borders contain runes of
+// varying widths, the widest rune is returned. If no border exists on the top
+// edge, 0 is returned.
+func (b Border) GetTopSize() int {
+ return getBorderEdgeWidth(b.TopLeft, b.Top, b.TopRight)
+}
+
+// GetRightSize returns the width of the right border. If borders contain
+// runes of varying widths, the widest rune is returned. If no border exists on
+// the right edge, 0 is returned.
+func (b Border) GetRightSize() int {
+ return getBorderEdgeWidth(b.TopRight, b.Right, b.BottomRight)
+}
+
+// GetBottomSize returns the width of the bottom border. If borders contain
+// runes of varying widths, the widest rune is returned. If no border exists on
+// the bottom edge, 0 is returned.
+func (b Border) GetBottomSize() int {
+ return getBorderEdgeWidth(b.BottomLeft, b.Bottom, b.BottomRight)
+}
+
+// GetLeftSize returns the width of the left border. If borders contain runes
+// of varying widths, the widest rune is returned. If no border exists on the
+// left edge, 0 is returned.
+func (b Border) GetLeftSize() int {
+ return getBorderEdgeWidth(b.TopLeft, b.Left, b.BottomLeft)
+}
+
+func getBorderEdgeWidth(borderParts ...string) (maxWidth int) {
+ for _, piece := range borderParts {
+ w := maxRuneWidth(piece)
+ if w > maxWidth {
+ maxWidth = w
+ }
+ }
+ return maxWidth
+}
+
+var (
+ noBorder = Border{}
+
+ normalBorder = Border{
+ Top: "─",
+ Bottom: "─",
+ Left: "│",
+ Right: "│",
+ TopLeft: "┌",
+ TopRight: "┐",
+ BottomLeft: "└",
+ BottomRight: "┘",
+ MiddleLeft: "├",
+ MiddleRight: "┤",
+ Middle: "┼",
+ MiddleTop: "┬",
+ MiddleBottom: "┴",
+ }
+
+ roundedBorder = Border{
+ Top: "─",
+ Bottom: "─",
+ Left: "│",
+ Right: "│",
+ TopLeft: "╭",
+ TopRight: "╮",
+ BottomLeft: "╰",
+ BottomRight: "╯",
+ MiddleLeft: "├",
+ MiddleRight: "┤",
+ Middle: "┼",
+ MiddleTop: "┬",
+ MiddleBottom: "┴",
+ }
+
+ blockBorder = Border{
+ Top: "█",
+ Bottom: "█",
+ Left: "█",
+ Right: "█",
+ TopLeft: "█",
+ TopRight: "█",
+ BottomLeft: "█",
+ BottomRight: "█",
+ MiddleLeft: "█",
+ MiddleRight: "█",
+ Middle: "█",
+ MiddleTop: "█",
+ MiddleBottom: "█",
+ }
+
+ outerHalfBlockBorder = Border{
+ Top: "▀",
+ Bottom: "▄",
+ Left: "▌",
+ Right: "▐",
+ TopLeft: "▛",
+ TopRight: "▜",
+ BottomLeft: "▙",
+ BottomRight: "▟",
+ }
+
+ innerHalfBlockBorder = Border{
+ Top: "▄",
+ Bottom: "▀",
+ Left: "▐",
+ Right: "▌",
+ TopLeft: "▗",
+ TopRight: "▖",
+ BottomLeft: "▝",
+ BottomRight: "▘",
+ }
+
+ thickBorder = Border{
+ Top: "━",
+ Bottom: "━",
+ Left: "┃",
+ Right: "┃",
+ TopLeft: "┏",
+ TopRight: "┓",
+ BottomLeft: "┗",
+ BottomRight: "┛",
+ MiddleLeft: "┣",
+ MiddleRight: "┫",
+ Middle: "╋",
+ MiddleTop: "┳",
+ MiddleBottom: "┻",
+ }
+
+ doubleBorder = Border{
+ Top: "═",
+ Bottom: "═",
+ Left: "║",
+ Right: "║",
+ TopLeft: "╔",
+ TopRight: "╗",
+ BottomLeft: "╚",
+ BottomRight: "╝",
+ MiddleLeft: "╠",
+ MiddleRight: "╣",
+ Middle: "╬",
+ MiddleTop: "╦",
+ MiddleBottom: "╩",
+ }
+
+ hiddenBorder = Border{
+ Top: " ",
+ Bottom: " ",
+ Left: " ",
+ Right: " ",
+ TopLeft: " ",
+ TopRight: " ",
+ BottomLeft: " ",
+ BottomRight: " ",
+ MiddleLeft: " ",
+ MiddleRight: " ",
+ Middle: " ",
+ MiddleTop: " ",
+ MiddleBottom: " ",
+ }
+
+ markdownBorder = Border{
+ Top: "-",
+ Bottom: "-",
+ Left: "|",
+ Right: "|",
+ TopLeft: "|",
+ TopRight: "|",
+ BottomLeft: "|",
+ BottomRight: "|",
+ MiddleLeft: "|",
+ MiddleRight: "|",
+ Middle: "|",
+ MiddleTop: "|",
+ MiddleBottom: "|",
+ }
+
+ asciiBorder = Border{
+ Top: "-",
+ Bottom: "-",
+ Left: "|",
+ Right: "|",
+ TopLeft: "+",
+ TopRight: "+",
+ BottomLeft: "+",
+ BottomRight: "+",
+ MiddleLeft: "+",
+ MiddleRight: "+",
+ Middle: "+",
+ MiddleTop: "+",
+ MiddleBottom: "+",
+ }
+)
+
+// NormalBorder returns a standard-type border with a normal weight and 90
+// degree corners.
+func NormalBorder() Border {
+ return normalBorder
+}
+
+// RoundedBorder returns a border with rounded corners.
+func RoundedBorder() Border {
+ return roundedBorder
+}
+
+// BlockBorder returns a border that takes the whole block.
+func BlockBorder() Border {
+ return blockBorder
+}
+
+// OuterHalfBlockBorder returns a half-block border that sits outside the frame.
+func OuterHalfBlockBorder() Border {
+ return outerHalfBlockBorder
+}
+
+// InnerHalfBlockBorder returns a half-block border that sits inside the frame.
+func InnerHalfBlockBorder() Border {
+ return innerHalfBlockBorder
+}
+
+// ThickBorder returns a border that's thicker than the one returned by
+// NormalBorder.
+func ThickBorder() Border {
+ return thickBorder
+}
+
+// DoubleBorder returns a border comprised of two thin strokes.
+func DoubleBorder() Border {
+ return doubleBorder
+}
+
+// HiddenBorder returns a border that renders as a series of single-cell
+// spaces. It's useful for cases when you want to remove a standard border but
+// maintain layout positioning. This said, you can still apply a background
+// color to a hidden border.
+func HiddenBorder() Border {
+ return hiddenBorder
+}
+
+// MarkdownBorder return a table border in markdown style.
+//
+// Make sure to disable top and bottom border for the best result. This will
+// ensure that the output is valid markdown.
+//
+// table.New().Border(lipgloss.MarkdownBorder()).BorderTop(false).BorderBottom(false)
+func MarkdownBorder() Border {
+ return markdownBorder
+}
+
+// ASCIIBorder returns a table border with ASCII characters.
+func ASCIIBorder() Border {
+ return asciiBorder
+}
+
+func (s Style) applyBorder(str string) string {
+ var (
+ border = s.getBorderStyle()
+ hasTop = s.getAsBool(borderTopKey, false)
+ hasRight = s.getAsBool(borderRightKey, false)
+ hasBottom = s.getAsBool(borderBottomKey, false)
+ hasLeft = s.getAsBool(borderLeftKey, false)
+
+ topFG = s.getAsColor(borderTopForegroundKey)
+ rightFG = s.getAsColor(borderRightForegroundKey)
+ bottomFG = s.getAsColor(borderBottomForegroundKey)
+ leftFG = s.getAsColor(borderLeftForegroundKey)
+
+ topBG = s.getAsColor(borderTopBackgroundKey)
+ rightBG = s.getAsColor(borderRightBackgroundKey)
+ bottomBG = s.getAsColor(borderBottomBackgroundKey)
+ leftBG = s.getAsColor(borderLeftBackgroundKey)
+ )
+
+ // If a border is set and no sides have been specifically turned on or off
+ // render borders on all sides.
+ if s.implicitBorders() {
+ hasTop = true
+ hasRight = true
+ hasBottom = true
+ hasLeft = true
+ }
+
+ // If no border is set or all borders are been disabled, abort.
+ if border == noBorder || (!hasTop && !hasRight && !hasBottom && !hasLeft) {
+ return str
+ }
+
+ lines, width := getLines(str)
+
+ if hasLeft {
+ if border.Left == "" {
+ border.Left = " "
+ }
+ width += maxRuneWidth(border.Left)
+ }
+
+ if hasRight && border.Right == "" {
+ border.Right = " "
+ }
+
+ // If corners should be rendered but are set with the empty string, fill them
+ // with a single space.
+ if hasTop && hasLeft && border.TopLeft == "" {
+ border.TopLeft = " "
+ }
+ if hasTop && hasRight && border.TopRight == "" {
+ border.TopRight = " "
+ }
+ if hasBottom && hasLeft && border.BottomLeft == "" {
+ border.BottomLeft = " "
+ }
+ if hasBottom && hasRight && border.BottomRight == "" {
+ border.BottomRight = " "
+ }
+
+ // Figure out which corners we should actually be using based on which
+ // sides are set to show.
+ if hasTop {
+ switch {
+ case !hasLeft && !hasRight:
+ border.TopLeft = ""
+ border.TopRight = ""
+ case !hasLeft:
+ border.TopLeft = ""
+ case !hasRight:
+ border.TopRight = ""
+ }
+ }
+ if hasBottom {
+ switch {
+ case !hasLeft && !hasRight:
+ border.BottomLeft = ""
+ border.BottomRight = ""
+ case !hasLeft:
+ border.BottomLeft = ""
+ case !hasRight:
+ border.BottomRight = ""
+ }
+ }
+
+ // For now, limit corners to one rune.
+ border.TopLeft = getFirstRuneAsString(border.TopLeft)
+ border.TopRight = getFirstRuneAsString(border.TopRight)
+ border.BottomRight = getFirstRuneAsString(border.BottomRight)
+ border.BottomLeft = getFirstRuneAsString(border.BottomLeft)
+
+ var out strings.Builder
+
+ // Render top
+ if hasTop {
+ top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width)
+ top = s.styleBorder(top, topFG, topBG)
+ out.WriteString(top)
+ out.WriteRune('\n')
+ }
+
+ leftRunes := []rune(border.Left)
+ leftIndex := 0
+
+ rightRunes := []rune(border.Right)
+ rightIndex := 0
+
+ // Render sides
+ for i, l := range lines {
+ if hasLeft {
+ r := string(leftRunes[leftIndex])
+ leftIndex++
+ if leftIndex >= len(leftRunes) {
+ leftIndex = 0
+ }
+ out.WriteString(s.styleBorder(r, leftFG, leftBG))
+ }
+ out.WriteString(l)
+ if hasRight {
+ r := string(rightRunes[rightIndex])
+ rightIndex++
+ if rightIndex >= len(rightRunes) {
+ rightIndex = 0
+ }
+ out.WriteString(s.styleBorder(r, rightFG, rightBG))
+ }
+ if i < len(lines)-1 {
+ out.WriteRune('\n')
+ }
+ }
+
+ // Render bottom
+ if hasBottom {
+ bottom := renderHorizontalEdge(border.BottomLeft, border.Bottom, border.BottomRight, width)
+ bottom = s.styleBorder(bottom, bottomFG, bottomBG)
+ out.WriteRune('\n')
+ out.WriteString(bottom)
+ }
+
+ return out.String()
+}
+
+// Render the horizontal (top or bottom) portion of a border.
+func renderHorizontalEdge(left, middle, right string, width int) string {
+ if middle == "" {
+ middle = " "
+ }
+
+ leftWidth := ansi.StringWidth(left)
+ rightWidth := ansi.StringWidth(right)
+
+ runes := []rune(middle)
+ j := 0
+
+ out := strings.Builder{}
+ out.WriteString(left)
+ for i := leftWidth + rightWidth; i < width+rightWidth; {
+ out.WriteRune(runes[j])
+ j++
+ if j >= len(runes) {
+ j = 0
+ }
+ i += ansi.StringWidth(string(runes[j]))
+ }
+ out.WriteString(right)
+
+ return out.String()
+}
+
+// Apply foreground and background styling to a border.
+func (s Style) styleBorder(border string, fg, bg TerminalColor) string {
+ if fg == noColor && bg == noColor {
+ return border
+ }
+
+ style := termenv.Style{}
+
+ if fg != noColor {
+ style = style.Foreground(fg.color(s.r))
+ }
+ if bg != noColor {
+ style = style.Background(bg.color(s.r))
+ }
+
+ return style.Styled(border)
+}
+
+func maxRuneWidth(str string) int {
+ var width int
+
+ state := -1
+ for len(str) > 0 {
+ var w int
+ _, str, w, state = uniseg.FirstGraphemeClusterInString(str, state)
+ if w > width {
+ width = w
+ }
+ }
+
+ return width
+}
+
+func getFirstRuneAsString(str string) string {
+ if str == "" {
+ return str
+ }
+ r := []rune(str)
+ return string(r[0])
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/color.go b/vendor/github.com/charmbracelet/lipgloss/color.go
new file mode 100644
index 00000000..6caf3a3d
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/color.go
@@ -0,0 +1,172 @@
+package lipgloss
+
+import (
+ "strconv"
+
+ "github.com/muesli/termenv"
+)
+
+// TerminalColor is a color intended to be rendered in the terminal.
+type TerminalColor interface {
+ color(*Renderer) termenv.Color
+ RGBA() (r, g, b, a uint32)
+}
+
+var noColor = NoColor{}
+
+// NoColor is used to specify the absence of color styling. When this is active
+// foreground colors will be rendered with the terminal's default text color,
+// and background colors will not be drawn at all.
+//
+// Example usage:
+//
+// var style = someStyle.Background(lipgloss.NoColor{})
+type NoColor struct{}
+
+func (NoColor) color(*Renderer) termenv.Color {
+ return termenv.NoColor{}
+}
+
+// RGBA returns the RGBA value of this color. Because we have to return
+// something, despite this color being the absence of color, we're returning
+// black with 100% opacity.
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+//
+// Deprecated.
+func (n NoColor) RGBA() (r, g, b, a uint32) {
+ return 0x0, 0x0, 0x0, 0xFFFF //nolint:mnd
+}
+
+// Color specifies a color by hex or ANSI value. For example:
+//
+// ansiColor := lipgloss.Color("21")
+// hexColor := lipgloss.Color("#0000ff")
+type Color string
+
+func (c Color) color(r *Renderer) termenv.Color {
+ return r.ColorProfile().Color(string(c))
+}
+
+// RGBA returns the RGBA value of this color. This satisfies the Go Color
+// interface. Note that on error we return black with 100% opacity, or:
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+//
+// Deprecated.
+func (c Color) RGBA() (r, g, b, a uint32) {
+ return termenv.ConvertToRGB(c.color(renderer)).RGBA()
+}
+
+// ANSIColor is a color specified by an ANSI color value. It's merely syntactic
+// sugar for the more general Color function. Invalid colors will render as
+// black.
+//
+// Example usage:
+//
+// // These two statements are equivalent.
+// colorA := lipgloss.ANSIColor(21)
+// colorB := lipgloss.Color("21")
+type ANSIColor uint
+
+func (ac ANSIColor) color(r *Renderer) termenv.Color {
+ return Color(strconv.FormatUint(uint64(ac), 10)).color(r)
+}
+
+// RGBA returns the RGBA value of this color. This satisfies the Go Color
+// interface. Note that on error we return black with 100% opacity, or:
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+//
+// Deprecated.
+func (ac ANSIColor) RGBA() (r, g, b, a uint32) {
+ cf := Color(strconv.FormatUint(uint64(ac), 10))
+ return cf.RGBA()
+}
+
+// AdaptiveColor provides color options for light and dark backgrounds. The
+// appropriate color will be returned at runtime based on the darkness of the
+// terminal background color.
+//
+// Example usage:
+//
+// color := lipgloss.AdaptiveColor{Light: "#0000ff", Dark: "#000099"}
+type AdaptiveColor struct {
+ Light string
+ Dark string
+}
+
+func (ac AdaptiveColor) color(r *Renderer) termenv.Color {
+ if r.HasDarkBackground() {
+ return Color(ac.Dark).color(r)
+ }
+ return Color(ac.Light).color(r)
+}
+
+// RGBA returns the RGBA value of this color. This satisfies the Go Color
+// interface. Note that on error we return black with 100% opacity, or:
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+//
+// Deprecated.
+func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) {
+ return termenv.ConvertToRGB(ac.color(renderer)).RGBA()
+}
+
+// CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color
+// profiles. Automatic color degradation will not be performed.
+type CompleteColor struct {
+ TrueColor string
+ ANSI256 string
+ ANSI string
+}
+
+func (c CompleteColor) color(r *Renderer) termenv.Color {
+ p := r.ColorProfile()
+ switch p { //nolint:exhaustive
+ case termenv.TrueColor:
+ return p.Color(c.TrueColor)
+ case termenv.ANSI256:
+ return p.Color(c.ANSI256)
+ case termenv.ANSI:
+ return p.Color(c.ANSI)
+ default:
+ return termenv.NoColor{}
+ }
+}
+
+// RGBA returns the RGBA value of this color. This satisfies the Go Color
+// interface. Note that on error we return black with 100% opacity, or:
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color
+//
+// Deprecated.
+func (c CompleteColor) RGBA() (r, g, b, a uint32) {
+ return termenv.ConvertToRGB(c.color(renderer)).RGBA()
+}
+
+// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color
+// profiles, with separate options for light and dark backgrounds. Automatic
+// color degradation will not be performed.
+type CompleteAdaptiveColor struct {
+ Light CompleteColor
+ Dark CompleteColor
+}
+
+func (cac CompleteAdaptiveColor) color(r *Renderer) termenv.Color {
+ if r.HasDarkBackground() {
+ return cac.Dark.color(r)
+ }
+ return cac.Light.color(r)
+}
+
+// RGBA returns the RGBA value of this color. This satisfies the Go Color
+// interface. Note that on error we return black with 100% opacity, or:
+//
+// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
+//
+// Deprecated.
+func (cac CompleteAdaptiveColor) RGBA() (r, g, b, a uint32) {
+ return termenv.ConvertToRGB(cac.color(renderer)).RGBA()
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/get.go b/vendor/github.com/charmbracelet/lipgloss/get.go
new file mode 100644
index 00000000..146462f2
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/get.go
@@ -0,0 +1,556 @@
+package lipgloss
+
+import (
+ "strings"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+// GetBold returns the style's bold value. If no value is set false is returned.
+func (s Style) GetBold() bool {
+ return s.getAsBool(boldKey, false)
+}
+
+// GetItalic returns the style's italic value. If no value is set false is
+// returned.
+func (s Style) GetItalic() bool {
+ return s.getAsBool(italicKey, false)
+}
+
+// GetUnderline returns the style's underline value. If no value is set false is
+// returned.
+func (s Style) GetUnderline() bool {
+ return s.getAsBool(underlineKey, false)
+}
+
+// GetStrikethrough returns the style's strikethrough value. If no value is set false
+// is returned.
+func (s Style) GetStrikethrough() bool {
+ return s.getAsBool(strikethroughKey, false)
+}
+
+// GetReverse returns the style's reverse value. If no value is set false is
+// returned.
+func (s Style) GetReverse() bool {
+ return s.getAsBool(reverseKey, false)
+}
+
+// GetBlink returns the style's blink value. If no value is set false is
+// returned.
+func (s Style) GetBlink() bool {
+ return s.getAsBool(blinkKey, false)
+}
+
+// GetFaint returns the style's faint value. If no value is set false is
+// returned.
+func (s Style) GetFaint() bool {
+ return s.getAsBool(faintKey, false)
+}
+
+// GetForeground returns the style's foreground color. If no value is set
+// NoColor{} is returned.
+func (s Style) GetForeground() TerminalColor {
+ return s.getAsColor(foregroundKey)
+}
+
+// GetBackground returns the style's background color. If no value is set
+// NoColor{} is returned.
+func (s Style) GetBackground() TerminalColor {
+ return s.getAsColor(backgroundKey)
+}
+
+// GetWidth returns the style's width setting. If no width is set 0 is
+// returned.
+func (s Style) GetWidth() int {
+ return s.getAsInt(widthKey)
+}
+
+// GetHeight returns the style's height setting. If no height is set 0 is
+// returned.
+func (s Style) GetHeight() int {
+ return s.getAsInt(heightKey)
+}
+
+// GetAlign returns the style's implicit horizontal alignment setting.
+// If no alignment is set Position.Left is returned.
+func (s Style) GetAlign() Position {
+ v := s.getAsPosition(alignHorizontalKey)
+ if v == Position(0) {
+ return Left
+ }
+ return v
+}
+
+// GetAlignHorizontal returns the style's implicit horizontal alignment setting.
+// If no alignment is set Position.Left is returned.
+func (s Style) GetAlignHorizontal() Position {
+ v := s.getAsPosition(alignHorizontalKey)
+ if v == Position(0) {
+ return Left
+ }
+ return v
+}
+
+// GetAlignVertical returns the style's implicit vertical alignment setting.
+// If no alignment is set Position.Top is returned.
+func (s Style) GetAlignVertical() Position {
+ v := s.getAsPosition(alignVerticalKey)
+ if v == Position(0) {
+ return Top
+ }
+ return v
+}
+
+// GetPadding returns the style's top, right, bottom, and left padding values,
+// in that order. 0 is returned for unset values.
+func (s Style) GetPadding() (top, right, bottom, left int) {
+ return s.getAsInt(paddingTopKey),
+ s.getAsInt(paddingRightKey),
+ s.getAsInt(paddingBottomKey),
+ s.getAsInt(paddingLeftKey)
+}
+
+// GetPaddingTop returns the style's top padding. If no value is set 0 is
+// returned.
+func (s Style) GetPaddingTop() int {
+ return s.getAsInt(paddingTopKey)
+}
+
+// GetPaddingRight returns the style's right padding. If no value is set 0 is
+// returned.
+func (s Style) GetPaddingRight() int {
+ return s.getAsInt(paddingRightKey)
+}
+
+// GetPaddingBottom returns the style's bottom padding. If no value is set 0 is
+// returned.
+func (s Style) GetPaddingBottom() int {
+ return s.getAsInt(paddingBottomKey)
+}
+
+// GetPaddingLeft returns the style's left padding. If no value is set 0 is
+// returned.
+func (s Style) GetPaddingLeft() int {
+ return s.getAsInt(paddingLeftKey)
+}
+
+// GetHorizontalPadding returns the style's left and right padding. Unset
+// values are measured as 0.
+func (s Style) GetHorizontalPadding() int {
+ return s.getAsInt(paddingLeftKey) + s.getAsInt(paddingRightKey)
+}
+
+// GetVerticalPadding returns the style's top and bottom padding. Unset values
+// are measured as 0.
+func (s Style) GetVerticalPadding() int {
+ return s.getAsInt(paddingTopKey) + s.getAsInt(paddingBottomKey)
+}
+
+// GetColorWhitespace returns the style's whitespace coloring setting. If no
+// value is set false is returned.
+func (s Style) GetColorWhitespace() bool {
+ return s.getAsBool(colorWhitespaceKey, false)
+}
+
+// GetMargin returns the style's top, right, bottom, and left margins, in that
+// order. 0 is returned for unset values.
+func (s Style) GetMargin() (top, right, bottom, left int) {
+ return s.getAsInt(marginTopKey),
+ s.getAsInt(marginRightKey),
+ s.getAsInt(marginBottomKey),
+ s.getAsInt(marginLeftKey)
+}
+
+// GetMarginTop returns the style's top margin. If no value is set 0 is
+// returned.
+func (s Style) GetMarginTop() int {
+ return s.getAsInt(marginTopKey)
+}
+
+// GetMarginRight returns the style's right margin. If no value is set 0 is
+// returned.
+func (s Style) GetMarginRight() int {
+ return s.getAsInt(marginRightKey)
+}
+
+// GetMarginBottom returns the style's bottom margin. If no value is set 0 is
+// returned.
+func (s Style) GetMarginBottom() int {
+ return s.getAsInt(marginBottomKey)
+}
+
+// GetMarginLeft returns the style's left margin. If no value is set 0 is
+// returned.
+func (s Style) GetMarginLeft() int {
+ return s.getAsInt(marginLeftKey)
+}
+
+// GetHorizontalMargins returns the style's left and right margins. Unset
+// values are measured as 0.
+func (s Style) GetHorizontalMargins() int {
+ return s.getAsInt(marginLeftKey) + s.getAsInt(marginRightKey)
+}
+
+// GetVerticalMargins returns the style's top and bottom margins. Unset values
+// are measured as 0.
+func (s Style) GetVerticalMargins() int {
+ return s.getAsInt(marginTopKey) + s.getAsInt(marginBottomKey)
+}
+
+// GetBorder returns the style's border style (type Border) and value for the
+// top, right, bottom, and left in that order. If no value is set for the
+// border style, Border{} is returned. For all other unset values false is
+// returned.
+func (s Style) GetBorder() (b Border, top, right, bottom, left bool) {
+ return s.getBorderStyle(),
+ s.getAsBool(borderTopKey, false),
+ s.getAsBool(borderRightKey, false),
+ s.getAsBool(borderBottomKey, false),
+ s.getAsBool(borderLeftKey, false)
+}
+
+// GetBorderStyle returns the style's border style (type Border). If no value
+// is set Border{} is returned.
+func (s Style) GetBorderStyle() Border {
+ return s.getBorderStyle()
+}
+
+// GetBorderTop returns the style's top border setting. If no value is set
+// false is returned.
+func (s Style) GetBorderTop() bool {
+ return s.getAsBool(borderTopKey, false)
+}
+
+// GetBorderRight returns the style's right border setting. If no value is set
+// false is returned.
+func (s Style) GetBorderRight() bool {
+ return s.getAsBool(borderRightKey, false)
+}
+
+// GetBorderBottom returns the style's bottom border setting. If no value is
+// set false is returned.
+func (s Style) GetBorderBottom() bool {
+ return s.getAsBool(borderBottomKey, false)
+}
+
+// GetBorderLeft returns the style's left border setting. If no value is
+// set false is returned.
+func (s Style) GetBorderLeft() bool {
+ return s.getAsBool(borderLeftKey, false)
+}
+
+// GetBorderTopForeground returns the style's border top foreground color. If
+// no value is set NoColor{} is returned.
+func (s Style) GetBorderTopForeground() TerminalColor {
+ return s.getAsColor(borderTopForegroundKey)
+}
+
+// GetBorderRightForeground returns the style's border right foreground color.
+// If no value is set NoColor{} is returned.
+func (s Style) GetBorderRightForeground() TerminalColor {
+ return s.getAsColor(borderRightForegroundKey)
+}
+
+// GetBorderBottomForeground returns the style's border bottom foreground
+// color. If no value is set NoColor{} is returned.
+func (s Style) GetBorderBottomForeground() TerminalColor {
+ return s.getAsColor(borderBottomForegroundKey)
+}
+
+// GetBorderLeftForeground returns the style's border left foreground
+// color. If no value is set NoColor{} is returned.
+func (s Style) GetBorderLeftForeground() TerminalColor {
+ return s.getAsColor(borderLeftForegroundKey)
+}
+
+// GetBorderTopBackground returns the style's border top background color. If
+// no value is set NoColor{} is returned.
+func (s Style) GetBorderTopBackground() TerminalColor {
+ return s.getAsColor(borderTopBackgroundKey)
+}
+
+// GetBorderRightBackground returns the style's border right background color.
+// If no value is set NoColor{} is returned.
+func (s Style) GetBorderRightBackground() TerminalColor {
+ return s.getAsColor(borderRightBackgroundKey)
+}
+
+// GetBorderBottomBackground returns the style's border bottom background
+// color. If no value is set NoColor{} is returned.
+func (s Style) GetBorderBottomBackground() TerminalColor {
+ return s.getAsColor(borderBottomBackgroundKey)
+}
+
+// GetBorderLeftBackground returns the style's border left background
+// color. If no value is set NoColor{} is returned.
+func (s Style) GetBorderLeftBackground() TerminalColor {
+ return s.getAsColor(borderLeftBackgroundKey)
+}
+
+// GetBorderTopWidth returns the width of the top border. If borders contain
+// runes of varying widths, the widest rune is returned. If no border exists on
+// the top edge, 0 is returned.
+//
+// Deprecated: This function simply calls Style.GetBorderTopSize.
+func (s Style) GetBorderTopWidth() int {
+ return s.GetBorderTopSize()
+}
+
+// GetBorderTopSize returns the width of the top border. If borders contain
+// runes of varying widths, the widest rune is returned. If no border exists on
+// the top edge, 0 is returned.
+func (s Style) GetBorderTopSize() int {
+ if !s.getAsBool(borderTopKey, false) && !s.implicitBorders() {
+ return 0
+ }
+ return s.getBorderStyle().GetTopSize()
+}
+
+// GetBorderLeftSize returns the width of the left border. If borders contain
+// runes of varying widths, the widest rune is returned. If no border exists on
+// the left edge, 0 is returned.
+func (s Style) GetBorderLeftSize() int {
+ if !s.getAsBool(borderLeftKey, false) && !s.implicitBorders() {
+ return 0
+ }
+ return s.getBorderStyle().GetLeftSize()
+}
+
+// GetBorderBottomSize returns the width of the bottom border. If borders
+// contain runes of varying widths, the widest rune is returned. If no border
+// exists on the left edge, 0 is returned.
+func (s Style) GetBorderBottomSize() int {
+ if !s.getAsBool(borderBottomKey, false) && !s.implicitBorders() {
+ return 0
+ }
+ return s.getBorderStyle().GetBottomSize()
+}
+
+// GetBorderRightSize returns the width of the right border. If borders
+// contain runes of varying widths, the widest rune is returned. If no border
+// exists on the right edge, 0 is returned.
+func (s Style) GetBorderRightSize() int {
+ if !s.getAsBool(borderRightKey, false) && !s.implicitBorders() {
+ return 0
+ }
+ return s.getBorderStyle().GetRightSize()
+}
+
+// GetHorizontalBorderSize returns the width of the horizontal borders. If
+// borders contain runes of varying widths, the widest rune is returned. If no
+// border exists on the horizontal edges, 0 is returned.
+func (s Style) GetHorizontalBorderSize() int {
+ return s.GetBorderLeftSize() + s.GetBorderRightSize()
+}
+
+// GetVerticalBorderSize returns the width of the vertical borders. If
+// borders contain runes of varying widths, the widest rune is returned. If no
+// border exists on the vertical edges, 0 is returned.
+func (s Style) GetVerticalBorderSize() int {
+ return s.GetBorderTopSize() + s.GetBorderBottomSize()
+}
+
+// GetInline returns the style's inline setting. If no value is set false is
+// returned.
+func (s Style) GetInline() bool {
+ return s.getAsBool(inlineKey, false)
+}
+
+// GetMaxWidth returns the style's max width setting. If no value is set 0 is
+// returned.
+func (s Style) GetMaxWidth() int {
+ return s.getAsInt(maxWidthKey)
+}
+
+// GetMaxHeight returns the style's max height setting. If no value is set 0 is
+// returned.
+func (s Style) GetMaxHeight() int {
+ return s.getAsInt(maxHeightKey)
+}
+
+// GetTabWidth returns the style's tab width setting. If no value is set 4 is
+// returned which is the implicit default.
+func (s Style) GetTabWidth() int {
+ return s.getAsInt(tabWidthKey)
+}
+
+// GetUnderlineSpaces returns whether or not the style is set to underline
+// spaces. If not value is set false is returned.
+func (s Style) GetUnderlineSpaces() bool {
+ return s.getAsBool(underlineSpacesKey, false)
+}
+
+// GetStrikethroughSpaces returns whether or not the style is set to strikethrough
+// spaces. If not value is set false is returned.
+func (s Style) GetStrikethroughSpaces() bool {
+ return s.getAsBool(strikethroughSpacesKey, false)
+}
+
+// GetHorizontalFrameSize returns the sum of the style's horizontal margins, padding
+// and border widths.
+//
+// Provisional: this method may be renamed.
+func (s Style) GetHorizontalFrameSize() int {
+ return s.GetHorizontalMargins() + s.GetHorizontalPadding() + s.GetHorizontalBorderSize()
+}
+
+// GetVerticalFrameSize returns the sum of the style's vertical margins, padding
+// and border widths.
+//
+// Provisional: this method may be renamed.
+func (s Style) GetVerticalFrameSize() int {
+ return s.GetVerticalMargins() + s.GetVerticalPadding() + s.GetVerticalBorderSize()
+}
+
+// GetFrameSize returns the sum of the margins, padding and border width for
+// both the horizontal and vertical margins.
+func (s Style) GetFrameSize() (x, y int) {
+ return s.GetHorizontalFrameSize(), s.GetVerticalFrameSize()
+}
+
+// GetTransform returns the transform set on the style. If no transform is set
+// nil is returned.
+func (s Style) GetTransform() func(string) string {
+ return s.getAsTransform(transformKey)
+}
+
+// Returns whether or not the given property is set.
+func (s Style) isSet(k propKey) bool {
+ return s.props.has(k)
+}
+
+func (s Style) getAsBool(k propKey, defaultVal bool) bool {
+ if !s.isSet(k) {
+ return defaultVal
+ }
+ return s.attrs&int(k) != 0
+}
+
+func (s Style) getAsColor(k propKey) TerminalColor {
+ if !s.isSet(k) {
+ return noColor
+ }
+
+ var c TerminalColor
+ switch k { //nolint:exhaustive
+ case foregroundKey:
+ c = s.fgColor
+ case backgroundKey:
+ c = s.bgColor
+ case marginBackgroundKey:
+ c = s.marginBgColor
+ case borderTopForegroundKey:
+ c = s.borderTopFgColor
+ case borderRightForegroundKey:
+ c = s.borderRightFgColor
+ case borderBottomForegroundKey:
+ c = s.borderBottomFgColor
+ case borderLeftForegroundKey:
+ c = s.borderLeftFgColor
+ case borderTopBackgroundKey:
+ c = s.borderTopBgColor
+ case borderRightBackgroundKey:
+ c = s.borderRightBgColor
+ case borderBottomBackgroundKey:
+ c = s.borderBottomBgColor
+ case borderLeftBackgroundKey:
+ c = s.borderLeftBgColor
+ }
+
+ if c != nil {
+ return c
+ }
+
+ return noColor
+}
+
+func (s Style) getAsInt(k propKey) int {
+ if !s.isSet(k) {
+ return 0
+ }
+ switch k { //nolint:exhaustive
+ case widthKey:
+ return s.width
+ case heightKey:
+ return s.height
+ case paddingTopKey:
+ return s.paddingTop
+ case paddingRightKey:
+ return s.paddingRight
+ case paddingBottomKey:
+ return s.paddingBottom
+ case paddingLeftKey:
+ return s.paddingLeft
+ case marginTopKey:
+ return s.marginTop
+ case marginRightKey:
+ return s.marginRight
+ case marginBottomKey:
+ return s.marginBottom
+ case marginLeftKey:
+ return s.marginLeft
+ case maxWidthKey:
+ return s.maxWidth
+ case maxHeightKey:
+ return s.maxHeight
+ case tabWidthKey:
+ return s.tabWidth
+ }
+ return 0
+}
+
+func (s Style) getAsPosition(k propKey) Position {
+ if !s.isSet(k) {
+ return Position(0)
+ }
+ switch k { //nolint:exhaustive
+ case alignHorizontalKey:
+ return s.alignHorizontal
+ case alignVerticalKey:
+ return s.alignVertical
+ }
+ return Position(0)
+}
+
+func (s Style) getBorderStyle() Border {
+ if !s.isSet(borderStyleKey) {
+ return noBorder
+ }
+ return s.borderStyle
+}
+
+// Returns whether or not the style has implicit borders. This happens when
+// a border style has been set but no border sides have been explicitly turned
+// on or off.
+func (s Style) implicitBorders() bool {
+ var (
+ borderStyle = s.getBorderStyle()
+ topSet = s.isSet(borderTopKey)
+ rightSet = s.isSet(borderRightKey)
+ bottomSet = s.isSet(borderBottomKey)
+ leftSet = s.isSet(borderLeftKey)
+ )
+ return borderStyle != noBorder && !(topSet || rightSet || bottomSet || leftSet) //nolint:staticcheck
+}
+
+func (s Style) getAsTransform(propKey) func(string) string {
+ if !s.isSet(transformKey) {
+ return nil
+ }
+ return s.transform
+}
+
+// Split a string into lines, additionally returning the size of the widest
+// line.
+func getLines(s string) (lines []string, widest int) {
+ lines = strings.Split(s, "\n")
+
+ for _, l := range lines {
+ w := ansi.StringWidth(l)
+ if widest < w {
+ widest = w
+ }
+ }
+
+ return lines, widest
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/join.go b/vendor/github.com/charmbracelet/lipgloss/join.go
new file mode 100644
index 00000000..3e6688e2
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/join.go
@@ -0,0 +1,175 @@
+package lipgloss
+
+import (
+ "math"
+ "strings"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+// JoinHorizontal is a utility function for horizontally joining two
+// potentially multi-lined strings along a vertical axis. The first argument is
+// the position, with 0 being all the way at the top and 1 being all the way
+// at the bottom.
+//
+// If you just want to align to the top, center or bottom you may as well just
+// use the helper constants Top, Center, and Bottom.
+//
+// Example:
+//
+// blockB := "...\n...\n..."
+// blockA := "...\n...\n...\n...\n..."
+//
+// // Join 20% from the top
+// str := lipgloss.JoinHorizontal(0.2, blockA, blockB)
+//
+// // Join on the top edge
+// str := lipgloss.JoinHorizontal(lipgloss.Top, blockA, blockB)
+func JoinHorizontal(pos Position, strs ...string) string {
+ if len(strs) == 0 {
+ return ""
+ }
+ if len(strs) == 1 {
+ return strs[0]
+ }
+
+ var (
+ // Groups of strings broken into multiple lines
+ blocks = make([][]string, len(strs))
+
+ // Max line widths for the above text blocks
+ maxWidths = make([]int, len(strs))
+
+ // Height of the tallest block
+ maxHeight int
+ )
+
+ // Break text blocks into lines and get max widths for each text block
+ for i, str := range strs {
+ blocks[i], maxWidths[i] = getLines(str)
+ if len(blocks[i]) > maxHeight {
+ maxHeight = len(blocks[i])
+ }
+ }
+
+ // Add extra lines to make each side the same height
+ for i := range blocks {
+ if len(blocks[i]) >= maxHeight {
+ continue
+ }
+
+ extraLines := make([]string, maxHeight-len(blocks[i]))
+
+ switch pos { //nolint:exhaustive
+ case Top:
+ blocks[i] = append(blocks[i], extraLines...)
+
+ case Bottom:
+ blocks[i] = append(extraLines, blocks[i]...)
+
+ default: // Somewhere in the middle
+ n := len(extraLines)
+ split := int(math.Round(float64(n) * pos.value()))
+ top := n - split
+ bottom := n - top
+
+ blocks[i] = append(extraLines[top:], blocks[i]...)
+ blocks[i] = append(blocks[i], extraLines[bottom:]...)
+ }
+ }
+
+ // Merge lines
+ var b strings.Builder
+ for i := range blocks[0] { // remember, all blocks have the same number of members now
+ for j, block := range blocks {
+ b.WriteString(block[i])
+
+ // Also make lines the same length
+ b.WriteString(strings.Repeat(" ", maxWidths[j]-ansi.StringWidth(block[i])))
+ }
+ if i < len(blocks[0])-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ return b.String()
+}
+
+// JoinVertical is a utility function for vertically joining two potentially
+// multi-lined strings along a horizontal axis. The first argument is the
+// position, with 0 being all the way to the left and 1 being all the way to
+// the right.
+//
+// If you just want to align to the left, right or center you may as well just
+// use the helper constants Left, Center, and Right.
+//
+// Example:
+//
+// blockB := "...\n...\n..."
+// blockA := "...\n...\n...\n...\n..."
+//
+// // Join 20% from the top
+// str := lipgloss.JoinVertical(0.2, blockA, blockB)
+//
+// // Join on the right edge
+// str := lipgloss.JoinVertical(lipgloss.Right, blockA, blockB)
+func JoinVertical(pos Position, strs ...string) string {
+ if len(strs) == 0 {
+ return ""
+ }
+ if len(strs) == 1 {
+ return strs[0]
+ }
+
+ var (
+ blocks = make([][]string, len(strs))
+ maxWidth int
+ )
+
+ for i := range strs {
+ var w int
+ blocks[i], w = getLines(strs[i])
+ if w > maxWidth {
+ maxWidth = w
+ }
+ }
+
+ var b strings.Builder
+ for i, block := range blocks {
+ for j, line := range block {
+ w := maxWidth - ansi.StringWidth(line)
+
+ switch pos { //nolint:exhaustive
+ case Left:
+ b.WriteString(line)
+ b.WriteString(strings.Repeat(" ", w))
+
+ case Right:
+ b.WriteString(strings.Repeat(" ", w))
+ b.WriteString(line)
+
+ default: // Somewhere in the middle
+ if w < 1 {
+ b.WriteString(line)
+ break
+ }
+
+ split := int(math.Round(float64(w) * pos.value()))
+ right := w - split
+ left := w - right
+
+ b.WriteString(strings.Repeat(" ", left))
+ b.WriteString(line)
+ b.WriteString(strings.Repeat(" ", right))
+ }
+
+ // Write a newline as long as we're not on the last line of the
+ // last block.
+ if !(i == len(blocks)-1 && j == len(block)-1) { //nolint:staticcheck
+ b.WriteRune('\n')
+ }
+ }
+ }
+
+ return b.String()
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/lipgloss.go b/vendor/github.com/charmbracelet/lipgloss/lipgloss.go
new file mode 100644
index 00000000..3fb4e6f2
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/lipgloss.go
@@ -0,0 +1,3 @@
+// Package lipgloss provides style definitions for nice terminal layouts. Built
+// with TUIs in mind.
+package lipgloss
diff --git a/vendor/github.com/charmbracelet/lipgloss/position.go b/vendor/github.com/charmbracelet/lipgloss/position.go
new file mode 100644
index 00000000..185f5af3
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/position.go
@@ -0,0 +1,154 @@
+package lipgloss
+
+import (
+ "math"
+ "strings"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+// Position represents a position along a horizontal or vertical axis. It's in
+// situations where an axis is involved, like alignment, joining, placement and
+// so on.
+//
+// A value of 0 represents the start (the left or top) and 1 represents the end
+// (the right or bottom). 0.5 represents the center.
+//
+// There are constants Top, Bottom, Center, Left and Right in this package that
+// can be used to aid readability.
+type Position float64
+
+func (p Position) value() float64 {
+ return math.Min(1, math.Max(0, float64(p)))
+}
+
+// Position aliases.
+const (
+ Top Position = 0.0
+ Bottom Position = 1.0
+ Center Position = 0.5
+ Left Position = 0.0
+ Right Position = 1.0
+)
+
+// Place places a string or text block vertically in an unstyled box of a given
+// width or height.
+func Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
+ return renderer.Place(width, height, hPos, vPos, str, opts...)
+}
+
+// Place places a string or text block vertically in an unstyled box of a given
+// width or height.
+func (r *Renderer) Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
+ return r.PlaceVertical(height, vPos, r.PlaceHorizontal(width, hPos, str, opts...), opts...)
+}
+
+// PlaceHorizontal places a string or text block horizontally in an unstyled
+// block of a given width. If the given width is shorter than the max width of
+// the string (measured by its longest line) this will be a noop.
+func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
+ return renderer.PlaceHorizontal(width, pos, str, opts...)
+}
+
+// PlaceHorizontal places a string or text block horizontally in an unstyled
+// block of a given width. If the given width is shorter than the max width of
+// the string (measured by its longest line) this will be a noöp.
+func (r *Renderer) PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
+ lines, contentWidth := getLines(str)
+ gap := width - contentWidth
+
+ if gap <= 0 {
+ return str
+ }
+
+ ws := newWhitespace(r, opts...)
+
+ var b strings.Builder
+ for i, l := range lines {
+ // Is this line shorter than the longest line?
+ short := max(0, contentWidth-ansi.StringWidth(l))
+
+ switch pos { //nolint:exhaustive
+ case Left:
+ b.WriteString(l)
+ b.WriteString(ws.render(gap + short))
+
+ case Right:
+ b.WriteString(ws.render(gap + short))
+ b.WriteString(l)
+
+ default: // somewhere in the middle
+ totalGap := gap + short
+
+ split := int(math.Round(float64(totalGap) * pos.value()))
+ left := totalGap - split
+ right := totalGap - left
+
+ b.WriteString(ws.render(left))
+ b.WriteString(l)
+ b.WriteString(ws.render(right))
+ }
+
+ if i < len(lines)-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ return b.String()
+}
+
+// PlaceVertical places a string or text block vertically in an unstyled block
+// of a given height. If the given height is shorter than the height of the
+// string (measured by its newlines) then this will be a noop.
+func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
+ return renderer.PlaceVertical(height, pos, str, opts...)
+}
+
+// PlaceVertical places a string or text block vertically in an unstyled block
+// of a given height. If the given height is shorter than the height of the
+// string (measured by its newlines) then this will be a noöp.
+func (r *Renderer) PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
+ contentHeight := strings.Count(str, "\n") + 1
+ gap := height - contentHeight
+
+ if gap <= 0 {
+ return str
+ }
+
+ ws := newWhitespace(r, opts...)
+
+ _, width := getLines(str)
+ emptyLine := ws.render(width)
+ b := strings.Builder{}
+
+ switch pos { //nolint:exhaustive
+ case Top:
+ b.WriteString(str)
+ b.WriteRune('\n')
+ for i := 0; i < gap; i++ {
+ b.WriteString(emptyLine)
+ if i < gap-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ case Bottom:
+ b.WriteString(strings.Repeat(emptyLine+"\n", gap))
+ b.WriteString(str)
+
+ default: // Somewhere in the middle
+ split := int(math.Round(float64(gap) * pos.value()))
+ top := gap - split
+ bottom := gap - top
+
+ b.WriteString(strings.Repeat(emptyLine+"\n", top))
+ b.WriteString(str)
+
+ for i := 0; i < bottom; i++ {
+ b.WriteRune('\n')
+ b.WriteString(emptyLine)
+ }
+ }
+
+ return b.String()
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/ranges.go b/vendor/github.com/charmbracelet/lipgloss/ranges.go
new file mode 100644
index 00000000..d1716998
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/ranges.go
@@ -0,0 +1,48 @@
+package lipgloss
+
+import (
+ "strings"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+// StyleRanges allows to, given a string, style ranges of it differently.
+// The function will take into account existing styles.
+// Ranges should not overlap.
+func StyleRanges(s string, ranges ...Range) string {
+ if len(ranges) == 0 {
+ return s
+ }
+
+ var buf strings.Builder
+ lastIdx := 0
+ stripped := ansi.Strip(s)
+
+ // Use Truncate and TruncateLeft to style match.MatchedIndexes without
+ // losing the original option style:
+ for _, rng := range ranges {
+ // Add the text before this match
+ if rng.Start > lastIdx {
+ buf.WriteString(ansi.Cut(s, lastIdx, rng.Start))
+ }
+ // Add the matched range with its highlight
+ buf.WriteString(rng.Style.Render(ansi.Cut(stripped, rng.Start, rng.End)))
+ lastIdx = rng.End
+ }
+
+ // Add any remaining text after the last match
+ buf.WriteString(ansi.TruncateLeft(s, lastIdx, ""))
+
+ return buf.String()
+}
+
+// NewRange returns a range that can be used with [StyleRanges].
+func NewRange(start, end int, style Style) Range {
+ return Range{start, end, style}
+}
+
+// Range to be used with [StyleRanges].
+type Range struct {
+ Start, End int
+ Style Style
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/renderer.go b/vendor/github.com/charmbracelet/lipgloss/renderer.go
new file mode 100644
index 00000000..233aa7c0
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/renderer.go
@@ -0,0 +1,181 @@
+package lipgloss
+
+import (
+ "io"
+ "sync"
+
+ "github.com/muesli/termenv"
+)
+
+// We're manually creating the struct here to avoid initializing the output and
+// query the terminal multiple times.
+var renderer = &Renderer{
+ output: termenv.DefaultOutput(),
+}
+
+// Renderer is a lipgloss terminal renderer.
+type Renderer struct {
+ output *termenv.Output
+ colorProfile termenv.Profile
+ hasDarkBackground bool
+
+ getColorProfile sync.Once
+ explicitColorProfile bool
+
+ getBackgroundColor sync.Once
+ explicitBackgroundColor bool
+
+ mtx sync.RWMutex
+}
+
+// DefaultRenderer returns the default renderer.
+func DefaultRenderer() *Renderer {
+ return renderer
+}
+
+// SetDefaultRenderer sets the default global renderer.
+func SetDefaultRenderer(r *Renderer) {
+ renderer = r
+}
+
+// NewRenderer creates a new Renderer.
+//
+// w will be used to determine the terminal's color capabilities.
+func NewRenderer(w io.Writer, opts ...termenv.OutputOption) *Renderer {
+ r := &Renderer{
+ output: termenv.NewOutput(w, opts...),
+ }
+ return r
+}
+
+// Output returns the termenv output.
+func (r *Renderer) Output() *termenv.Output {
+ r.mtx.RLock()
+ defer r.mtx.RUnlock()
+ return r.output
+}
+
+// SetOutput sets the termenv output.
+func (r *Renderer) SetOutput(o *termenv.Output) {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+ r.output = o
+}
+
+// ColorProfile returns the detected termenv color profile.
+func (r *Renderer) ColorProfile() termenv.Profile {
+ r.mtx.RLock()
+ defer r.mtx.RUnlock()
+
+ if !r.explicitColorProfile {
+ r.getColorProfile.Do(func() {
+ // NOTE: we don't need to lock here because sync.Once provides its
+ // own locking mechanism.
+ r.colorProfile = r.output.EnvColorProfile()
+ })
+ }
+
+ return r.colorProfile
+}
+
+// ColorProfile returns the detected termenv color profile.
+func ColorProfile() termenv.Profile {
+ return renderer.ColorProfile()
+}
+
+// SetColorProfile sets the color profile on the renderer. This function exists
+// mostly for testing purposes so that you can assure you're testing against
+// a specific profile.
+//
+// Outside of testing you likely won't want to use this function as the color
+// profile will detect and cache the terminal's color capabilities and choose
+// the best available profile.
+//
+// Available color profiles are:
+//
+// termenv.Ascii // no color, 1-bit
+// termenv.ANSI //16 colors, 4-bit
+// termenv.ANSI256 // 256 colors, 8-bit
+// termenv.TrueColor // 16,777,216 colors, 24-bit
+//
+// This function is thread-safe.
+func (r *Renderer) SetColorProfile(p termenv.Profile) {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.colorProfile = p
+ r.explicitColorProfile = true
+}
+
+// SetColorProfile sets the color profile on the default renderer. This
+// function exists mostly for testing purposes so that you can assure you're
+// testing against a specific profile.
+//
+// Outside of testing you likely won't want to use this function as the color
+// profile will detect and cache the terminal's color capabilities and choose
+// the best available profile.
+//
+// Available color profiles are:
+//
+// termenv.Ascii // no color, 1-bit
+// termenv.ANSI //16 colors, 4-bit
+// termenv.ANSI256 // 256 colors, 8-bit
+// termenv.TrueColor // 16,777,216 colors, 24-bit
+//
+// This function is thread-safe.
+func SetColorProfile(p termenv.Profile) {
+ renderer.SetColorProfile(p)
+}
+
+// HasDarkBackground returns whether or not the terminal has a dark background.
+func HasDarkBackground() bool {
+ return renderer.HasDarkBackground()
+}
+
+// HasDarkBackground returns whether or not the renderer will render to a dark
+// background. A dark background can either be auto-detected, or set explicitly
+// on the renderer.
+func (r *Renderer) HasDarkBackground() bool {
+ r.mtx.RLock()
+ defer r.mtx.RUnlock()
+
+ if !r.explicitBackgroundColor {
+ r.getBackgroundColor.Do(func() {
+ // NOTE: we don't need to lock here because sync.Once provides its
+ // own locking mechanism.
+ r.hasDarkBackground = r.output.HasDarkBackground()
+ })
+ }
+
+ return r.hasDarkBackground
+}
+
+// SetHasDarkBackground sets the background color detection value for the
+// default renderer. This function exists mostly for testing purposes so that
+// you can assure you're testing against a specific background color setting.
+//
+// Outside of testing you likely won't want to use this function as the
+// backgrounds value will be automatically detected and cached against the
+// terminal's current background color setting.
+//
+// This function is thread-safe.
+func SetHasDarkBackground(b bool) {
+ renderer.SetHasDarkBackground(b)
+}
+
+// SetHasDarkBackground sets the background color detection value on the
+// renderer. This function exists mostly for testing purposes so that you can
+// assure you're testing against a specific background color setting.
+//
+// Outside of testing you likely won't want to use this function as the
+// backgrounds value will be automatically detected and cached against the
+// terminal's current background color setting.
+//
+// This function is thread-safe.
+func (r *Renderer) SetHasDarkBackground(b bool) {
+ r.mtx.Lock()
+ defer r.mtx.Unlock()
+
+ r.hasDarkBackground = b
+ r.explicitBackgroundColor = true
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/runes.go b/vendor/github.com/charmbracelet/lipgloss/runes.go
new file mode 100644
index 00000000..7a49e326
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/runes.go
@@ -0,0 +1,43 @@
+package lipgloss
+
+import (
+ "strings"
+)
+
+// StyleRunes apply a given style to runes at the given indices in the string.
+// Note that you must provide styling options for both matched and unmatched
+// runes. Indices out of bounds will be ignored.
+func StyleRunes(str string, indices []int, matched, unmatched Style) string {
+ // Convert slice of indices to a map for easier lookups
+ m := make(map[int]struct{})
+ for _, i := range indices {
+ m[i] = struct{}{}
+ }
+
+ var (
+ out strings.Builder
+ group strings.Builder
+ style Style
+ runes = []rune(str)
+ )
+
+ for i, r := range runes {
+ group.WriteRune(r)
+
+ _, matches := m[i]
+ _, nextMatches := m[i+1]
+
+ if matches != nextMatches || i == len(runes)-1 {
+ // Flush
+ if matches {
+ style = matched
+ } else {
+ style = unmatched
+ }
+ out.WriteString(style.Render(group.String()))
+ group.Reset()
+ }
+ }
+
+ return out.String()
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/set.go b/vendor/github.com/charmbracelet/lipgloss/set.go
new file mode 100644
index 00000000..fde38fae
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/set.go
@@ -0,0 +1,799 @@
+package lipgloss
+
+// Set a value on the underlying rules map.
+func (s *Style) set(key propKey, value interface{}) {
+ // We don't allow negative integers on any of our other values, so just keep
+ // them at zero or above. We could use uints instead, but the
+ // conversions are a little tedious, so we're sticking with ints for
+ // sake of usability.
+ switch key { //nolint:exhaustive
+ case foregroundKey:
+ s.fgColor = colorOrNil(value)
+ case backgroundKey:
+ s.bgColor = colorOrNil(value)
+ case widthKey:
+ s.width = max(0, value.(int))
+ case heightKey:
+ s.height = max(0, value.(int))
+ case alignHorizontalKey:
+ s.alignHorizontal = value.(Position)
+ case alignVerticalKey:
+ s.alignVertical = value.(Position)
+ case paddingTopKey:
+ s.paddingTop = max(0, value.(int))
+ case paddingRightKey:
+ s.paddingRight = max(0, value.(int))
+ case paddingBottomKey:
+ s.paddingBottom = max(0, value.(int))
+ case paddingLeftKey:
+ s.paddingLeft = max(0, value.(int))
+ case marginTopKey:
+ s.marginTop = max(0, value.(int))
+ case marginRightKey:
+ s.marginRight = max(0, value.(int))
+ case marginBottomKey:
+ s.marginBottom = max(0, value.(int))
+ case marginLeftKey:
+ s.marginLeft = max(0, value.(int))
+ case marginBackgroundKey:
+ s.marginBgColor = colorOrNil(value)
+ case borderStyleKey:
+ s.borderStyle = value.(Border)
+ case borderTopForegroundKey:
+ s.borderTopFgColor = colorOrNil(value)
+ case borderRightForegroundKey:
+ s.borderRightFgColor = colorOrNil(value)
+ case borderBottomForegroundKey:
+ s.borderBottomFgColor = colorOrNil(value)
+ case borderLeftForegroundKey:
+ s.borderLeftFgColor = colorOrNil(value)
+ case borderTopBackgroundKey:
+ s.borderTopBgColor = colorOrNil(value)
+ case borderRightBackgroundKey:
+ s.borderRightBgColor = colorOrNil(value)
+ case borderBottomBackgroundKey:
+ s.borderBottomBgColor = colorOrNil(value)
+ case borderLeftBackgroundKey:
+ s.borderLeftBgColor = colorOrNil(value)
+ case maxWidthKey:
+ s.maxWidth = max(0, value.(int))
+ case maxHeightKey:
+ s.maxHeight = max(0, value.(int))
+ case tabWidthKey:
+ // TabWidth is the only property that may have a negative value (and
+ // that negative value can be no less than -1).
+ s.tabWidth = value.(int)
+ case transformKey:
+ s.transform = value.(func(string) string)
+ default:
+ if v, ok := value.(bool); ok { //nolint:nestif
+ if v {
+ s.attrs |= int(key)
+ } else {
+ s.attrs &^= int(key)
+ }
+ } else if attrs, ok := value.(int); ok {
+ // bool attrs
+ if attrs&int(key) != 0 {
+ s.attrs |= int(key)
+ } else {
+ s.attrs &^= int(key)
+ }
+ }
+ }
+
+ // Set the prop on
+ s.props = s.props.set(key)
+}
+
+// setFrom sets the property from another style.
+func (s *Style) setFrom(key propKey, i Style) {
+ switch key { //nolint:exhaustive
+ case foregroundKey:
+ s.set(foregroundKey, i.fgColor)
+ case backgroundKey:
+ s.set(backgroundKey, i.bgColor)
+ case widthKey:
+ s.set(widthKey, i.width)
+ case heightKey:
+ s.set(heightKey, i.height)
+ case alignHorizontalKey:
+ s.set(alignHorizontalKey, i.alignHorizontal)
+ case alignVerticalKey:
+ s.set(alignVerticalKey, i.alignVertical)
+ case paddingTopKey:
+ s.set(paddingTopKey, i.paddingTop)
+ case paddingRightKey:
+ s.set(paddingRightKey, i.paddingRight)
+ case paddingBottomKey:
+ s.set(paddingBottomKey, i.paddingBottom)
+ case paddingLeftKey:
+ s.set(paddingLeftKey, i.paddingLeft)
+ case marginTopKey:
+ s.set(marginTopKey, i.marginTop)
+ case marginRightKey:
+ s.set(marginRightKey, i.marginRight)
+ case marginBottomKey:
+ s.set(marginBottomKey, i.marginBottom)
+ case marginLeftKey:
+ s.set(marginLeftKey, i.marginLeft)
+ case marginBackgroundKey:
+ s.set(marginBackgroundKey, i.marginBgColor)
+ case borderStyleKey:
+ s.set(borderStyleKey, i.borderStyle)
+ case borderTopForegroundKey:
+ s.set(borderTopForegroundKey, i.borderTopFgColor)
+ case borderRightForegroundKey:
+ s.set(borderRightForegroundKey, i.borderRightFgColor)
+ case borderBottomForegroundKey:
+ s.set(borderBottomForegroundKey, i.borderBottomFgColor)
+ case borderLeftForegroundKey:
+ s.set(borderLeftForegroundKey, i.borderLeftFgColor)
+ case borderTopBackgroundKey:
+ s.set(borderTopBackgroundKey, i.borderTopBgColor)
+ case borderRightBackgroundKey:
+ s.set(borderRightBackgroundKey, i.borderRightBgColor)
+ case borderBottomBackgroundKey:
+ s.set(borderBottomBackgroundKey, i.borderBottomBgColor)
+ case borderLeftBackgroundKey:
+ s.set(borderLeftBackgroundKey, i.borderLeftBgColor)
+ case maxWidthKey:
+ s.set(maxWidthKey, i.maxWidth)
+ case maxHeightKey:
+ s.set(maxHeightKey, i.maxHeight)
+ case tabWidthKey:
+ s.set(tabWidthKey, i.tabWidth)
+ case transformKey:
+ s.set(transformKey, i.transform)
+ default:
+ // Set attributes for set bool properties
+ s.set(key, i.attrs)
+ }
+}
+
+func colorOrNil(c interface{}) TerminalColor {
+ if c, ok := c.(TerminalColor); ok {
+ return c
+ }
+ return nil
+}
+
+// Bold sets a bold formatting rule.
+func (s Style) Bold(v bool) Style {
+ s.set(boldKey, v)
+ return s
+}
+
+// Italic sets an italic formatting rule. In some terminal emulators this will
+// render with "reverse" coloring if not italic font variant is available.
+func (s Style) Italic(v bool) Style {
+ s.set(italicKey, v)
+ return s
+}
+
+// Underline sets an underline rule. By default, underlines will not be drawn on
+// whitespace like margins and padding. To change this behavior set
+// UnderlineSpaces.
+func (s Style) Underline(v bool) Style {
+ s.set(underlineKey, v)
+ return s
+}
+
+// Strikethrough sets a strikethrough rule. By default, strikes will not be
+// drawn on whitespace like margins and padding. To change this behavior set
+// StrikethroughSpaces.
+func (s Style) Strikethrough(v bool) Style {
+ s.set(strikethroughKey, v)
+ return s
+}
+
+// Reverse sets a rule for inverting foreground and background colors.
+func (s Style) Reverse(v bool) Style {
+ s.set(reverseKey, v)
+ return s
+}
+
+// Blink sets a rule for blinking foreground text.
+func (s Style) Blink(v bool) Style {
+ s.set(blinkKey, v)
+ return s
+}
+
+// Faint sets a rule for rendering the foreground color in a dimmer shade.
+func (s Style) Faint(v bool) Style {
+ s.set(faintKey, v)
+ return s
+}
+
+// Foreground sets a foreground color.
+//
+// // Sets the foreground to blue
+// s := lipgloss.NewStyle().Foreground(lipgloss.Color("#0000ff"))
+//
+// // Removes the foreground color
+// s.Foreground(lipgloss.NoColor)
+func (s Style) Foreground(c TerminalColor) Style {
+ s.set(foregroundKey, c)
+ return s
+}
+
+// Background sets a background color.
+func (s Style) Background(c TerminalColor) Style {
+ s.set(backgroundKey, c)
+ return s
+}
+
+// Width sets the width of the block before applying margins. The width, if
+// set, also determines where text will wrap.
+func (s Style) Width(i int) Style {
+ s.set(widthKey, i)
+ return s
+}
+
+// Height sets the height of the block before applying margins. If the height of
+// the text block is less than this value after applying padding (or not), the
+// block will be set to this height.
+func (s Style) Height(i int) Style {
+ s.set(heightKey, i)
+ return s
+}
+
+// Align is a shorthand method for setting horizontal and vertical alignment.
+//
+// With one argument, the position value is applied to the horizontal alignment.
+//
+// With two arguments, the value is applied to the horizontal and vertical
+// alignments, in that order.
+func (s Style) Align(p ...Position) Style {
+ if len(p) > 0 {
+ s.set(alignHorizontalKey, p[0])
+ }
+ if len(p) > 1 {
+ s.set(alignVerticalKey, p[1])
+ }
+ return s
+}
+
+// AlignHorizontal sets a horizontal text alignment rule.
+func (s Style) AlignHorizontal(p Position) Style {
+ s.set(alignHorizontalKey, p)
+ return s
+}
+
+// AlignVertical sets a vertical text alignment rule.
+func (s Style) AlignVertical(p Position) Style {
+ s.set(alignVerticalKey, p)
+ return s
+}
+
+// Padding is a shorthand method for setting padding on all sides at once.
+//
+// With one argument, the value is applied to all sides.
+//
+// With two arguments, the value is applied to the vertical and horizontal
+// sides, in that order.
+//
+// With three arguments, the value is applied to the top side, the horizontal
+// sides, and the bottom side, in that order.
+//
+// With four arguments, the value is applied clockwise starting from the top
+// side, followed by the right side, then the bottom, and finally the left.
+//
+// With more than four arguments no padding will be added.
+func (s Style) Padding(i ...int) Style {
+ top, right, bottom, left, ok := whichSidesInt(i...)
+ if !ok {
+ return s
+ }
+
+ s.set(paddingTopKey, top)
+ s.set(paddingRightKey, right)
+ s.set(paddingBottomKey, bottom)
+ s.set(paddingLeftKey, left)
+ return s
+}
+
+// PaddingLeft adds padding on the left.
+func (s Style) PaddingLeft(i int) Style {
+ s.set(paddingLeftKey, i)
+ return s
+}
+
+// PaddingRight adds padding on the right.
+func (s Style) PaddingRight(i int) Style {
+ s.set(paddingRightKey, i)
+ return s
+}
+
+// PaddingTop adds padding to the top of the block.
+func (s Style) PaddingTop(i int) Style {
+ s.set(paddingTopKey, i)
+ return s
+}
+
+// PaddingBottom adds padding to the bottom of the block.
+func (s Style) PaddingBottom(i int) Style {
+ s.set(paddingBottomKey, i)
+ return s
+}
+
+// ColorWhitespace determines whether or not the background color should be
+// applied to the padding. This is true by default as it's more than likely the
+// desired and expected behavior, but it can be disabled for certain graphic
+// effects.
+//
+// Deprecated: Just use margins and padding.
+func (s Style) ColorWhitespace(v bool) Style {
+ s.set(colorWhitespaceKey, v)
+ return s
+}
+
+// Margin is a shorthand method for setting margins on all sides at once.
+//
+// With one argument, the value is applied to all sides.
+//
+// With two arguments, the value is applied to the vertical and horizontal
+// sides, in that order.
+//
+// With three arguments, the value is applied to the top side, the horizontal
+// sides, and the bottom side, in that order.
+//
+// With four arguments, the value is applied clockwise starting from the top
+// side, followed by the right side, then the bottom, and finally the left.
+//
+// With more than four arguments no margin will be added.
+func (s Style) Margin(i ...int) Style {
+ top, right, bottom, left, ok := whichSidesInt(i...)
+ if !ok {
+ return s
+ }
+
+ s.set(marginTopKey, top)
+ s.set(marginRightKey, right)
+ s.set(marginBottomKey, bottom)
+ s.set(marginLeftKey, left)
+ return s
+}
+
+// MarginLeft sets the value of the left margin.
+func (s Style) MarginLeft(i int) Style {
+ s.set(marginLeftKey, i)
+ return s
+}
+
+// MarginRight sets the value of the right margin.
+func (s Style) MarginRight(i int) Style {
+ s.set(marginRightKey, i)
+ return s
+}
+
+// MarginTop sets the value of the top margin.
+func (s Style) MarginTop(i int) Style {
+ s.set(marginTopKey, i)
+ return s
+}
+
+// MarginBottom sets the value of the bottom margin.
+func (s Style) MarginBottom(i int) Style {
+ s.set(marginBottomKey, i)
+ return s
+}
+
+// MarginBackground sets the background color of the margin. Note that this is
+// also set when inheriting from a style with a background color. In that case
+// the background color on that style will set the margin color on this style.
+func (s Style) MarginBackground(c TerminalColor) Style {
+ s.set(marginBackgroundKey, c)
+ return s
+}
+
+// Border is shorthand for setting the border style and which sides should
+// have a border at once. The variadic argument sides works as follows:
+//
+// With one value, the value is applied to all sides.
+//
+// With two values, the values are applied to the vertical and horizontal
+// sides, in that order.
+//
+// With three values, the values are applied to the top side, the horizontal
+// sides, and the bottom side, in that order.
+//
+// With four values, the values are applied clockwise starting from the top
+// side, followed by the right side, then the bottom, and finally the left.
+//
+// With more than four arguments the border will be applied to all sides.
+//
+// Examples:
+//
+// // Applies borders to the top and bottom only
+// lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false)
+//
+// // Applies rounded borders to the right and bottom only
+// lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), false, true, true, false)
+func (s Style) Border(b Border, sides ...bool) Style {
+ s.set(borderStyleKey, b)
+
+ top, right, bottom, left, ok := whichSidesBool(sides...)
+ if !ok {
+ top = true
+ right = true
+ bottom = true
+ left = true
+ }
+
+ s.set(borderTopKey, top)
+ s.set(borderRightKey, right)
+ s.set(borderBottomKey, bottom)
+ s.set(borderLeftKey, left)
+
+ return s
+}
+
+// BorderStyle defines the Border on a style. A Border contains a series of
+// definitions for the sides and corners of a border.
+//
+// Note that if border visibility has not been set for any sides when setting
+// the border style, the border will be enabled for all sides during rendering.
+//
+// You can define border characters as you'd like, though several default
+// styles are included: NormalBorder(), RoundedBorder(), BlockBorder(),
+// OuterHalfBlockBorder(), InnerHalfBlockBorder(), ThickBorder(),
+// and DoubleBorder().
+//
+// Example:
+//
+// lipgloss.NewStyle().BorderStyle(lipgloss.ThickBorder())
+func (s Style) BorderStyle(b Border) Style {
+ s.set(borderStyleKey, b)
+ return s
+}
+
+// BorderTop determines whether or not to draw a top border.
+func (s Style) BorderTop(v bool) Style {
+ s.set(borderTopKey, v)
+ return s
+}
+
+// BorderRight determines whether or not to draw a right border.
+func (s Style) BorderRight(v bool) Style {
+ s.set(borderRightKey, v)
+ return s
+}
+
+// BorderBottom determines whether or not to draw a bottom border.
+func (s Style) BorderBottom(v bool) Style {
+ s.set(borderBottomKey, v)
+ return s
+}
+
+// BorderLeft determines whether or not to draw a left border.
+func (s Style) BorderLeft(v bool) Style {
+ s.set(borderLeftKey, v)
+ return s
+}
+
+// BorderForeground is a shorthand function for setting all of the
+// foreground colors of the borders at once. The arguments work as follows:
+//
+// With one argument, the argument is applied to all sides.
+//
+// With two arguments, the arguments are applied to the vertical and horizontal
+// sides, in that order.
+//
+// With three arguments, the arguments are applied to the top side, the
+// horizontal sides, and the bottom side, in that order.
+//
+// With four arguments, the arguments are applied clockwise starting from the
+// top side, followed by the right side, then the bottom, and finally the left.
+//
+// With more than four arguments nothing will be set.
+func (s Style) BorderForeground(c ...TerminalColor) Style {
+ if len(c) == 0 {
+ return s
+ }
+
+ top, right, bottom, left, ok := whichSidesColor(c...)
+ if !ok {
+ return s
+ }
+
+ s.set(borderTopForegroundKey, top)
+ s.set(borderRightForegroundKey, right)
+ s.set(borderBottomForegroundKey, bottom)
+ s.set(borderLeftForegroundKey, left)
+
+ return s
+}
+
+// BorderTopForeground set the foreground color for the top of the border.
+func (s Style) BorderTopForeground(c TerminalColor) Style {
+ s.set(borderTopForegroundKey, c)
+ return s
+}
+
+// BorderRightForeground sets the foreground color for the right side of the
+// border.
+func (s Style) BorderRightForeground(c TerminalColor) Style {
+ s.set(borderRightForegroundKey, c)
+ return s
+}
+
+// BorderBottomForeground sets the foreground color for the bottom of the
+// border.
+func (s Style) BorderBottomForeground(c TerminalColor) Style {
+ s.set(borderBottomForegroundKey, c)
+ return s
+}
+
+// BorderLeftForeground sets the foreground color for the left side of the
+// border.
+func (s Style) BorderLeftForeground(c TerminalColor) Style {
+ s.set(borderLeftForegroundKey, c)
+ return s
+}
+
+// BorderBackground is a shorthand function for setting all of the
+// background colors of the borders at once. The arguments work as follows:
+//
+// With one argument, the argument is applied to all sides.
+//
+// With two arguments, the arguments are applied to the vertical and horizontal
+// sides, in that order.
+//
+// With three arguments, the arguments are applied to the top side, the
+// horizontal sides, and the bottom side, in that order.
+//
+// With four arguments, the arguments are applied clockwise starting from the
+// top side, followed by the right side, then the bottom, and finally the left.
+//
+// With more than four arguments nothing will be set.
+func (s Style) BorderBackground(c ...TerminalColor) Style {
+ if len(c) == 0 {
+ return s
+ }
+
+ top, right, bottom, left, ok := whichSidesColor(c...)
+ if !ok {
+ return s
+ }
+
+ s.set(borderTopBackgroundKey, top)
+ s.set(borderRightBackgroundKey, right)
+ s.set(borderBottomBackgroundKey, bottom)
+ s.set(borderLeftBackgroundKey, left)
+
+ return s
+}
+
+// BorderTopBackground sets the background color of the top of the border.
+func (s Style) BorderTopBackground(c TerminalColor) Style {
+ s.set(borderTopBackgroundKey, c)
+ return s
+}
+
+// BorderRightBackground sets the background color of right side the border.
+func (s Style) BorderRightBackground(c TerminalColor) Style {
+ s.set(borderRightBackgroundKey, c)
+ return s
+}
+
+// BorderBottomBackground sets the background color of the bottom of the
+// border.
+func (s Style) BorderBottomBackground(c TerminalColor) Style {
+ s.set(borderBottomBackgroundKey, c)
+ return s
+}
+
+// BorderLeftBackground set the background color of the left side of the
+// border.
+func (s Style) BorderLeftBackground(c TerminalColor) Style {
+ s.set(borderLeftBackgroundKey, c)
+ return s
+}
+
+// Inline makes rendering output one line and disables the rendering of
+// margins, padding and borders. This is useful when you need a style to apply
+// only to font rendering and don't want it to change any physical dimensions.
+// It works well with Style.MaxWidth.
+//
+// Because this in intended to be used at the time of render, this method will
+// not mutate the style and instead return a copy.
+//
+// Example:
+//
+// var userInput string = "..."
+// var userStyle = text.Style{ /* ... */ }
+// fmt.Println(userStyle.Inline(true).Render(userInput))
+func (s Style) Inline(v bool) Style {
+ o := s // copy
+ o.set(inlineKey, v)
+ return o
+}
+
+// MaxWidth applies a max width to a given style. This is useful in enforcing
+// a certain width at render time, particularly with arbitrary strings and
+// styles.
+//
+// Because this in intended to be used at the time of render, this method will
+// not mutate the style and instead return a copy.
+//
+// Example:
+//
+// var userInput string = "..."
+// var userStyle = text.Style{ /* ... */ }
+// fmt.Println(userStyle.MaxWidth(16).Render(userInput))
+func (s Style) MaxWidth(n int) Style {
+ o := s // copy
+ o.set(maxWidthKey, n)
+ return o
+}
+
+// MaxHeight applies a max height to a given style. This is useful in enforcing
+// a certain height at render time, particularly with arbitrary strings and
+// styles.
+//
+// Because this in intended to be used at the time of render, this method will
+// not mutate the style and instead returns a copy.
+func (s Style) MaxHeight(n int) Style {
+ o := s // copy
+ o.set(maxHeightKey, n)
+ return o
+}
+
+// NoTabConversion can be passed to [Style.TabWidth] to disable the replacement
+// of tabs with spaces at render time.
+const NoTabConversion = -1
+
+// TabWidth sets the number of spaces that a tab (/t) should be rendered as.
+// When set to 0, tabs will be removed. To disable the replacement of tabs with
+// spaces entirely, set this to [NoTabConversion].
+//
+// By default, tabs will be replaced with 4 spaces.
+func (s Style) TabWidth(n int) Style {
+ if n <= -1 {
+ n = -1
+ }
+ s.set(tabWidthKey, n)
+ return s
+}
+
+// UnderlineSpaces determines whether to underline spaces between words. By
+// default, this is true. Spaces can also be underlined without underlining the
+// text itself.
+func (s Style) UnderlineSpaces(v bool) Style {
+ s.set(underlineSpacesKey, v)
+ return s
+}
+
+// StrikethroughSpaces determines whether to apply strikethroughs to spaces
+// between words. By default, this is true. Spaces can also be struck without
+// underlining the text itself.
+func (s Style) StrikethroughSpaces(v bool) Style {
+ s.set(strikethroughSpacesKey, v)
+ return s
+}
+
+// Transform applies a given function to a string at render time, allowing for
+// the string being rendered to be manipuated.
+//
+// Example:
+//
+// s := NewStyle().Transform(strings.ToUpper)
+// fmt.Println(s.Render("raow!") // "RAOW!"
+func (s Style) Transform(fn func(string) string) Style {
+ s.set(transformKey, fn)
+ return s
+}
+
+// Renderer sets the renderer for the style. This is useful for changing the
+// renderer for a style that is being used in a different context.
+func (s Style) Renderer(r *Renderer) Style {
+ s.r = r
+ return s
+}
+
+// whichSidesInt is a helper method for setting values on sides of a block based
+// on the number of arguments. It follows the CSS shorthand rules for blocks
+// like margin, padding. and borders. Here are how the rules work:
+//
+// 0 args: do nothing
+// 1 arg: all sides
+// 2 args: top -> bottom
+// 3 args: top -> horizontal -> bottom
+// 4 args: top -> right -> bottom -> left
+// 5+ args: do nothing.
+func whichSidesInt(i ...int) (top, right, bottom, left int, ok bool) {
+ switch len(i) {
+ case 1:
+ top = i[0]
+ bottom = i[0]
+ left = i[0]
+ right = i[0]
+ ok = true
+ case 2: //nolint:mnd
+ top = i[0]
+ bottom = i[0]
+ left = i[1]
+ right = i[1]
+ ok = true
+ case 3: //nolint:mnd
+ top = i[0]
+ left = i[1]
+ right = i[1]
+ bottom = i[2]
+ ok = true
+ case 4: //nolint:mnd
+ top = i[0]
+ right = i[1]
+ bottom = i[2]
+ left = i[3]
+ ok = true
+ }
+ return top, right, bottom, left, ok
+}
+
+// whichSidesBool is like whichSidesInt, except it operates on a series of
+// boolean values. See the comment on whichSidesInt for details on how this
+// works.
+func whichSidesBool(i ...bool) (top, right, bottom, left bool, ok bool) {
+ switch len(i) {
+ case 1:
+ top = i[0]
+ bottom = i[0]
+ left = i[0]
+ right = i[0]
+ ok = true
+ case 2: //nolint:mnd
+ top = i[0]
+ bottom = i[0]
+ left = i[1]
+ right = i[1]
+ ok = true
+ case 3: //nolint:mnd
+ top = i[0]
+ left = i[1]
+ right = i[1]
+ bottom = i[2]
+ ok = true
+ case 4: //nolint:mnd
+ top = i[0]
+ right = i[1]
+ bottom = i[2]
+ left = i[3]
+ ok = true
+ }
+ return top, right, bottom, left, ok
+}
+
+// whichSidesColor is like whichSides, except it operates on a series of
+// boolean values. See the comment on whichSidesInt for details on how this
+// works.
+func whichSidesColor(i ...TerminalColor) (top, right, bottom, left TerminalColor, ok bool) {
+ switch len(i) {
+ case 1:
+ top = i[0]
+ bottom = i[0]
+ left = i[0]
+ right = i[0]
+ ok = true
+ case 2: //nolint:mnd
+ top = i[0]
+ bottom = i[0]
+ left = i[1]
+ right = i[1]
+ ok = true
+ case 3: //nolint:mnd
+ top = i[0]
+ left = i[1]
+ right = i[1]
+ bottom = i[2]
+ ok = true
+ case 4: //nolint:mnd
+ top = i[0]
+ right = i[1]
+ bottom = i[2]
+ left = i[3]
+ ok = true
+ }
+ return top, right, bottom, left, ok
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/size.go b/vendor/github.com/charmbracelet/lipgloss/size.go
new file mode 100644
index 00000000..e169ff5e
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/size.go
@@ -0,0 +1,41 @@
+package lipgloss
+
+import (
+ "strings"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+// Width returns the cell width of characters in the string. ANSI sequences are
+// ignored and characters wider than one cell (such as Chinese characters and
+// emojis) are appropriately measured.
+//
+// You should use this instead of len(string) len([]rune(string) as neither
+// will give you accurate results.
+func Width(str string) (width int) {
+ for _, l := range strings.Split(str, "\n") {
+ w := ansi.StringWidth(l)
+ if w > width {
+ width = w
+ }
+ }
+
+ return width
+}
+
+// Height returns height of a string in cells. This is done simply by
+// counting \n characters. If your strings use \r\n for newlines you should
+// convert them to \n first, or simply write a separate function for measuring
+// height.
+func Height(str string) int {
+ return strings.Count(str, "\n") + 1
+}
+
+// Size returns the width and height of the string in cells. ANSI sequences are
+// ignored and characters wider than one cell (such as Chinese characters and
+// emojis) are appropriately measured.
+func Size(str string) (width, height int) {
+ width = Width(str)
+ height = Height(str)
+ return width, height
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/style.go b/vendor/github.com/charmbracelet/lipgloss/style.go
new file mode 100644
index 00000000..59fa3ab2
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/style.go
@@ -0,0 +1,588 @@
+package lipgloss
+
+import (
+ "strings"
+ "unicode"
+
+ "github.com/charmbracelet/x/ansi"
+ "github.com/charmbracelet/x/cellbuf"
+ "github.com/muesli/termenv"
+)
+
+const tabWidthDefault = 4
+
+// Property for a key.
+type propKey int64
+
+// Available properties.
+const (
+ // Boolean props come first.
+ boldKey propKey = 1 << iota
+ italicKey
+ underlineKey
+ strikethroughKey
+ reverseKey
+ blinkKey
+ faintKey
+ underlineSpacesKey
+ strikethroughSpacesKey
+ colorWhitespaceKey
+
+ // Non-boolean props.
+ foregroundKey
+ backgroundKey
+ widthKey
+ heightKey
+ alignHorizontalKey
+ alignVerticalKey
+
+ // Padding.
+ paddingTopKey
+ paddingRightKey
+ paddingBottomKey
+ paddingLeftKey
+
+ // Margins.
+ marginTopKey
+ marginRightKey
+ marginBottomKey
+ marginLeftKey
+ marginBackgroundKey
+
+ // Border runes.
+ borderStyleKey
+
+ // Border edges.
+ borderTopKey
+ borderRightKey
+ borderBottomKey
+ borderLeftKey
+
+ // Border foreground colors.
+ borderTopForegroundKey
+ borderRightForegroundKey
+ borderBottomForegroundKey
+ borderLeftForegroundKey
+
+ // Border background colors.
+ borderTopBackgroundKey
+ borderRightBackgroundKey
+ borderBottomBackgroundKey
+ borderLeftBackgroundKey
+
+ inlineKey
+ maxWidthKey
+ maxHeightKey
+ tabWidthKey
+
+ transformKey
+)
+
+// props is a set of properties.
+type props int64
+
+// set sets a property.
+func (p props) set(k propKey) props {
+ return p | props(k)
+}
+
+// unset unsets a property.
+func (p props) unset(k propKey) props {
+ return p &^ props(k)
+}
+
+// has checks if a property is set.
+func (p props) has(k propKey) bool {
+ return p&props(k) != 0
+}
+
+// NewStyle returns a new, empty Style. While it's syntactic sugar for the
+// Style{} primitive, it's recommended to use this function for creating styles
+// in case the underlying implementation changes. It takes an optional string
+// value to be set as the underlying string value for this style.
+func NewStyle() Style {
+ return renderer.NewStyle()
+}
+
+// NewStyle returns a new, empty Style. While it's syntactic sugar for the
+// Style{} primitive, it's recommended to use this function for creating styles
+// in case the underlying implementation changes. It takes an optional string
+// value to be set as the underlying string value for this style.
+func (r *Renderer) NewStyle() Style {
+ s := Style{r: r}
+ return s
+}
+
+// Style contains a set of rules that comprise a style as a whole.
+type Style struct {
+ r *Renderer
+ props props
+ value string
+
+ // we store bool props values here
+ attrs int
+
+ // props that have values
+ fgColor TerminalColor
+ bgColor TerminalColor
+
+ width int
+ height int
+
+ alignHorizontal Position
+ alignVertical Position
+
+ paddingTop int
+ paddingRight int
+ paddingBottom int
+ paddingLeft int
+
+ marginTop int
+ marginRight int
+ marginBottom int
+ marginLeft int
+ marginBgColor TerminalColor
+
+ borderStyle Border
+ borderTopFgColor TerminalColor
+ borderRightFgColor TerminalColor
+ borderBottomFgColor TerminalColor
+ borderLeftFgColor TerminalColor
+ borderTopBgColor TerminalColor
+ borderRightBgColor TerminalColor
+ borderBottomBgColor TerminalColor
+ borderLeftBgColor TerminalColor
+
+ maxWidth int
+ maxHeight int
+ tabWidth int
+
+ transform func(string) string
+}
+
+// joinString joins a list of strings into a single string separated with a
+// space.
+func joinString(strs ...string) string {
+ return strings.Join(strs, " ")
+}
+
+// SetString sets the underlying string value for this style. To render once
+// the underlying string is set, use the Style.String. This method is
+// a convenience for cases when having a stringer implementation is handy, such
+// as when using fmt.Sprintf. You can also simply define a style and render out
+// strings directly with Style.Render.
+func (s Style) SetString(strs ...string) Style {
+ s.value = joinString(strs...)
+ return s
+}
+
+// Value returns the raw, unformatted, underlying string value for this style.
+func (s Style) Value() string {
+ return s.value
+}
+
+// String implements stringer for a Style, returning the rendered result based
+// on the rules in this style. An underlying string value must be set with
+// Style.SetString prior to using this method.
+func (s Style) String() string {
+ return s.Render()
+}
+
+// Copy returns a copy of this style, including any underlying string values.
+//
+// Deprecated: to copy just use assignment (i.e. a := b). All methods also
+// return a new style.
+func (s Style) Copy() Style {
+ return s
+}
+
+// Inherit overlays the style in the argument onto this style by copying each explicitly
+// set value from the argument style onto this style if it is not already explicitly set.
+// Existing set values are kept intact and not overwritten.
+//
+// Margins, padding, and underlying string values are not inherited.
+func (s Style) Inherit(i Style) Style {
+ for k := boldKey; k <= transformKey; k <<= 1 {
+ if !i.isSet(k) {
+ continue
+ }
+
+ switch k { //nolint:exhaustive
+ case marginTopKey, marginRightKey, marginBottomKey, marginLeftKey:
+ // Margins are not inherited
+ continue
+ case paddingTopKey, paddingRightKey, paddingBottomKey, paddingLeftKey:
+ // Padding is not inherited
+ continue
+ case backgroundKey:
+ // The margins also inherit the background color
+ if !s.isSet(marginBackgroundKey) && !i.isSet(marginBackgroundKey) {
+ s.set(marginBackgroundKey, i.bgColor)
+ }
+ }
+
+ if s.isSet(k) {
+ continue
+ }
+
+ s.setFrom(k, i)
+ }
+ return s
+}
+
+// Render applies the defined style formatting to a given string.
+func (s Style) Render(strs ...string) string {
+ if s.r == nil {
+ s.r = renderer
+ }
+ if s.value != "" {
+ strs = append([]string{s.value}, strs...)
+ }
+
+ var (
+ str = joinString(strs...)
+
+ p = s.r.ColorProfile()
+ te = p.String()
+ teSpace = p.String()
+ teWhitespace = p.String()
+
+ bold = s.getAsBool(boldKey, false)
+ italic = s.getAsBool(italicKey, false)
+ underline = s.getAsBool(underlineKey, false)
+ strikethrough = s.getAsBool(strikethroughKey, false)
+ reverse = s.getAsBool(reverseKey, false)
+ blink = s.getAsBool(blinkKey, false)
+ faint = s.getAsBool(faintKey, false)
+
+ fg = s.getAsColor(foregroundKey)
+ bg = s.getAsColor(backgroundKey)
+
+ width = s.getAsInt(widthKey)
+ height = s.getAsInt(heightKey)
+ horizontalAlign = s.getAsPosition(alignHorizontalKey)
+ verticalAlign = s.getAsPosition(alignVerticalKey)
+
+ topPadding = s.getAsInt(paddingTopKey)
+ rightPadding = s.getAsInt(paddingRightKey)
+ bottomPadding = s.getAsInt(paddingBottomKey)
+ leftPadding = s.getAsInt(paddingLeftKey)
+
+ colorWhitespace = s.getAsBool(colorWhitespaceKey, true)
+ inline = s.getAsBool(inlineKey, false)
+ maxWidth = s.getAsInt(maxWidthKey)
+ maxHeight = s.getAsInt(maxHeightKey)
+
+ underlineSpaces = s.getAsBool(underlineSpacesKey, false) || (underline && s.getAsBool(underlineSpacesKey, true))
+ strikethroughSpaces = s.getAsBool(strikethroughSpacesKey, false) || (strikethrough && s.getAsBool(strikethroughSpacesKey, true))
+
+ // Do we need to style whitespace (padding and space outside
+ // paragraphs) separately?
+ styleWhitespace = reverse
+
+ // Do we need to style spaces separately?
+ useSpaceStyler = (underline && !underlineSpaces) || (strikethrough && !strikethroughSpaces) || underlineSpaces || strikethroughSpaces
+
+ transform = s.getAsTransform(transformKey)
+ )
+
+ if transform != nil {
+ str = transform(str)
+ }
+
+ if s.props == 0 {
+ return s.maybeConvertTabs(str)
+ }
+
+ // Enable support for ANSI on the legacy Windows cmd.exe console. This is a
+ // no-op on non-Windows systems and on Windows runs only once.
+ enableLegacyWindowsANSI()
+
+ if bold {
+ te = te.Bold()
+ }
+ if italic {
+ te = te.Italic()
+ }
+ if underline {
+ te = te.Underline()
+ }
+ if reverse {
+ teWhitespace = teWhitespace.Reverse()
+ te = te.Reverse()
+ }
+ if blink {
+ te = te.Blink()
+ }
+ if faint {
+ te = te.Faint()
+ }
+
+ if fg != noColor {
+ te = te.Foreground(fg.color(s.r))
+ if styleWhitespace {
+ teWhitespace = teWhitespace.Foreground(fg.color(s.r))
+ }
+ if useSpaceStyler {
+ teSpace = teSpace.Foreground(fg.color(s.r))
+ }
+ }
+
+ if bg != noColor {
+ te = te.Background(bg.color(s.r))
+ if colorWhitespace {
+ teWhitespace = teWhitespace.Background(bg.color(s.r))
+ }
+ if useSpaceStyler {
+ teSpace = teSpace.Background(bg.color(s.r))
+ }
+ }
+
+ if underline {
+ te = te.Underline()
+ }
+ if strikethrough {
+ te = te.CrossOut()
+ }
+
+ if underlineSpaces {
+ teSpace = teSpace.Underline()
+ }
+ if strikethroughSpaces {
+ teSpace = teSpace.CrossOut()
+ }
+
+ // Potentially convert tabs to spaces
+ str = s.maybeConvertTabs(str)
+ // carriage returns can cause strange behaviour when rendering.
+ str = strings.ReplaceAll(str, "\r\n", "\n")
+
+ // Strip newlines in single line mode
+ if inline {
+ str = strings.ReplaceAll(str, "\n", "")
+ }
+
+ // Word wrap
+ if !inline && width > 0 {
+ wrapAt := width - leftPadding - rightPadding
+ str = cellbuf.Wrap(str, wrapAt, "")
+ }
+
+ // Render core text
+ {
+ var b strings.Builder
+
+ l := strings.Split(str, "\n")
+ for i := range l {
+ if useSpaceStyler {
+ // Look for spaces and apply a different styler
+ for _, r := range l[i] {
+ if unicode.IsSpace(r) {
+ b.WriteString(teSpace.Styled(string(r)))
+ continue
+ }
+ b.WriteString(te.Styled(string(r)))
+ }
+ } else {
+ b.WriteString(te.Styled(l[i]))
+ }
+ if i != len(l)-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ str = b.String()
+ }
+
+ // Padding
+ if !inline { //nolint:nestif
+ if leftPadding > 0 {
+ var st *termenv.Style
+ if colorWhitespace || styleWhitespace {
+ st = &teWhitespace
+ }
+ str = padLeft(str, leftPadding, st)
+ }
+
+ if rightPadding > 0 {
+ var st *termenv.Style
+ if colorWhitespace || styleWhitespace {
+ st = &teWhitespace
+ }
+ str = padRight(str, rightPadding, st)
+ }
+
+ if topPadding > 0 {
+ str = strings.Repeat("\n", topPadding) + str
+ }
+
+ if bottomPadding > 0 {
+ str += strings.Repeat("\n", bottomPadding)
+ }
+ }
+
+ // Height
+ if height > 0 {
+ str = alignTextVertical(str, verticalAlign, height, nil)
+ }
+
+ // Set alignment. This will also pad short lines with spaces so that all
+ // lines are the same length, so we run it under a few different conditions
+ // beyond alignment.
+ {
+ numLines := strings.Count(str, "\n")
+
+ if numLines != 0 || width != 0 {
+ var st *termenv.Style
+ if colorWhitespace || styleWhitespace {
+ st = &teWhitespace
+ }
+ str = alignTextHorizontal(str, horizontalAlign, width, st)
+ }
+ }
+
+ if !inline {
+ str = s.applyBorder(str)
+ str = s.applyMargins(str, inline)
+ }
+
+ // Truncate according to MaxWidth
+ if maxWidth > 0 {
+ lines := strings.Split(str, "\n")
+
+ for i := range lines {
+ lines[i] = ansi.Truncate(lines[i], maxWidth, "")
+ }
+
+ str = strings.Join(lines, "\n")
+ }
+
+ // Truncate according to MaxHeight
+ if maxHeight > 0 {
+ lines := strings.Split(str, "\n")
+ height := min(maxHeight, len(lines))
+ if len(lines) > 0 {
+ str = strings.Join(lines[:height], "\n")
+ }
+ }
+
+ return str
+}
+
+func (s Style) maybeConvertTabs(str string) string {
+ tw := tabWidthDefault
+ if s.isSet(tabWidthKey) {
+ tw = s.getAsInt(tabWidthKey)
+ }
+ switch tw {
+ case -1:
+ return str
+ case 0:
+ return strings.ReplaceAll(str, "\t", "")
+ default:
+ return strings.ReplaceAll(str, "\t", strings.Repeat(" ", tw))
+ }
+}
+
+func (s Style) applyMargins(str string, inline bool) string {
+ var (
+ topMargin = s.getAsInt(marginTopKey)
+ rightMargin = s.getAsInt(marginRightKey)
+ bottomMargin = s.getAsInt(marginBottomKey)
+ leftMargin = s.getAsInt(marginLeftKey)
+
+ styler termenv.Style
+ )
+
+ bgc := s.getAsColor(marginBackgroundKey)
+ if bgc != noColor {
+ styler = styler.Background(bgc.color(s.r))
+ }
+
+ // Add left and right margin
+ str = padLeft(str, leftMargin, &styler)
+ str = padRight(str, rightMargin, &styler)
+
+ // Top/bottom margin
+ if !inline {
+ _, width := getLines(str)
+ spaces := strings.Repeat(" ", width)
+
+ if topMargin > 0 {
+ str = styler.Styled(strings.Repeat(spaces+"\n", topMargin)) + str
+ }
+ if bottomMargin > 0 {
+ str += styler.Styled(strings.Repeat("\n"+spaces, bottomMargin))
+ }
+ }
+
+ return str
+}
+
+// Apply left padding.
+func padLeft(str string, n int, style *termenv.Style) string {
+ return pad(str, -n, style)
+}
+
+// Apply right padding.
+func padRight(str string, n int, style *termenv.Style) string {
+ return pad(str, n, style)
+}
+
+// pad adds padding to either the left or right side of a string.
+// Positive values add to the right side while negative values
+// add to the left side.
+func pad(str string, n int, style *termenv.Style) string {
+ if n == 0 {
+ return str
+ }
+
+ sp := strings.Repeat(" ", abs(n))
+ if style != nil {
+ sp = style.Styled(sp)
+ }
+
+ b := strings.Builder{}
+ l := strings.Split(str, "\n")
+
+ for i := range l {
+ switch {
+ // pad right
+ case n > 0:
+ b.WriteString(l[i])
+ b.WriteString(sp)
+ // pad left
+ default:
+ b.WriteString(sp)
+ b.WriteString(l[i])
+ }
+
+ if i != len(l)-1 {
+ b.WriteRune('\n')
+ }
+ }
+
+ return b.String()
+}
+
+func max(a, b int) int { //nolint:unparam,predeclared
+ if a > b {
+ return a
+ }
+ return b
+}
+
+func min(a, b int) int { //nolint:predeclared
+ if a < b {
+ return a
+ }
+ return b
+}
+
+func abs(a int) int {
+ if a < 0 {
+ return -a
+ }
+
+ return a
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/table/resizing.go b/vendor/github.com/charmbracelet/lipgloss/table/resizing.go
new file mode 100644
index 00000000..30b0a010
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/table/resizing.go
@@ -0,0 +1,429 @@
+package table
+
+import (
+ "math"
+ "strings"
+
+ "github.com/charmbracelet/lipgloss"
+ "github.com/charmbracelet/x/ansi"
+)
+
+// resize resizes the table to fit the specified width.
+//
+// Given a user defined table width, we must ensure the table is exactly that
+// width. This must account for all borders, column, separators, and column
+// data.
+//
+// In the case where the table is narrower than the specified table width,
+// we simply expand the columns evenly to fit the width.
+// For example, a table with 3 columns takes up 50 characters total, and the
+// width specified is 80, we expand each column by 10 characters, adding 30
+// to the total width.
+//
+// In the case where the table is wider than the specified table width, we
+// _could_ simply shrink the columns evenly but this would result in data
+// being truncated (perhaps unnecessarily). The naive approach could result
+// in very poor cropping of the table data. So, instead of shrinking columns
+// evenly, we calculate the median non-whitespace length of each column, and
+// shrink the columns based on the largest median.
+//
+// For example,
+//
+// ┌──────┬───────────────┬──────────┐
+// │ Name │ Age of Person │ Location │
+// ├──────┼───────────────┼──────────┤
+// │ Kini │ 40 │ New York │
+// │ Eli │ 30 │ London │
+// │ Iris │ 20 │ Paris │
+// └──────┴───────────────┴──────────┘
+//
+// Median non-whitespace length vs column width of each column:
+//
+// Name: 4 / 5
+// Age of Person: 2 / 15
+// Location: 6 / 10
+//
+// The biggest difference is 15 - 2, so we can shrink the 2nd column by 13.
+func (t *Table) resize() {
+ hasHeaders := len(t.headers) > 0
+ rows := dataToMatrix(t.data)
+ r := newResizer(t.width, t.height, t.headers, rows)
+ r.wrap = t.wrap
+ r.borderColumn = t.borderColumn
+ r.yPaddings = make([][]int, len(r.allRows))
+
+ var allRows [][]string
+ if hasHeaders {
+ allRows = append([][]string{t.headers}, rows...)
+ } else {
+ allRows = rows
+ }
+
+ styleFunc := t.styleFunc
+ if t.styleFunc == nil {
+ styleFunc = DefaultStyles
+ }
+
+ r.rowHeights = r.defaultRowHeights()
+
+ for i, row := range allRows {
+ r.yPaddings[i] = make([]int, len(row))
+
+ for j := range row {
+ column := &r.columns[j]
+
+ // Making sure we're passing the right index to `styleFunc`. The header row should be `-1` and
+ // the others should start from `0`.
+ rowIndex := i
+ if hasHeaders {
+ rowIndex--
+ }
+ style := styleFunc(rowIndex, j)
+
+ topMargin, rightMargin, bottomMargin, leftMargin := style.GetMargin()
+ topPadding, rightPadding, bottomPadding, leftPadding := style.GetPadding()
+
+ totalHorizontalPadding := leftMargin + rightMargin + leftPadding + rightPadding
+ column.xPadding = max(column.xPadding, totalHorizontalPadding)
+ column.fixedWidth = max(column.fixedWidth, style.GetWidth())
+
+ r.rowHeights[i] = max(r.rowHeights[i], style.GetHeight())
+
+ totalVerticalPadding := topMargin + bottomMargin + topPadding + bottomPadding
+ r.yPaddings[i][j] = totalVerticalPadding
+ }
+ }
+
+ // A table width wasn't specified. In this case, detect according to
+ // content width.
+ if r.tableWidth <= 0 {
+ r.tableWidth = r.detectTableWidth()
+ }
+
+ t.widths, t.heights = r.optimizedWidths()
+}
+
+// resizerColumn is a column in the resizer.
+type resizerColumn struct {
+ index int
+ min int
+ max int
+ median int
+ rows [][]string
+ xPadding int // horizontal padding
+ fixedWidth int
+}
+
+// resizer is a table resizer.
+type resizer struct {
+ tableWidth int
+ tableHeight int
+ headers []string
+ allRows [][]string
+ rowHeights []int
+ columns []resizerColumn
+
+ wrap bool
+ borderColumn bool
+ yPaddings [][]int // vertical paddings
+}
+
+// newResizer creates a new resizer.
+func newResizer(tableWidth, tableHeight int, headers []string, rows [][]string) *resizer {
+ r := &resizer{
+ tableWidth: tableWidth,
+ tableHeight: tableHeight,
+ headers: headers,
+ }
+
+ if len(headers) > 0 {
+ r.allRows = append([][]string{headers}, rows...)
+ } else {
+ r.allRows = rows
+ }
+
+ for _, row := range r.allRows {
+ for i, cell := range row {
+ cellLen := lipgloss.Width(cell)
+
+ // Header or first row. Just add as is.
+ if len(r.columns) <= i {
+ r.columns = append(r.columns, resizerColumn{
+ index: i,
+ min: cellLen,
+ max: cellLen,
+ median: cellLen,
+ })
+ continue
+ }
+
+ r.columns[i].rows = append(r.columns[i].rows, row)
+ r.columns[i].min = min(r.columns[i].min, cellLen)
+ r.columns[i].max = max(r.columns[i].max, cellLen)
+ }
+ }
+ for j := range r.columns {
+ widths := make([]int, len(r.columns[j].rows))
+ for i, row := range r.columns[j].rows {
+ widths[i] = lipgloss.Width(row[j])
+ }
+ r.columns[j].median = median(widths)
+ }
+
+ return r
+}
+
+// optimizedWidths returns the optimized column widths and row heights.
+func (r *resizer) optimizedWidths() (colWidths, rowHeights []int) {
+ if r.maxTotal() <= r.tableWidth {
+ return r.expandTableWidth()
+ }
+ return r.shrinkTableWidth()
+}
+
+// detectTableWidth detects the table width.
+func (r *resizer) detectTableWidth() int {
+ return r.maxCharCount() + r.totalHorizontalPadding() + r.totalHorizontalBorder()
+}
+
+// expandTableWidth expands the table width.
+func (r *resizer) expandTableWidth() (colWidths, rowHeights []int) {
+ colWidths = r.maxColumnWidths()
+
+ for {
+ totalWidth := sum(colWidths) + r.totalHorizontalBorder()
+ if totalWidth >= r.tableWidth {
+ break
+ }
+
+ shorterColumnIndex := 0
+ shorterColumnWidth := math.MaxInt32
+
+ for j, width := range colWidths {
+ if width == r.columns[j].fixedWidth {
+ continue
+ }
+ if width < shorterColumnWidth {
+ shorterColumnWidth = width
+ shorterColumnIndex = j
+ }
+ }
+
+ colWidths[shorterColumnIndex]++
+ }
+
+ rowHeights = r.expandRowHeights(colWidths)
+ return
+}
+
+// shrinkTableWidth shrinks the table width.
+func (r *resizer) shrinkTableWidth() (colWidths, rowHeights []int) {
+ colWidths = r.maxColumnWidths()
+
+ // Cut width of columns that are way too big.
+ shrinkBiggestColumns := func(veryBigOnly bool) {
+ for {
+ totalWidth := sum(colWidths) + r.totalHorizontalBorder()
+ if totalWidth <= r.tableWidth {
+ break
+ }
+
+ bigColumnIndex := -math.MaxInt32
+ bigColumnWidth := -math.MaxInt32
+
+ for j, width := range colWidths {
+ if width == r.columns[j].fixedWidth {
+ continue
+ }
+ if veryBigOnly {
+ if width >= (r.tableWidth/2) && width > bigColumnWidth { //nolint:mnd
+ bigColumnWidth = width
+ bigColumnIndex = j
+ }
+ } else {
+ if width > bigColumnWidth {
+ bigColumnWidth = width
+ bigColumnIndex = j
+ }
+ }
+ }
+
+ if bigColumnIndex < 0 || colWidths[bigColumnIndex] == 0 {
+ break
+ }
+ colWidths[bigColumnIndex]--
+ }
+ }
+
+ // Cut width of columns that differ the most from the median.
+ shrinkToMedian := func() {
+ for {
+ totalWidth := sum(colWidths) + r.totalHorizontalBorder()
+ if totalWidth <= r.tableWidth {
+ break
+ }
+
+ biggestDiffToMedian := -math.MaxInt32
+ biggestDiffToMedianIndex := -math.MaxInt32
+
+ for j, width := range colWidths {
+ if width == r.columns[j].fixedWidth {
+ continue
+ }
+ diffToMedian := width - r.columns[j].median
+ if diffToMedian > 0 && diffToMedian > biggestDiffToMedian {
+ biggestDiffToMedian = diffToMedian
+ biggestDiffToMedianIndex = j
+ }
+ }
+
+ if biggestDiffToMedianIndex <= 0 || colWidths[biggestDiffToMedianIndex] == 0 {
+ break
+ }
+ colWidths[biggestDiffToMedianIndex]--
+ }
+ }
+
+ shrinkBiggestColumns(true)
+ shrinkToMedian()
+ shrinkBiggestColumns(false)
+
+ return colWidths, r.expandRowHeights(colWidths)
+}
+
+// expandRowHeights expands the row heights.
+func (r *resizer) expandRowHeights(colWidths []int) (rowHeights []int) {
+ rowHeights = r.defaultRowHeights()
+ if !r.wrap {
+ return rowHeights
+ }
+ hasHeaders := len(r.headers) > 0
+
+ for i, row := range r.allRows {
+ for j, cell := range row {
+ // NOTE(@andreynering): Headers always have a height of 1, even when wrap is enabled.
+ if hasHeaders && i == 0 {
+ continue
+ }
+ height := r.detectContentHeight(cell, colWidths[j]-r.xPaddingForCol(j)) + r.xPaddingForCell(i, j)
+ if height > rowHeights[i] {
+ rowHeights[i] = height
+ }
+ }
+ }
+ return
+}
+
+// defaultRowHeights returns the default row heights.
+func (r *resizer) defaultRowHeights() (rowHeights []int) {
+ rowHeights = make([]int, len(r.allRows))
+ for i := range rowHeights {
+ if i < len(r.rowHeights) {
+ rowHeights[i] = r.rowHeights[i]
+ }
+ if rowHeights[i] < 1 {
+ rowHeights[i] = 1
+ }
+ }
+ return
+}
+
+// maxColumnWidths returns the maximum column widths.
+func (r *resizer) maxColumnWidths() []int {
+ maxColumnWidths := make([]int, len(r.columns))
+ for i, col := range r.columns {
+ if col.fixedWidth > 0 {
+ maxColumnWidths[i] = col.fixedWidth
+ } else {
+ maxColumnWidths[i] = col.max + r.xPaddingForCol(col.index)
+ }
+ }
+ return maxColumnWidths
+}
+
+// columnCount returns the column count.
+func (r *resizer) columnCount() int {
+ return len(r.columns)
+}
+
+// maxCharCount returns the maximum character count.
+func (r *resizer) maxCharCount() int {
+ var count int
+ for _, col := range r.columns {
+ if col.fixedWidth > 0 {
+ count += col.fixedWidth - r.xPaddingForCol(col.index)
+ } else {
+ count += col.max
+ }
+ }
+ return count
+}
+
+// maxTotal returns the maximum total width.
+func (r *resizer) maxTotal() (maxTotal int) {
+ for j, column := range r.columns {
+ if column.fixedWidth > 0 {
+ maxTotal += column.fixedWidth
+ } else {
+ maxTotal += column.max + r.xPaddingForCol(j)
+ }
+ }
+ return
+}
+
+// totalHorizontalPadding returns the total padding.
+func (r *resizer) totalHorizontalPadding() (totalHorizontalPadding int) {
+ for _, col := range r.columns {
+ totalHorizontalPadding += col.xPadding
+ }
+ return
+}
+
+// xPaddingForCol returns the horizontal padding for a column.
+func (r *resizer) xPaddingForCol(j int) int {
+ if j >= len(r.columns) {
+ return 0
+ }
+ return r.columns[j].xPadding
+}
+
+// xPaddingForCell returns the horizontal padding for a cell.
+func (r *resizer) xPaddingForCell(i, j int) int {
+ if i >= len(r.yPaddings) || j >= len(r.yPaddings[i]) {
+ return 0
+ }
+ return r.yPaddings[i][j]
+}
+
+// totalHorizontalBorder returns the total border.
+func (r *resizer) totalHorizontalBorder() int {
+ return (r.columnCount() * r.borderPerCell()) + r.extraBorder()
+}
+
+// borderPerCell returns number of border chars per cell.
+func (r *resizer) borderPerCell() int {
+ if r.borderColumn {
+ return 1
+ }
+ return 0
+}
+
+// extraBorder returns the number of the extra border char at the end of the table.
+func (r *resizer) extraBorder() int {
+ if r.borderColumn {
+ return 1
+ }
+ return 0
+}
+
+// detectContentHeight detects the content height.
+func (r *resizer) detectContentHeight(content string, width int) (height int) {
+ if width == 0 {
+ return 1
+ }
+ content = strings.ReplaceAll(content, "\r\n", "\n")
+ for _, line := range strings.Split(content, "\n") {
+ height += strings.Count(ansi.Wrap(line, width, ""), "\n") + 1
+ }
+ return
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/table/rows.go b/vendor/github.com/charmbracelet/lipgloss/table/rows.go
new file mode 100644
index 00000000..a48bc794
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/table/rows.go
@@ -0,0 +1,129 @@
+package table
+
+// Data is the interface that wraps the basic methods of a table model.
+type Data interface {
+ // At returns the contents of the cell at the given index.
+ At(row, cell int) string
+
+ // Rows returns the number of rows in the table.
+ Rows() int
+
+ // Columns returns the number of columns in the table.
+ Columns() int
+}
+
+// StringData is a string-based implementation of the Data interface.
+type StringData struct {
+ rows [][]string
+ columns int
+}
+
+// NewStringData creates a new StringData with the given number of columns.
+func NewStringData(rows ...[]string) *StringData {
+ m := StringData{columns: 0}
+
+ for _, row := range rows {
+ m.columns = max(m.columns, len(row))
+ m.rows = append(m.rows, row)
+ }
+
+ return &m
+}
+
+// Append appends the given row to the table.
+func (m *StringData) Append(row []string) {
+ m.columns = max(m.columns, len(row))
+ m.rows = append(m.rows, row)
+}
+
+// At returns the contents of the cell at the given index.
+func (m *StringData) At(row, cell int) string {
+ if row >= len(m.rows) || cell >= len(m.rows[row]) {
+ return ""
+ }
+
+ return m.rows[row][cell]
+}
+
+// Columns returns the number of columns in the table.
+func (m *StringData) Columns() int {
+ return m.columns
+}
+
+// Item appends the given row to the table.
+func (m *StringData) Item(rows ...string) *StringData {
+ m.columns = max(m.columns, len(rows))
+ m.rows = append(m.rows, rows)
+ return m
+}
+
+// Rows returns the number of rows in the table.
+func (m *StringData) Rows() int {
+ return len(m.rows)
+}
+
+// Filter applies a filter on some data.
+type Filter struct {
+ data Data
+ filter func(row int) bool
+}
+
+// NewFilter initializes a new Filter.
+func NewFilter(data Data) *Filter {
+ return &Filter{data: data}
+}
+
+// Filter applies the given filter function to the data.
+func (m *Filter) Filter(f func(row int) bool) *Filter {
+ m.filter = f
+ return m
+}
+
+// At returns the row at the given index.
+func (m *Filter) At(row, cell int) string {
+ j := 0
+ for i := 0; i < m.data.Rows(); i++ {
+ if m.filter(i) {
+ if j == row {
+ return m.data.At(i, cell)
+ }
+
+ j++
+ }
+ }
+
+ return ""
+}
+
+// Columns returns the number of columns in the table.
+func (m *Filter) Columns() int {
+ return m.data.Columns()
+}
+
+// Rows returns the number of rows in the table.
+func (m *Filter) Rows() int {
+ j := 0
+ for i := 0; i < m.data.Rows(); i++ {
+ if m.filter(i) {
+ j++
+ }
+ }
+
+ return j
+}
+
+// dataToMatrix converts an object that implements the Data interface to a table.
+func dataToMatrix(data Data) (rows [][]string) {
+ numRows := data.Rows()
+ numCols := data.Columns()
+ rows = make([][]string, numRows)
+
+ for i := 0; i < numRows; i++ {
+ rows[i] = make([]string, numCols)
+
+ for j := 0; j < numCols; j++ {
+ rows[i][j] = data.At(i, j)
+ }
+ }
+ return
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/table/table.go b/vendor/github.com/charmbracelet/lipgloss/table/table.go
new file mode 100644
index 00000000..ad17fcfb
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/table/table.go
@@ -0,0 +1,503 @@
+// Package table provides a styled table renderer for terminals.
+package table
+
+import (
+ "strings"
+
+ "github.com/charmbracelet/lipgloss"
+ "github.com/charmbracelet/x/ansi"
+)
+
+// HeaderRow denotes the header's row index used when rendering headers. Use
+// this value when looking to customize header styles in StyleFunc.
+const HeaderRow int = -1
+
+// StyleFunc is the style function that determines the style of a Cell.
+//
+// It takes the row and column of the cell as an input and determines the
+// lipgloss Style to use for that cell position.
+//
+// Example:
+//
+// t := table.New().
+// Headers("Name", "Age").
+// Row("Kini", 4).
+// Row("Eli", 1).
+// Row("Iris", 102).
+// StyleFunc(func(row, col int) lipgloss.Style {
+// switch {
+// case row == 0:
+// return HeaderStyle
+// case row%2 == 0:
+// return EvenRowStyle
+// default:
+// return OddRowStyle
+// }
+// })
+type StyleFunc func(row, col int) lipgloss.Style
+
+// DefaultStyles is a TableStyleFunc that returns a new Style with no attributes.
+func DefaultStyles(_, _ int) lipgloss.Style {
+ return lipgloss.NewStyle()
+}
+
+// Table is a type for rendering tables.
+type Table struct {
+ styleFunc StyleFunc
+ border lipgloss.Border
+
+ borderTop bool
+ borderBottom bool
+ borderLeft bool
+ borderRight bool
+ borderHeader bool
+ borderColumn bool
+ borderRow bool
+
+ borderStyle lipgloss.Style
+ headers []string
+ data Data
+
+ width int
+ height int
+ useManualHeight bool
+ offset int
+ wrap bool
+
+ // widths tracks the width of each column.
+ widths []int
+
+ // heights tracks the height of each row.
+ heights []int
+}
+
+// New returns a new Table that can be modified through different
+// attributes.
+//
+// By default, a table has no border, no styling, and no rows.
+func New() *Table {
+ return &Table{
+ styleFunc: DefaultStyles,
+ border: lipgloss.RoundedBorder(),
+ borderBottom: true,
+ borderColumn: true,
+ borderHeader: true,
+ borderLeft: true,
+ borderRight: true,
+ borderTop: true,
+ wrap: true,
+ data: NewStringData(),
+ }
+}
+
+// ClearRows clears the table rows.
+func (t *Table) ClearRows() *Table {
+ t.data = NewStringData()
+ return t
+}
+
+// StyleFunc sets the style for a cell based on it's position (row, column).
+func (t *Table) StyleFunc(style StyleFunc) *Table {
+ t.styleFunc = style
+ return t
+}
+
+// style returns the style for a cell based on it's position (row, column).
+func (t *Table) style(row, col int) lipgloss.Style {
+ if t.styleFunc == nil {
+ return lipgloss.NewStyle()
+ }
+ return t.styleFunc(row, col)
+}
+
+// Data sets the table data.
+func (t *Table) Data(data Data) *Table {
+ t.data = data
+ return t
+}
+
+// Rows appends rows to the table data.
+func (t *Table) Rows(rows ...[]string) *Table {
+ for _, row := range rows {
+ switch t.data.(type) {
+ case *StringData:
+ t.data.(*StringData).Append(row)
+ }
+ }
+ return t
+}
+
+// Row appends a row to the table data.
+func (t *Table) Row(row ...string) *Table {
+ switch t.data.(type) {
+ case *StringData:
+ t.data.(*StringData).Append(row)
+ }
+ return t
+}
+
+// Headers sets the table headers.
+func (t *Table) Headers(headers ...string) *Table {
+ t.headers = headers
+ return t
+}
+
+// Border sets the table border.
+func (t *Table) Border(border lipgloss.Border) *Table {
+ t.border = border
+ return t
+}
+
+// BorderTop sets the top border.
+func (t *Table) BorderTop(v bool) *Table {
+ t.borderTop = v
+ return t
+}
+
+// BorderBottom sets the bottom border.
+func (t *Table) BorderBottom(v bool) *Table {
+ t.borderBottom = v
+ return t
+}
+
+// BorderLeft sets the left border.
+func (t *Table) BorderLeft(v bool) *Table {
+ t.borderLeft = v
+ return t
+}
+
+// BorderRight sets the right border.
+func (t *Table) BorderRight(v bool) *Table {
+ t.borderRight = v
+ return t
+}
+
+// BorderHeader sets the header separator border.
+func (t *Table) BorderHeader(v bool) *Table {
+ t.borderHeader = v
+ return t
+}
+
+// BorderColumn sets the column border separator.
+func (t *Table) BorderColumn(v bool) *Table {
+ t.borderColumn = v
+ return t
+}
+
+// BorderRow sets the row border separator.
+func (t *Table) BorderRow(v bool) *Table {
+ t.borderRow = v
+ return t
+}
+
+// BorderStyle sets the style for the table border.
+func (t *Table) BorderStyle(style lipgloss.Style) *Table {
+ t.borderStyle = style
+ return t
+}
+
+// Width sets the table width, this auto-sizes the columns to fit the width by
+// either expanding or contracting the widths of each column as a best effort
+// approach.
+func (t *Table) Width(w int) *Table {
+ t.width = w
+ return t
+}
+
+// Height sets the table height.
+func (t *Table) Height(h int) *Table {
+ t.height = h
+ t.useManualHeight = true
+ return t
+}
+
+// Offset sets the table rendering offset.
+//
+// Warning: you may declare Offset only after setting Rows. Otherwise it will be
+// ignored.
+func (t *Table) Offset(o int) *Table {
+ t.offset = o
+ return t
+}
+
+// Wrap dictates whether or not the table content should wrap.
+func (t *Table) Wrap(w bool) *Table {
+ t.wrap = w
+ return t
+}
+
+// String returns the table as a string.
+func (t *Table) String() string {
+ hasHeaders := len(t.headers) > 0
+ hasRows := t.data != nil && t.data.Rows() > 0
+
+ if !hasHeaders && !hasRows {
+ return ""
+ }
+
+ // Add empty cells to the headers, until it's the same length as the longest
+ // row (only if there are at headers in the first place).
+ if hasHeaders {
+ for i := len(t.headers); i < t.data.Columns(); i++ {
+ t.headers = append(t.headers, "")
+ }
+ }
+
+ // Do all the sizing calculations for width and height.
+ t.resize()
+
+ var sb strings.Builder
+
+ if t.borderTop {
+ sb.WriteString(t.constructTopBorder())
+ sb.WriteString("\n")
+ }
+
+ if hasHeaders {
+ sb.WriteString(t.constructHeaders())
+ sb.WriteString("\n")
+ }
+
+ var bottom string
+ if t.borderBottom {
+ bottom = t.constructBottomBorder()
+ }
+
+ // If there are no data rows render nothing.
+ if t.data.Rows() > 0 {
+ switch {
+ case t.useManualHeight:
+ // The height of the top border. Subtract 1 for the newline.
+ topHeight := lipgloss.Height(sb.String()) - 1
+ availableLines := t.height - (topHeight + lipgloss.Height(bottom))
+
+ // if the height is larger than the number of rows, use the number
+ // of rows.
+ if availableLines > t.data.Rows() {
+ availableLines = t.data.Rows()
+ }
+ sb.WriteString(t.constructRows(availableLines))
+
+ default:
+ for r := t.offset; r < t.data.Rows(); r++ {
+ sb.WriteString(t.constructRow(r, false))
+ }
+ }
+ }
+
+ sb.WriteString(bottom)
+
+ return lipgloss.NewStyle().
+ MaxHeight(t.computeHeight()).
+ MaxWidth(t.width).
+ Render(sb.String())
+}
+
+// computeHeight computes the height of the table in it's current configuration.
+func (t *Table) computeHeight() int {
+ hasHeaders := len(t.headers) > 0
+ return sum(t.heights) - 1 + btoi(hasHeaders) +
+ btoi(t.borderTop) + btoi(t.borderBottom) +
+ btoi(t.borderHeader) + t.data.Rows()*btoi(t.borderRow)
+}
+
+// Render returns the table as a string.
+func (t *Table) Render() string {
+ return t.String()
+}
+
+// constructTopBorder constructs the top border for the table given it's current
+// border configuration and data.
+func (t *Table) constructTopBorder() string {
+ var s strings.Builder
+ if t.borderLeft {
+ s.WriteString(t.borderStyle.Render(t.border.TopLeft))
+ }
+ for i := 0; i < len(t.widths); i++ {
+ s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Top, t.widths[i])))
+ if i < len(t.widths)-1 && t.borderColumn {
+ s.WriteString(t.borderStyle.Render(t.border.MiddleTop))
+ }
+ }
+ if t.borderRight {
+ s.WriteString(t.borderStyle.Render(t.border.TopRight))
+ }
+ return s.String()
+}
+
+// constructBottomBorder constructs the bottom border for the table given it's current
+// border configuration and data.
+func (t *Table) constructBottomBorder() string {
+ var s strings.Builder
+ if t.borderLeft {
+ s.WriteString(t.borderStyle.Render(t.border.BottomLeft))
+ }
+ for i := 0; i < len(t.widths); i++ {
+ s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[i])))
+ if i < len(t.widths)-1 && t.borderColumn {
+ s.WriteString(t.borderStyle.Render(t.border.MiddleBottom))
+ }
+ }
+ if t.borderRight {
+ s.WriteString(t.borderStyle.Render(t.border.BottomRight))
+ }
+ return s.String()
+}
+
+// constructHeaders constructs the headers for the table given it's current
+// header configuration and data.
+func (t *Table) constructHeaders() string {
+ height := t.heights[HeaderRow+1]
+
+ var s strings.Builder
+ if t.borderLeft {
+ s.WriteString(t.borderStyle.Render(t.border.Left))
+ }
+ for i, header := range t.headers {
+ cellStyle := t.style(HeaderRow, i)
+
+ if !t.wrap {
+ header = t.truncateCell(header, HeaderRow, i)
+ }
+
+ s.WriteString(cellStyle.
+ Height(height - cellStyle.GetVerticalMargins()).
+ MaxHeight(height).
+ Width(t.widths[i] - cellStyle.GetHorizontalMargins()).
+ MaxWidth(t.widths[i]).
+ Render(t.truncateCell(header, HeaderRow, i)))
+ if i < len(t.headers)-1 && t.borderColumn {
+ s.WriteString(t.borderStyle.Render(t.border.Left))
+ }
+ }
+ if t.borderHeader {
+ if t.borderRight {
+ s.WriteString(t.borderStyle.Render(t.border.Right))
+ }
+ s.WriteString("\n")
+ if t.borderLeft {
+ s.WriteString(t.borderStyle.Render(t.border.MiddleLeft))
+ }
+ for i := 0; i < len(t.headers); i++ {
+ s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Top, t.widths[i])))
+ if i < len(t.headers)-1 && t.borderColumn {
+ s.WriteString(t.borderStyle.Render(t.border.Middle))
+ }
+ }
+ if t.borderRight {
+ s.WriteString(t.borderStyle.Render(t.border.MiddleRight))
+ }
+ }
+ if t.borderRight && !t.borderHeader {
+ s.WriteString(t.borderStyle.Render(t.border.Right))
+ }
+ return s.String()
+}
+
+func (t *Table) constructRows(availableLines int) string {
+ var sb strings.Builder
+
+ // The number of rows to render after removing the offset.
+ offsetRowCount := t.data.Rows() - t.offset
+
+ // The number of rows to render. We always render at least one row.
+ rowsToRender := availableLines
+ rowsToRender = max(rowsToRender, 1)
+
+ // Check if we need to render an overflow row.
+ needsOverflow := rowsToRender < offsetRowCount
+
+ // only use the offset as the starting value if there is overflow.
+ rowIdx := t.offset
+ if !needsOverflow {
+ // if there is no overflow, just render to the height of the table
+ // check there's enough content to fill the table
+ rowIdx = t.data.Rows() - rowsToRender
+ }
+ for rowsToRender > 0 && rowIdx < t.data.Rows() {
+ // Whenever the height is too small to render all rows, the bottom row will be an overflow row (ellipsis).
+ isOverflow := needsOverflow && rowsToRender == 1
+
+ sb.WriteString(t.constructRow(rowIdx, isOverflow))
+
+ rowIdx++
+ rowsToRender--
+ }
+ return sb.String()
+}
+
+// constructRow constructs the row for the table given an index and row data
+// based on the current configuration. If isOverflow is true, the row is
+// rendered as an overflow row (using ellipsis).
+func (t *Table) constructRow(index int, isOverflow bool) string {
+ var s strings.Builder
+
+ hasHeaders := len(t.headers) > 0
+ height := t.heights[index+btoi(hasHeaders)]
+ if isOverflow {
+ height = 1
+ }
+
+ var cells []string
+ left := strings.Repeat(t.borderStyle.Render(t.border.Left)+"\n", height)
+ if t.borderLeft {
+ cells = append(cells, left)
+ }
+
+ for c := 0; c < t.data.Columns(); c++ {
+ cell := "…"
+ if !isOverflow {
+ cell = t.data.At(index, c)
+ }
+
+ cellStyle := t.style(index, c)
+ if !t.wrap {
+ cell = t.truncateCell(cell, index, c)
+ }
+ cells = append(cells, cellStyle.
+ // Account for the margins in the cell sizing.
+ Height(height-cellStyle.GetVerticalMargins()).
+ MaxHeight(height).
+ Width(t.widths[c]-cellStyle.GetHorizontalMargins()).
+ MaxWidth(t.widths[c]).
+ Render(cell))
+
+ if c < t.data.Columns()-1 && t.borderColumn {
+ cells = append(cells, left)
+ }
+ }
+
+ if t.borderRight {
+ right := strings.Repeat(t.borderStyle.Render(t.border.Right)+"\n", height)
+ cells = append(cells, right)
+ }
+
+ for i, cell := range cells {
+ cells[i] = strings.TrimRight(cell, "\n")
+ }
+
+ s.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, cells...) + "\n")
+
+ if t.borderRow && index < t.data.Rows()-1 && !isOverflow {
+ s.WriteString(t.borderStyle.Render(t.border.MiddleLeft))
+ for i := 0; i < len(t.widths); i++ {
+ s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[i])))
+ if i < len(t.widths)-1 && t.borderColumn {
+ s.WriteString(t.borderStyle.Render(t.border.Middle))
+ }
+ }
+ s.WriteString(t.borderStyle.Render(t.border.MiddleRight) + "\n")
+ }
+
+ return s.String()
+}
+
+func (t *Table) truncateCell(cell string, rowIndex, colIndex int) string {
+ hasHeaders := len(t.headers) > 0
+ height := t.heights[rowIndex+btoi(hasHeaders)]
+ cellWidth := t.widths[colIndex]
+ cellStyle := t.style(rowIndex, colIndex)
+
+ length := (cellWidth * height) - cellStyle.GetHorizontalPadding() - cellStyle.GetHorizontalMargins()
+ return ansi.Truncate(cell, length, "…")
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/table/util.go b/vendor/github.com/charmbracelet/lipgloss/table/util.go
new file mode 100644
index 00000000..74bcdffe
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/table/util.go
@@ -0,0 +1,52 @@
+package table
+
+import (
+ "sort"
+)
+
+// btoi converts a boolean to an integer, 1 if true, 0 if false.
+func btoi(b bool) int {
+ if b {
+ return 1
+ }
+ return 0
+}
+
+// max returns the greater of two integers.
+func max(a, b int) int { //nolint:predeclared
+ if a > b {
+ return a
+ }
+ return b
+}
+
+// min returns the smaller of two integers.
+func min(a, b int) int { //nolint:predeclared
+ if a < b {
+ return a
+ }
+ return b
+}
+
+// sum returns the sum of all integers in a slice.
+func sum(n []int) int {
+ var sum int
+ for _, i := range n {
+ sum += i
+ }
+ return sum
+}
+
+// median returns the median of a slice of integers.
+func median(n []int) int {
+ sort.Ints(n)
+
+ if len(n) <= 0 {
+ return 0
+ }
+ if len(n)%2 == 0 {
+ h := len(n) / 2 //nolint:mnd
+ return (n[h-1] + n[h]) / 2 //nolint:mnd
+ }
+ return n[len(n)/2]
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/unset.go b/vendor/github.com/charmbracelet/lipgloss/unset.go
new file mode 100644
index 00000000..1086e722
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/unset.go
@@ -0,0 +1,331 @@
+package lipgloss
+
+// unset unsets a property from a style.
+func (s *Style) unset(key propKey) {
+ s.props = s.props.unset(key)
+}
+
+// UnsetBold removes the bold style rule, if set.
+func (s Style) UnsetBold() Style {
+ s.unset(boldKey)
+ return s
+}
+
+// UnsetItalic removes the italic style rule, if set.
+func (s Style) UnsetItalic() Style {
+ s.unset(italicKey)
+ return s
+}
+
+// UnsetUnderline removes the underline style rule, if set.
+func (s Style) UnsetUnderline() Style {
+ s.unset(underlineKey)
+ return s
+}
+
+// UnsetStrikethrough removes the strikethrough style rule, if set.
+func (s Style) UnsetStrikethrough() Style {
+ s.unset(strikethroughKey)
+ return s
+}
+
+// UnsetReverse removes the reverse style rule, if set.
+func (s Style) UnsetReverse() Style {
+ s.unset(reverseKey)
+ return s
+}
+
+// UnsetBlink removes the blink style rule, if set.
+func (s Style) UnsetBlink() Style {
+ s.unset(blinkKey)
+ return s
+}
+
+// UnsetFaint removes the faint style rule, if set.
+func (s Style) UnsetFaint() Style {
+ s.unset(faintKey)
+ return s
+}
+
+// UnsetForeground removes the foreground style rule, if set.
+func (s Style) UnsetForeground() Style {
+ s.unset(foregroundKey)
+ return s
+}
+
+// UnsetBackground removes the background style rule, if set.
+func (s Style) UnsetBackground() Style {
+ s.unset(backgroundKey)
+ return s
+}
+
+// UnsetWidth removes the width style rule, if set.
+func (s Style) UnsetWidth() Style {
+ s.unset(widthKey)
+ return s
+}
+
+// UnsetHeight removes the height style rule, if set.
+func (s Style) UnsetHeight() Style {
+ s.unset(heightKey)
+ return s
+}
+
+// UnsetAlign removes the horizontal and vertical text alignment style rule, if set.
+func (s Style) UnsetAlign() Style {
+ s.unset(alignHorizontalKey)
+ s.unset(alignVerticalKey)
+ return s
+}
+
+// UnsetAlignHorizontal removes the horizontal text alignment style rule, if set.
+func (s Style) UnsetAlignHorizontal() Style {
+ s.unset(alignHorizontalKey)
+ return s
+}
+
+// UnsetAlignVertical removes the vertical text alignment style rule, if set.
+func (s Style) UnsetAlignVertical() Style {
+ s.unset(alignVerticalKey)
+ return s
+}
+
+// UnsetPadding removes all padding style rules.
+func (s Style) UnsetPadding() Style {
+ s.unset(paddingLeftKey)
+ s.unset(paddingRightKey)
+ s.unset(paddingTopKey)
+ s.unset(paddingBottomKey)
+ return s
+}
+
+// UnsetPaddingLeft removes the left padding style rule, if set.
+func (s Style) UnsetPaddingLeft() Style {
+ s.unset(paddingLeftKey)
+ return s
+}
+
+// UnsetPaddingRight removes the right padding style rule, if set.
+func (s Style) UnsetPaddingRight() Style {
+ s.unset(paddingRightKey)
+ return s
+}
+
+// UnsetPaddingTop removes the top padding style rule, if set.
+func (s Style) UnsetPaddingTop() Style {
+ s.unset(paddingTopKey)
+ return s
+}
+
+// UnsetPaddingBottom removes the bottom padding style rule, if set.
+func (s Style) UnsetPaddingBottom() Style {
+ s.unset(paddingBottomKey)
+ return s
+}
+
+// UnsetColorWhitespace removes the rule for coloring padding, if set.
+func (s Style) UnsetColorWhitespace() Style {
+ s.unset(colorWhitespaceKey)
+ return s
+}
+
+// UnsetMargins removes all margin style rules.
+func (s Style) UnsetMargins() Style {
+ s.unset(marginLeftKey)
+ s.unset(marginRightKey)
+ s.unset(marginTopKey)
+ s.unset(marginBottomKey)
+ return s
+}
+
+// UnsetMarginLeft removes the left margin style rule, if set.
+func (s Style) UnsetMarginLeft() Style {
+ s.unset(marginLeftKey)
+ return s
+}
+
+// UnsetMarginRight removes the right margin style rule, if set.
+func (s Style) UnsetMarginRight() Style {
+ s.unset(marginRightKey)
+ return s
+}
+
+// UnsetMarginTop removes the top margin style rule, if set.
+func (s Style) UnsetMarginTop() Style {
+ s.unset(marginTopKey)
+ return s
+}
+
+// UnsetMarginBottom removes the bottom margin style rule, if set.
+func (s Style) UnsetMarginBottom() Style {
+ s.unset(marginBottomKey)
+ return s
+}
+
+// UnsetMarginBackground removes the margin's background color. Note that the
+// margin's background color can be set from the background color of another
+// style during inheritance.
+func (s Style) UnsetMarginBackground() Style {
+ s.unset(marginBackgroundKey)
+ return s
+}
+
+// UnsetBorderStyle removes the border style rule, if set.
+func (s Style) UnsetBorderStyle() Style {
+ s.unset(borderStyleKey)
+ return s
+}
+
+// UnsetBorderTop removes the border top style rule, if set.
+func (s Style) UnsetBorderTop() Style {
+ s.unset(borderTopKey)
+ return s
+}
+
+// UnsetBorderRight removes the border right style rule, if set.
+func (s Style) UnsetBorderRight() Style {
+ s.unset(borderRightKey)
+ return s
+}
+
+// UnsetBorderBottom removes the border bottom style rule, if set.
+func (s Style) UnsetBorderBottom() Style {
+ s.unset(borderBottomKey)
+ return s
+}
+
+// UnsetBorderLeft removes the border left style rule, if set.
+func (s Style) UnsetBorderLeft() Style {
+ s.unset(borderLeftKey)
+ return s
+}
+
+// UnsetBorderForeground removes all border foreground color styles, if set.
+func (s Style) UnsetBorderForeground() Style {
+ s.unset(borderTopForegroundKey)
+ s.unset(borderRightForegroundKey)
+ s.unset(borderBottomForegroundKey)
+ s.unset(borderLeftForegroundKey)
+ return s
+}
+
+// UnsetBorderTopForeground removes the top border foreground color rule,
+// if set.
+func (s Style) UnsetBorderTopForeground() Style {
+ s.unset(borderTopForegroundKey)
+ return s
+}
+
+// UnsetBorderRightForeground removes the right border foreground color rule,
+// if set.
+func (s Style) UnsetBorderRightForeground() Style {
+ s.unset(borderRightForegroundKey)
+ return s
+}
+
+// UnsetBorderBottomForeground removes the bottom border foreground color
+// rule, if set.
+func (s Style) UnsetBorderBottomForeground() Style {
+ s.unset(borderBottomForegroundKey)
+ return s
+}
+
+// UnsetBorderLeftForeground removes the left border foreground color rule,
+// if set.
+func (s Style) UnsetBorderLeftForeground() Style {
+ s.unset(borderLeftForegroundKey)
+ return s
+}
+
+// UnsetBorderBackground removes all border background color styles, if
+// set.
+func (s Style) UnsetBorderBackground() Style {
+ s.unset(borderTopBackgroundKey)
+ s.unset(borderRightBackgroundKey)
+ s.unset(borderBottomBackgroundKey)
+ s.unset(borderLeftBackgroundKey)
+ return s
+}
+
+// UnsetBorderTopBackgroundColor removes the top border background color rule,
+// if set.
+//
+// Deprecated: This function simply calls Style.UnsetBorderTopBackground.
+func (s Style) UnsetBorderTopBackgroundColor() Style {
+ return s.UnsetBorderTopBackground()
+}
+
+// UnsetBorderTopBackground removes the top border background color rule,
+// if set.
+func (s Style) UnsetBorderTopBackground() Style {
+ s.unset(borderTopBackgroundKey)
+ return s
+}
+
+// UnsetBorderRightBackground removes the right border background color
+// rule, if set.
+func (s Style) UnsetBorderRightBackground() Style {
+ s.unset(borderRightBackgroundKey)
+ return s
+}
+
+// UnsetBorderBottomBackground removes the bottom border background color
+// rule, if set.
+func (s Style) UnsetBorderBottomBackground() Style {
+ s.unset(borderBottomBackgroundKey)
+ return s
+}
+
+// UnsetBorderLeftBackground removes the left border color rule, if set.
+func (s Style) UnsetBorderLeftBackground() Style {
+ s.unset(borderLeftBackgroundKey)
+ return s
+}
+
+// UnsetInline removes the inline style rule, if set.
+func (s Style) UnsetInline() Style {
+ s.unset(inlineKey)
+ return s
+}
+
+// UnsetMaxWidth removes the max width style rule, if set.
+func (s Style) UnsetMaxWidth() Style {
+ s.unset(maxWidthKey)
+ return s
+}
+
+// UnsetMaxHeight removes the max height style rule, if set.
+func (s Style) UnsetMaxHeight() Style {
+ s.unset(maxHeightKey)
+ return s
+}
+
+// UnsetTabWidth removes the tab width style rule, if set.
+func (s Style) UnsetTabWidth() Style {
+ s.unset(tabWidthKey)
+ return s
+}
+
+// UnsetUnderlineSpaces removes the value set by UnderlineSpaces.
+func (s Style) UnsetUnderlineSpaces() Style {
+ s.unset(underlineSpacesKey)
+ return s
+}
+
+// UnsetStrikethroughSpaces removes the value set by StrikethroughSpaces.
+func (s Style) UnsetStrikethroughSpaces() Style {
+ s.unset(strikethroughSpacesKey)
+ return s
+}
+
+// UnsetTransform removes the value set by Transform.
+func (s Style) UnsetTransform() Style {
+ s.unset(transformKey)
+ return s
+}
+
+// UnsetString sets the underlying string value to the empty string.
+func (s Style) UnsetString() Style {
+ s.value = ""
+ return s
+}
diff --git a/vendor/github.com/charmbracelet/lipgloss/whitespace.go b/vendor/github.com/charmbracelet/lipgloss/whitespace.go
new file mode 100644
index 00000000..040dc98e
--- /dev/null
+++ b/vendor/github.com/charmbracelet/lipgloss/whitespace.go
@@ -0,0 +1,83 @@
+package lipgloss
+
+import (
+ "strings"
+
+ "github.com/charmbracelet/x/ansi"
+ "github.com/muesli/termenv"
+)
+
+// whitespace is a whitespace renderer.
+type whitespace struct {
+ re *Renderer
+ style termenv.Style
+ chars string
+}
+
+// newWhitespace creates a new whitespace renderer. The order of the options
+// matters, if you're using WithWhitespaceRenderer, make sure it comes first as
+// other options might depend on it.
+func newWhitespace(r *Renderer, opts ...WhitespaceOption) *whitespace {
+ w := &whitespace{
+ re: r,
+ style: r.ColorProfile().String(),
+ }
+ for _, opt := range opts {
+ opt(w)
+ }
+ return w
+}
+
+// Render whitespaces.
+func (w whitespace) render(width int) string {
+ if w.chars == "" {
+ w.chars = " "
+ }
+
+ r := []rune(w.chars)
+ j := 0
+ b := strings.Builder{}
+
+ // Cycle through runes and print them into the whitespace.
+ for i := 0; i < width; {
+ b.WriteRune(r[j])
+ j++
+ if j >= len(r) {
+ j = 0
+ }
+ i += ansi.StringWidth(string(r[j]))
+ }
+
+ // Fill any extra gaps white spaces. This might be necessary if any runes
+ // are more than one cell wide, which could leave a one-rune gap.
+ short := width - ansi.StringWidth(b.String())
+ if short > 0 {
+ b.WriteString(strings.Repeat(" ", short))
+ }
+
+ return w.style.Styled(b.String())
+}
+
+// WhitespaceOption sets a styling rule for rendering whitespace.
+type WhitespaceOption func(*whitespace)
+
+// WithWhitespaceForeground sets the color of the characters in the whitespace.
+func WithWhitespaceForeground(c TerminalColor) WhitespaceOption {
+ return func(w *whitespace) {
+ w.style = w.style.Foreground(c.color(w.re))
+ }
+}
+
+// WithWhitespaceBackground sets the background color of the whitespace.
+func WithWhitespaceBackground(c TerminalColor) WhitespaceOption {
+ return func(w *whitespace) {
+ w.style = w.style.Background(c.color(w.re))
+ }
+}
+
+// WithWhitespaceChars sets the characters to be rendered in the whitespace.
+func WithWhitespaceChars(s string) WhitespaceOption {
+ return func(w *whitespace) {
+ w.chars = s
+ }
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/LICENSE b/vendor/github.com/charmbracelet/x/ansi/LICENSE
new file mode 100644
index 00000000..65a5654e
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Charmbracelet, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/charmbracelet/x/ansi/ansi.go b/vendor/github.com/charmbracelet/x/ansi/ansi.go
new file mode 100644
index 00000000..48d873c3
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/ansi.go
@@ -0,0 +1,11 @@
+package ansi
+
+import "io"
+
+// Execute is a function that "execute" the given escape sequence by writing it
+// to the provided output writter.
+//
+// This is a syntactic sugar over [io.WriteString].
+func Execute(w io.Writer, s string) (int, error) {
+ return io.WriteString(w, s)
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/ascii.go b/vendor/github.com/charmbracelet/x/ansi/ascii.go
new file mode 100644
index 00000000..188582f7
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/ascii.go
@@ -0,0 +1,8 @@
+package ansi
+
+const (
+ // SP is the space character (Char: \x20).
+ SP = 0x20
+ // DEL is the delete character (Caret: ^?, Char: \x7f).
+ DEL = 0x7F
+)
diff --git a/vendor/github.com/charmbracelet/x/ansi/background.go b/vendor/github.com/charmbracelet/x/ansi/background.go
new file mode 100644
index 00000000..2383cf09
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/background.go
@@ -0,0 +1,169 @@
+package ansi
+
+import (
+ "fmt"
+ "image/color"
+)
+
+// Colorizer is a [color.Color] interface that can be formatted as a string.
+type Colorizer interface {
+ color.Color
+ fmt.Stringer
+}
+
+// HexColorizer is a [color.Color] that can be formatted as a hex string.
+type HexColorizer struct{ color.Color }
+
+var _ Colorizer = HexColorizer{}
+
+// String returns the color as a hex string. If the color is nil, an empty
+// string is returned.
+func (h HexColorizer) String() string {
+ if h.Color == nil {
+ return ""
+ }
+ r, g, b, _ := h.RGBA()
+ // Get the lower 8 bits
+ r &= 0xff
+ g &= 0xff
+ b &= 0xff
+ return fmt.Sprintf("#%02x%02x%02x", uint8(r), uint8(g), uint8(b)) //nolint:gosec
+}
+
+// XRGBColorizer is a [color.Color] that can be formatted as an XParseColor
+// rgb: string.
+//
+// See: https://linux.die.net/man/3/xparsecolor
+type XRGBColorizer struct{ color.Color }
+
+var _ Colorizer = XRGBColorizer{}
+
+// String returns the color as an XParseColor rgb: string. If the color is nil,
+// an empty string is returned.
+func (x XRGBColorizer) String() string {
+ if x.Color == nil {
+ return ""
+ }
+ r, g, b, _ := x.RGBA()
+ // Get the lower 8 bits
+ return fmt.Sprintf("rgb:%04x/%04x/%04x", r, g, b)
+}
+
+// XRGBAColorizer is a [color.Color] that can be formatted as an XParseColor
+// rgba: string.
+//
+// See: https://linux.die.net/man/3/xparsecolor
+type XRGBAColorizer struct{ color.Color }
+
+var _ Colorizer = XRGBAColorizer{}
+
+// String returns the color as an XParseColor rgba: string. If the color is nil,
+// an empty string is returned.
+func (x XRGBAColorizer) String() string {
+ if x.Color == nil {
+ return ""
+ }
+ r, g, b, a := x.RGBA()
+ // Get the lower 8 bits
+ return fmt.Sprintf("rgba:%04x/%04x/%04x/%04x", r, g, b, a)
+}
+
+// SetForegroundColor returns a sequence that sets the default terminal
+// foreground color.
+//
+// OSC 10 ; color ST
+// OSC 10 ; color BEL
+//
+// Where color is the encoded color number.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+func SetForegroundColor(c color.Color) string {
+ var s string
+ switch c := c.(type) {
+ case Colorizer:
+ s = c.String()
+ case fmt.Stringer:
+ s = c.String()
+ default:
+ s = HexColorizer{c}.String()
+ }
+ return "\x1b]10;" + s + "\x07"
+}
+
+// RequestForegroundColor is a sequence that requests the current default
+// terminal foreground color.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+const RequestForegroundColor = "\x1b]10;?\x07"
+
+// ResetForegroundColor is a sequence that resets the default terminal
+// foreground color.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+const ResetForegroundColor = "\x1b]110\x07"
+
+// SetBackgroundColor returns a sequence that sets the default terminal
+// background color.
+//
+// OSC 11 ; color ST
+// OSC 11 ; color BEL
+//
+// Where color is the encoded color number.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+func SetBackgroundColor(c color.Color) string {
+ var s string
+ switch c := c.(type) {
+ case Colorizer:
+ s = c.String()
+ case fmt.Stringer:
+ s = c.String()
+ default:
+ s = HexColorizer{c}.String()
+ }
+ return "\x1b]11;" + s + "\x07"
+}
+
+// RequestBackgroundColor is a sequence that requests the current default
+// terminal background color.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+const RequestBackgroundColor = "\x1b]11;?\x07"
+
+// ResetBackgroundColor is a sequence that resets the default terminal
+// background color.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+const ResetBackgroundColor = "\x1b]111\x07"
+
+// SetCursorColor returns a sequence that sets the terminal cursor color.
+//
+// OSC 12 ; color ST
+// OSC 12 ; color BEL
+//
+// Where color is the encoded color number.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+func SetCursorColor(c color.Color) string {
+ var s string
+ switch c := c.(type) {
+ case Colorizer:
+ s = c.String()
+ case fmt.Stringer:
+ s = c.String()
+ default:
+ s = HexColorizer{c}.String()
+ }
+ return "\x1b]12;" + s + "\x07"
+}
+
+// RequestCursorColor is a sequence that requests the current terminal cursor
+// color.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+const RequestCursorColor = "\x1b]12;?\x07"
+
+// ResetCursorColor is a sequence that resets the terminal cursor color.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+const ResetCursorColor = "\x1b]112\x07"
diff --git a/vendor/github.com/charmbracelet/x/ansi/c0.go b/vendor/github.com/charmbracelet/x/ansi/c0.go
new file mode 100644
index 00000000..28ff7c2a
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/c0.go
@@ -0,0 +1,79 @@
+package ansi
+
+// C0 control characters.
+//
+// These range from (0x00-0x1F) as defined in ISO 646 (ASCII).
+// See: https://en.wikipedia.org/wiki/C0_and_C1_control_codes
+const (
+ // NUL is the null character (Caret: ^@, Char: \0).
+ NUL = 0x00
+ // SOH is the start of heading character (Caret: ^A).
+ SOH = 0x01
+ // STX is the start of text character (Caret: ^B).
+ STX = 0x02
+ // ETX is the end of text character (Caret: ^C).
+ ETX = 0x03
+ // EOT is the end of transmission character (Caret: ^D).
+ EOT = 0x04
+ // ENQ is the enquiry character (Caret: ^E).
+ ENQ = 0x05
+ // ACK is the acknowledge character (Caret: ^F).
+ ACK = 0x06
+ // BEL is the bell character (Caret: ^G, Char: \a).
+ BEL = 0x07
+ // BS is the backspace character (Caret: ^H, Char: \b).
+ BS = 0x08
+ // HT is the horizontal tab character (Caret: ^I, Char: \t).
+ HT = 0x09
+ // LF is the line feed character (Caret: ^J, Char: \n).
+ LF = 0x0A
+ // VT is the vertical tab character (Caret: ^K, Char: \v).
+ VT = 0x0B
+ // FF is the form feed character (Caret: ^L, Char: \f).
+ FF = 0x0C
+ // CR is the carriage return character (Caret: ^M, Char: \r).
+ CR = 0x0D
+ // SO is the shift out character (Caret: ^N).
+ SO = 0x0E
+ // SI is the shift in character (Caret: ^O).
+ SI = 0x0F
+ // DLE is the data link escape character (Caret: ^P).
+ DLE = 0x10
+ // DC1 is the device control 1 character (Caret: ^Q).
+ DC1 = 0x11
+ // DC2 is the device control 2 character (Caret: ^R).
+ DC2 = 0x12
+ // DC3 is the device control 3 character (Caret: ^S).
+ DC3 = 0x13
+ // DC4 is the device control 4 character (Caret: ^T).
+ DC4 = 0x14
+ // NAK is the negative acknowledge character (Caret: ^U).
+ NAK = 0x15
+ // SYN is the synchronous idle character (Caret: ^V).
+ SYN = 0x16
+ // ETB is the end of transmission block character (Caret: ^W).
+ ETB = 0x17
+ // CAN is the cancel character (Caret: ^X).
+ CAN = 0x18
+ // EM is the end of medium character (Caret: ^Y).
+ EM = 0x19
+ // SUB is the substitute character (Caret: ^Z).
+ SUB = 0x1A
+ // ESC is the escape character (Caret: ^[, Char: \e).
+ ESC = 0x1B
+ // FS is the file separator character (Caret: ^\).
+ FS = 0x1C
+ // GS is the group separator character (Caret: ^]).
+ GS = 0x1D
+ // RS is the record separator character (Caret: ^^).
+ RS = 0x1E
+ // US is the unit separator character (Caret: ^_).
+ US = 0x1F
+
+ // LS0 is the locking shift 0 character.
+ // This is an alias for [SI].
+ LS0 = SI
+ // LS1 is the locking shift 1 character.
+ // This is an alias for [SO].
+ LS1 = SO
+)
diff --git a/vendor/github.com/charmbracelet/x/ansi/c1.go b/vendor/github.com/charmbracelet/x/ansi/c1.go
new file mode 100644
index 00000000..71058f53
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/c1.go
@@ -0,0 +1,72 @@
+package ansi
+
+// C1 control characters.
+//
+// These range from (0x80-0x9F) as defined in ISO 6429 (ECMA-48).
+// See: https://en.wikipedia.org/wiki/C0_and_C1_control_codes
+const (
+ // PAD is the padding character.
+ PAD = 0x80
+ // HOP is the high octet preset character.
+ HOP = 0x81
+ // BPH is the break permitted here character.
+ BPH = 0x82
+ // NBH is the no break here character.
+ NBH = 0x83
+ // IND is the index character.
+ IND = 0x84
+ // NEL is the next line character.
+ NEL = 0x85
+ // SSA is the start of selected area character.
+ SSA = 0x86
+ // ESA is the end of selected area character.
+ ESA = 0x87
+ // HTS is the horizontal tab set character.
+ HTS = 0x88
+ // HTJ is the horizontal tab with justification character.
+ HTJ = 0x89
+ // VTS is the vertical tab set character.
+ VTS = 0x8A
+ // PLD is the partial line forward character.
+ PLD = 0x8B
+ // PLU is the partial line backward character.
+ PLU = 0x8C
+ // RI is the reverse index character.
+ RI = 0x8D
+ // SS2 is the single shift 2 character.
+ SS2 = 0x8E
+ // SS3 is the single shift 3 character.
+ SS3 = 0x8F
+ // DCS is the device control string character.
+ DCS = 0x90
+ // PU1 is the private use 1 character.
+ PU1 = 0x91
+ // PU2 is the private use 2 character.
+ PU2 = 0x92
+ // STS is the set transmit state character.
+ STS = 0x93
+ // CCH is the cancel character.
+ CCH = 0x94
+ // MW is the message waiting character.
+ MW = 0x95
+ // SPA is the start of guarded area character.
+ SPA = 0x96
+ // EPA is the end of guarded area character.
+ EPA = 0x97
+ // SOS is the start of string character.
+ SOS = 0x98
+ // SGCI is the single graphic character introducer character.
+ SGCI = 0x99
+ // SCI is the single character introducer character.
+ SCI = 0x9A
+ // CSI is the control sequence introducer character.
+ CSI = 0x9B
+ // ST is the string terminator character.
+ ST = 0x9C
+ // OSC is the operating system command character.
+ OSC = 0x9D
+ // PM is the privacy message character.
+ PM = 0x9E
+ // APC is the application program command character.
+ APC = 0x9F
+)
diff --git a/vendor/github.com/charmbracelet/x/ansi/charset.go b/vendor/github.com/charmbracelet/x/ansi/charset.go
new file mode 100644
index 00000000..50fff51f
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/charset.go
@@ -0,0 +1,55 @@
+package ansi
+
+// SelectCharacterSet sets the G-set character designator to the specified
+// character set.
+//
+// ESC Ps Pd
+//
+// Where Ps is the G-set character designator, and Pd is the identifier.
+// For 94-character sets, the designator can be one of:
+// - ( G0
+// - ) G1
+// - * G2
+// - + G3
+//
+// For 96-character sets, the designator can be one of:
+// - - G1
+// - . G2
+// - / G3
+//
+// Some common 94-character sets are:
+// - 0 DEC Special Drawing Set
+// - A United Kingdom (UK)
+// - B United States (USASCII)
+//
+// Examples:
+//
+// ESC ( B Select character set G0 = United States (USASCII)
+// ESC ( 0 Select character set G0 = Special Character and Line Drawing Set
+// ESC ) 0 Select character set G1 = Special Character and Line Drawing Set
+// ESC * A Select character set G2 = United Kingdom (UK)
+//
+// See: https://vt100.net/docs/vt510-rm/SCS.html
+func SelectCharacterSet(gset byte, charset byte) string {
+ return "\x1b" + string(gset) + string(charset)
+}
+
+// SCS is an alias for SelectCharacterSet.
+func SCS(gset byte, charset byte) string {
+ return SelectCharacterSet(gset, charset)
+}
+
+// Locking Shift 1 Right (LS1R) shifts G1 into GR character set.
+const LS1R = "\x1b~"
+
+// Locking Shift 2 (LS2) shifts G2 into GL character set.
+const LS2 = "\x1bn"
+
+// Locking Shift 2 Right (LS2R) shifts G2 into GR character set.
+const LS2R = "\x1b}"
+
+// Locking Shift 3 (LS3) shifts G3 into GL character set.
+const LS3 = "\x1bo"
+
+// Locking Shift 3 Right (LS3R) shifts G3 into GR character set.
+const LS3R = "\x1b|"
diff --git a/vendor/github.com/charmbracelet/x/ansi/clipboard.go b/vendor/github.com/charmbracelet/x/ansi/clipboard.go
new file mode 100644
index 00000000..94d26c36
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/clipboard.go
@@ -0,0 +1,75 @@
+package ansi
+
+import "encoding/base64"
+
+// Clipboard names.
+const (
+ SystemClipboard = 'c'
+ PrimaryClipboard = 'p'
+)
+
+// SetClipboard returns a sequence for manipulating the clipboard.
+//
+// OSC 52 ; Pc ; Pd ST
+// OSC 52 ; Pc ; Pd BEL
+//
+// Where Pc is the clipboard name and Pd is the base64 encoded data.
+// Empty data or invalid base64 data will reset the clipboard.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+func SetClipboard(c byte, d string) string {
+ if d != "" {
+ d = base64.StdEncoding.EncodeToString([]byte(d))
+ }
+ return "\x1b]52;" + string(c) + ";" + d + "\x07"
+}
+
+// SetSystemClipboard returns a sequence for setting the system clipboard.
+//
+// This is equivalent to SetClipboard(SystemClipboard, d).
+func SetSystemClipboard(d string) string {
+ return SetClipboard(SystemClipboard, d)
+}
+
+// SetPrimaryClipboard returns a sequence for setting the primary clipboard.
+//
+// This is equivalent to SetClipboard(PrimaryClipboard, d).
+func SetPrimaryClipboard(d string) string {
+ return SetClipboard(PrimaryClipboard, d)
+}
+
+// ResetClipboard returns a sequence for resetting the clipboard.
+//
+// This is equivalent to SetClipboard(c, "").
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+func ResetClipboard(c byte) string {
+ return SetClipboard(c, "")
+}
+
+// ResetSystemClipboard is a sequence for resetting the system clipboard.
+//
+// This is equivalent to ResetClipboard(SystemClipboard).
+const ResetSystemClipboard = "\x1b]52;c;\x07"
+
+// ResetPrimaryClipboard is a sequence for resetting the primary clipboard.
+//
+// This is equivalent to ResetClipboard(PrimaryClipboard).
+const ResetPrimaryClipboard = "\x1b]52;p;\x07"
+
+// RequestClipboard returns a sequence for requesting the clipboard.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+func RequestClipboard(c byte) string {
+ return "\x1b]52;" + string(c) + ";?\x07"
+}
+
+// RequestSystemClipboard is a sequence for requesting the system clipboard.
+//
+// This is equivalent to RequestClipboard(SystemClipboard).
+const RequestSystemClipboard = "\x1b]52;c;?\x07"
+
+// RequestPrimaryClipboard is a sequence for requesting the primary clipboard.
+//
+// This is equivalent to RequestClipboard(PrimaryClipboard).
+const RequestPrimaryClipboard = "\x1b]52;p;?\x07"
diff --git a/vendor/github.com/charmbracelet/x/ansi/color.go b/vendor/github.com/charmbracelet/x/ansi/color.go
new file mode 100644
index 00000000..77f8a08d
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/color.go
@@ -0,0 +1,196 @@
+package ansi
+
+import (
+ "image/color"
+)
+
+// Technically speaking, the 16 basic ANSI colors are arbitrary and can be
+// customized at the terminal level. Given that, we're returning what we feel
+// are good defaults.
+//
+// This could also be a slice, but we use a map to make the mappings very
+// explicit.
+//
+// See: https://www.ditig.com/publications/256-colors-cheat-sheet
+var lowANSI = map[uint32]uint32{
+ 0: 0x000000, // black
+ 1: 0x800000, // red
+ 2: 0x008000, // green
+ 3: 0x808000, // yellow
+ 4: 0x000080, // blue
+ 5: 0x800080, // magenta
+ 6: 0x008080, // cyan
+ 7: 0xc0c0c0, // white
+ 8: 0x808080, // bright black
+ 9: 0xff0000, // bright red
+ 10: 0x00ff00, // bright green
+ 11: 0xffff00, // bright yellow
+ 12: 0x0000ff, // bright blue
+ 13: 0xff00ff, // bright magenta
+ 14: 0x00ffff, // bright cyan
+ 15: 0xffffff, // bright white
+}
+
+// Color is a color that can be used in a terminal. ANSI (including
+// ANSI256) and 24-bit "true colors" fall under this category.
+type Color interface {
+ color.Color
+}
+
+// BasicColor is an ANSI 3-bit or 4-bit color with a value from 0 to 15.
+type BasicColor uint8
+
+var _ Color = BasicColor(0)
+
+const (
+ // Black is the ANSI black color.
+ Black BasicColor = iota
+
+ // Red is the ANSI red color.
+ Red
+
+ // Green is the ANSI green color.
+ Green
+
+ // Yellow is the ANSI yellow color.
+ Yellow
+
+ // Blue is the ANSI blue color.
+ Blue
+
+ // Magenta is the ANSI magenta color.
+ Magenta
+
+ // Cyan is the ANSI cyan color.
+ Cyan
+
+ // White is the ANSI white color.
+ White
+
+ // BrightBlack is the ANSI bright black color.
+ BrightBlack
+
+ // BrightRed is the ANSI bright red color.
+ BrightRed
+
+ // BrightGreen is the ANSI bright green color.
+ BrightGreen
+
+ // BrightYellow is the ANSI bright yellow color.
+ BrightYellow
+
+ // BrightBlue is the ANSI bright blue color.
+ BrightBlue
+
+ // BrightMagenta is the ANSI bright magenta color.
+ BrightMagenta
+
+ // BrightCyan is the ANSI bright cyan color.
+ BrightCyan
+
+ // BrightWhite is the ANSI bright white color.
+ BrightWhite
+)
+
+// RGBA returns the red, green, blue and alpha components of the color. It
+// satisfies the color.Color interface.
+func (c BasicColor) RGBA() (uint32, uint32, uint32, uint32) {
+ ansi := uint32(c)
+ if ansi > 15 {
+ return 0, 0, 0, 0xffff
+ }
+
+ r, g, b := ansiToRGB(ansi)
+ return toRGBA(r, g, b)
+}
+
+// ExtendedColor is an ANSI 256 (8-bit) color with a value from 0 to 255.
+type ExtendedColor uint8
+
+var _ Color = ExtendedColor(0)
+
+// RGBA returns the red, green, blue and alpha components of the color. It
+// satisfies the color.Color interface.
+func (c ExtendedColor) RGBA() (uint32, uint32, uint32, uint32) {
+ r, g, b := ansiToRGB(uint32(c))
+ return toRGBA(r, g, b)
+}
+
+// TrueColor is a 24-bit color that can be used in the terminal.
+// This can be used to represent RGB colors.
+//
+// For example, the color red can be represented as:
+//
+// TrueColor(0xff0000)
+type TrueColor uint32
+
+var _ Color = TrueColor(0)
+
+// RGBA returns the red, green, blue and alpha components of the color. It
+// satisfies the color.Color interface.
+func (c TrueColor) RGBA() (uint32, uint32, uint32, uint32) {
+ r, g, b := hexToRGB(uint32(c))
+ return toRGBA(r, g, b)
+}
+
+// ansiToRGB converts an ANSI color to a 24-bit RGB color.
+//
+// r, g, b := ansiToRGB(57)
+func ansiToRGB(ansi uint32) (uint32, uint32, uint32) {
+ // For out-of-range values return black.
+ if ansi > 255 {
+ return 0, 0, 0
+ }
+
+ // Low ANSI.
+ if ansi < 16 {
+ h, ok := lowANSI[ansi]
+ if !ok {
+ return 0, 0, 0
+ }
+ r, g, b := hexToRGB(h)
+ return r, g, b
+ }
+
+ // Grays.
+ if ansi > 231 {
+ s := (ansi-232)*10 + 8
+ return s, s, s
+ }
+
+ // ANSI256.
+ n := ansi - 16
+ b := n % 6
+ g := (n - b) / 6 % 6
+ r := (n - b - g*6) / 36 % 6
+ for _, v := range []*uint32{&r, &g, &b} {
+ if *v > 0 {
+ c := *v*40 + 55
+ *v = c
+ }
+ }
+
+ return r, g, b
+}
+
+// hexToRGB converts a number in hexadecimal format to red, green, and blue
+// values.
+//
+// r, g, b := hexToRGB(0x0000FF)
+func hexToRGB(hex uint32) (uint32, uint32, uint32) {
+ return hex >> 16 & 0xff, hex >> 8 & 0xff, hex & 0xff
+}
+
+// toRGBA converts an RGB 8-bit color values to 32-bit color values suitable
+// for color.Color.
+//
+// color.Color requires 16-bit color values, so we duplicate the 8-bit values
+// to fill the 16-bit values.
+//
+// This always returns 0xffff (opaque) for the alpha channel.
+func toRGBA(r, g, b uint32) (uint32, uint32, uint32, uint32) {
+ r |= r << 8
+ g |= g << 8
+ b |= b << 8
+ return r, g, b, 0xffff
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/ctrl.go b/vendor/github.com/charmbracelet/x/ansi/ctrl.go
new file mode 100644
index 00000000..8ca744cf
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/ctrl.go
@@ -0,0 +1,137 @@
+package ansi
+
+import (
+ "strconv"
+ "strings"
+)
+
+// RequestNameVersion (XTVERSION) is a control sequence that requests the
+// terminal's name and version. It responds with a DSR sequence identifying the
+// terminal.
+//
+// CSI > 0 q
+// DCS > | text ST
+//
+// See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-PC-Style-Function-Keys
+const (
+ RequestNameVersion = "\x1b[>q"
+ XTVERSION = RequestNameVersion
+)
+
+// RequestXTVersion is a control sequence that requests the terminal's XTVERSION. It responds with a DSR sequence identifying the version.
+//
+// CSI > Ps q
+// DCS > | text ST
+//
+// See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-PC-Style-Function-Keys
+//
+// Deprecated: use [RequestNameVersion] instead.
+const RequestXTVersion = RequestNameVersion
+
+// PrimaryDeviceAttributes (DA1) is a control sequence that reports the
+// terminal's primary device attributes.
+//
+// CSI c
+// CSI 0 c
+// CSI ? Ps ; ... c
+//
+// If no attributes are given, or if the attribute is 0, this function returns
+// the request sequence. Otherwise, it returns the response sequence.
+//
+// See https://vt100.net/docs/vt510-rm/DA1.html
+func PrimaryDeviceAttributes(attrs ...int) string {
+ if len(attrs) == 0 {
+ return RequestPrimaryDeviceAttributes
+ } else if len(attrs) == 1 && attrs[0] == 0 {
+ return "\x1b[0c"
+ }
+
+ as := make([]string, len(attrs))
+ for i, a := range attrs {
+ as[i] = strconv.Itoa(a)
+ }
+ return "\x1b[?" + strings.Join(as, ";") + "c"
+}
+
+// DA1 is an alias for [PrimaryDeviceAttributes].
+func DA1(attrs ...int) string {
+ return PrimaryDeviceAttributes(attrs...)
+}
+
+// RequestPrimaryDeviceAttributes is a control sequence that requests the
+// terminal's primary device attributes (DA1).
+//
+// CSI c
+//
+// See https://vt100.net/docs/vt510-rm/DA1.html
+const RequestPrimaryDeviceAttributes = "\x1b[c"
+
+// SecondaryDeviceAttributes (DA2) is a control sequence that reports the
+// terminal's secondary device attributes.
+//
+// CSI > c
+// CSI > 0 c
+// CSI > Ps ; ... c
+//
+// See https://vt100.net/docs/vt510-rm/DA2.html
+func SecondaryDeviceAttributes(attrs ...int) string {
+ if len(attrs) == 0 {
+ return RequestSecondaryDeviceAttributes
+ }
+
+ as := make([]string, len(attrs))
+ for i, a := range attrs {
+ as[i] = strconv.Itoa(a)
+ }
+ return "\x1b[>" + strings.Join(as, ";") + "c"
+}
+
+// DA2 is an alias for [SecondaryDeviceAttributes].
+func DA2(attrs ...int) string {
+ return SecondaryDeviceAttributes(attrs...)
+}
+
+// RequestSecondaryDeviceAttributes is a control sequence that requests the
+// terminal's secondary device attributes (DA2).
+//
+// CSI > c
+//
+// See https://vt100.net/docs/vt510-rm/DA2.html
+const RequestSecondaryDeviceAttributes = "\x1b[>c"
+
+// TertiaryDeviceAttributes (DA3) is a control sequence that reports the
+// terminal's tertiary device attributes.
+//
+// CSI = c
+// CSI = 0 c
+// DCS ! | Text ST
+//
+// Where Text is the unit ID for the terminal.
+//
+// If no unit ID is given, or if the unit ID is 0, this function returns the
+// request sequence. Otherwise, it returns the response sequence.
+//
+// See https://vt100.net/docs/vt510-rm/DA3.html
+func TertiaryDeviceAttributes(unitID string) string {
+ switch unitID {
+ case "":
+ return RequestTertiaryDeviceAttributes
+ case "0":
+ return "\x1b[=0c"
+ }
+
+ return "\x1bP!|" + unitID + "\x1b\\"
+}
+
+// DA3 is an alias for [TertiaryDeviceAttributes].
+func DA3(unitID string) string {
+ return TertiaryDeviceAttributes(unitID)
+}
+
+// RequestTertiaryDeviceAttributes is a control sequence that requests the
+// terminal's tertiary device attributes (DA3).
+//
+// CSI = c
+//
+// See https://vt100.net/docs/vt510-rm/DA3.html
+const RequestTertiaryDeviceAttributes = "\x1b[=c"
diff --git a/vendor/github.com/charmbracelet/x/ansi/cursor.go b/vendor/github.com/charmbracelet/x/ansi/cursor.go
new file mode 100644
index 00000000..0c364d60
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/cursor.go
@@ -0,0 +1,633 @@
+package ansi
+
+import "strconv"
+
+// SaveCursor (DECSC) is an escape sequence that saves the current cursor
+// position.
+//
+// ESC 7
+//
+// See: https://vt100.net/docs/vt510-rm/DECSC.html
+const (
+ SaveCursor = "\x1b7"
+ DECSC = SaveCursor
+)
+
+// RestoreCursor (DECRC) is an escape sequence that restores the cursor
+// position.
+//
+// ESC 8
+//
+// See: https://vt100.net/docs/vt510-rm/DECRC.html
+const (
+ RestoreCursor = "\x1b8"
+ DECRC = RestoreCursor
+)
+
+// RequestCursorPosition is an escape sequence that requests the current cursor
+// position.
+//
+// CSI 6 n
+//
+// The terminal will report the cursor position as a CSI sequence in the
+// following format:
+//
+// CSI Pl ; Pc R
+//
+// Where Pl is the line number and Pc is the column number.
+// See: https://vt100.net/docs/vt510-rm/CPR.html
+//
+// Deprecated: use [RequestCursorPositionReport] instead.
+const RequestCursorPosition = "\x1b[6n"
+
+// RequestExtendedCursorPosition (DECXCPR) is a sequence for requesting the
+// cursor position report including the current page number.
+//
+// CSI ? 6 n
+//
+// The terminal will report the cursor position as a CSI sequence in the
+// following format:
+//
+// CSI ? Pl ; Pc ; Pp R
+//
+// Where Pl is the line number, Pc is the column number, and Pp is the page
+// number.
+// See: https://vt100.net/docs/vt510-rm/DECXCPR.html
+//
+// Deprecated: use [RequestExtendedCursorPositionReport] instead.
+const RequestExtendedCursorPosition = "\x1b[?6n"
+
+// CursorUp (CUU) returns a sequence for moving the cursor up n cells.
+//
+// CSI n A
+//
+// See: https://vt100.net/docs/vt510-rm/CUU.html
+func CursorUp(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "A"
+}
+
+// CUU is an alias for [CursorUp].
+func CUU(n int) string {
+ return CursorUp(n)
+}
+
+// CUU1 is a sequence for moving the cursor up one cell.
+const CUU1 = "\x1b[A"
+
+// CursorUp1 is a sequence for moving the cursor up one cell.
+//
+// This is equivalent to CursorUp(1).
+//
+// Deprecated: use [CUU1] instead.
+const CursorUp1 = "\x1b[A"
+
+// CursorDown (CUD) returns a sequence for moving the cursor down n cells.
+//
+// CSI n B
+//
+// See: https://vt100.net/docs/vt510-rm/CUD.html
+func CursorDown(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "B"
+}
+
+// CUD is an alias for [CursorDown].
+func CUD(n int) string {
+ return CursorDown(n)
+}
+
+// CUD1 is a sequence for moving the cursor down one cell.
+const CUD1 = "\x1b[B"
+
+// CursorDown1 is a sequence for moving the cursor down one cell.
+//
+// This is equivalent to CursorDown(1).
+//
+// Deprecated: use [CUD1] instead.
+const CursorDown1 = "\x1b[B"
+
+// CursorForward (CUF) returns a sequence for moving the cursor right n cells.
+//
+// # CSI n C
+//
+// See: https://vt100.net/docs/vt510-rm/CUF.html
+func CursorForward(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "C"
+}
+
+// CUF is an alias for [CursorForward].
+func CUF(n int) string {
+ return CursorForward(n)
+}
+
+// CUF1 is a sequence for moving the cursor right one cell.
+const CUF1 = "\x1b[C"
+
+// CursorRight (CUF) returns a sequence for moving the cursor right n cells.
+//
+// CSI n C
+//
+// See: https://vt100.net/docs/vt510-rm/CUF.html
+//
+// Deprecated: use [CursorForward] instead.
+func CursorRight(n int) string {
+ return CursorForward(n)
+}
+
+// CursorRight1 is a sequence for moving the cursor right one cell.
+//
+// This is equivalent to CursorRight(1).
+//
+// Deprecated: use [CUF1] instead.
+const CursorRight1 = CUF1
+
+// CursorBackward (CUB) returns a sequence for moving the cursor left n cells.
+//
+// # CSI n D
+//
+// See: https://vt100.net/docs/vt510-rm/CUB.html
+func CursorBackward(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "D"
+}
+
+// CUB is an alias for [CursorBackward].
+func CUB(n int) string {
+ return CursorBackward(n)
+}
+
+// CUB1 is a sequence for moving the cursor left one cell.
+const CUB1 = "\x1b[D"
+
+// CursorLeft (CUB) returns a sequence for moving the cursor left n cells.
+//
+// CSI n D
+//
+// See: https://vt100.net/docs/vt510-rm/CUB.html
+//
+// Deprecated: use [CursorBackward] instead.
+func CursorLeft(n int) string {
+ return CursorBackward(n)
+}
+
+// CursorLeft1 is a sequence for moving the cursor left one cell.
+//
+// This is equivalent to CursorLeft(1).
+//
+// Deprecated: use [CUB1] instead.
+const CursorLeft1 = CUB1
+
+// CursorNextLine (CNL) returns a sequence for moving the cursor to the
+// beginning of the next line n times.
+//
+// CSI n E
+//
+// See: https://vt100.net/docs/vt510-rm/CNL.html
+func CursorNextLine(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "E"
+}
+
+// CNL is an alias for [CursorNextLine].
+func CNL(n int) string {
+ return CursorNextLine(n)
+}
+
+// CursorPreviousLine (CPL) returns a sequence for moving the cursor to the
+// beginning of the previous line n times.
+//
+// CSI n F
+//
+// See: https://vt100.net/docs/vt510-rm/CPL.html
+func CursorPreviousLine(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "F"
+}
+
+// CPL is an alias for [CursorPreviousLine].
+func CPL(n int) string {
+ return CursorPreviousLine(n)
+}
+
+// CursorHorizontalAbsolute (CHA) returns a sequence for moving the cursor to
+// the given column.
+//
+// Default is 1.
+//
+// CSI n G
+//
+// See: https://vt100.net/docs/vt510-rm/CHA.html
+func CursorHorizontalAbsolute(col int) string {
+ var s string
+ if col > 0 {
+ s = strconv.Itoa(col)
+ }
+ return "\x1b[" + s + "G"
+}
+
+// CHA is an alias for [CursorHorizontalAbsolute].
+func CHA(col int) string {
+ return CursorHorizontalAbsolute(col)
+}
+
+// CursorPosition (CUP) returns a sequence for setting the cursor to the
+// given row and column.
+//
+// Default is 1,1.
+//
+// CSI n ; m H
+//
+// See: https://vt100.net/docs/vt510-rm/CUP.html
+func CursorPosition(col, row int) string {
+ if row <= 0 && col <= 0 {
+ return HomeCursorPosition
+ }
+
+ var r, c string
+ if row > 0 {
+ r = strconv.Itoa(row)
+ }
+ if col > 0 {
+ c = strconv.Itoa(col)
+ }
+ return "\x1b[" + r + ";" + c + "H"
+}
+
+// CUP is an alias for [CursorPosition].
+func CUP(col, row int) string {
+ return CursorPosition(col, row)
+}
+
+// CursorHomePosition is a sequence for moving the cursor to the upper left
+// corner of the scrolling region. This is equivalent to `CursorPosition(1, 1)`.
+const CursorHomePosition = "\x1b[H"
+
+// SetCursorPosition (CUP) returns a sequence for setting the cursor to the
+// given row and column.
+//
+// CSI n ; m H
+//
+// See: https://vt100.net/docs/vt510-rm/CUP.html
+//
+// Deprecated: use [CursorPosition] instead.
+func SetCursorPosition(col, row int) string {
+ if row <= 0 && col <= 0 {
+ return HomeCursorPosition
+ }
+
+ var r, c string
+ if row > 0 {
+ r = strconv.Itoa(row)
+ }
+ if col > 0 {
+ c = strconv.Itoa(col)
+ }
+ return "\x1b[" + r + ";" + c + "H"
+}
+
+// HomeCursorPosition is a sequence for moving the cursor to the upper left
+// corner of the scrolling region. This is equivalent to `SetCursorPosition(1, 1)`.
+//
+// Deprecated: use [CursorHomePosition] instead.
+const HomeCursorPosition = CursorHomePosition
+
+// MoveCursor (CUP) returns a sequence for setting the cursor to the
+// given row and column.
+//
+// CSI n ; m H
+//
+// See: https://vt100.net/docs/vt510-rm/CUP.html
+//
+// Deprecated: use [CursorPosition] instead.
+func MoveCursor(col, row int) string {
+ return SetCursorPosition(col, row)
+}
+
+// CursorOrigin is a sequence for moving the cursor to the upper left corner of
+// the display. This is equivalent to `SetCursorPosition(1, 1)`.
+//
+// Deprecated: use [CursorHomePosition] instead.
+const CursorOrigin = "\x1b[1;1H"
+
+// MoveCursorOrigin is a sequence for moving the cursor to the upper left
+// corner of the display. This is equivalent to `SetCursorPosition(1, 1)`.
+//
+// Deprecated: use [CursorHomePosition] instead.
+const MoveCursorOrigin = CursorOrigin
+
+// CursorHorizontalForwardTab (CHT) returns a sequence for moving the cursor to
+// the next tab stop n times.
+//
+// Default is 1.
+//
+// CSI n I
+//
+// See: https://vt100.net/docs/vt510-rm/CHT.html
+func CursorHorizontalForwardTab(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "I"
+}
+
+// CHT is an alias for [CursorHorizontalForwardTab].
+func CHT(n int) string {
+ return CursorHorizontalForwardTab(n)
+}
+
+// EraseCharacter (ECH) returns a sequence for erasing n characters and moving
+// the cursor to the right. This doesn't affect other cell attributes.
+//
+// Default is 1.
+//
+// CSI n X
+//
+// See: https://vt100.net/docs/vt510-rm/ECH.html
+func EraseCharacter(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "X"
+}
+
+// ECH is an alias for [EraseCharacter].
+func ECH(n int) string {
+ return EraseCharacter(n)
+}
+
+// CursorBackwardTab (CBT) returns a sequence for moving the cursor to the
+// previous tab stop n times.
+//
+// Default is 1.
+//
+// CSI n Z
+//
+// See: https://vt100.net/docs/vt510-rm/CBT.html
+func CursorBackwardTab(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "Z"
+}
+
+// CBT is an alias for [CursorBackwardTab].
+func CBT(n int) string {
+ return CursorBackwardTab(n)
+}
+
+// VerticalPositionAbsolute (VPA) returns a sequence for moving the cursor to
+// the given row.
+//
+// Default is 1.
+//
+// CSI n d
+//
+// See: https://vt100.net/docs/vt510-rm/VPA.html
+func VerticalPositionAbsolute(row int) string {
+ var s string
+ if row > 0 {
+ s = strconv.Itoa(row)
+ }
+ return "\x1b[" + s + "d"
+}
+
+// VPA is an alias for [VerticalPositionAbsolute].
+func VPA(row int) string {
+ return VerticalPositionAbsolute(row)
+}
+
+// VerticalPositionRelative (VPR) returns a sequence for moving the cursor down
+// n rows relative to the current position.
+//
+// Default is 1.
+//
+// CSI n e
+//
+// See: https://vt100.net/docs/vt510-rm/VPR.html
+func VerticalPositionRelative(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "e"
+}
+
+// VPR is an alias for [VerticalPositionRelative].
+func VPR(n int) string {
+ return VerticalPositionRelative(n)
+}
+
+// HorizontalVerticalPosition (HVP) returns a sequence for moving the cursor to
+// the given row and column.
+//
+// Default is 1,1.
+//
+// CSI n ; m f
+//
+// This has the same effect as [CursorPosition].
+//
+// See: https://vt100.net/docs/vt510-rm/HVP.html
+func HorizontalVerticalPosition(col, row int) string {
+ var r, c string
+ if row > 0 {
+ r = strconv.Itoa(row)
+ }
+ if col > 0 {
+ c = strconv.Itoa(col)
+ }
+ return "\x1b[" + r + ";" + c + "f"
+}
+
+// HVP is an alias for [HorizontalVerticalPosition].
+func HVP(col, row int) string {
+ return HorizontalVerticalPosition(col, row)
+}
+
+// HorizontalVerticalHomePosition is a sequence for moving the cursor to the
+// upper left corner of the scrolling region. This is equivalent to
+// `HorizontalVerticalPosition(1, 1)`.
+const HorizontalVerticalHomePosition = "\x1b[f"
+
+// SaveCurrentCursorPosition (SCOSC) is a sequence for saving the current cursor
+// position for SCO console mode.
+//
+// CSI s
+//
+// This acts like [DECSC], except the page number where the cursor is located
+// is not saved.
+//
+// See: https://vt100.net/docs/vt510-rm/SCOSC.html
+const (
+ SaveCurrentCursorPosition = "\x1b[s"
+ SCOSC = SaveCurrentCursorPosition
+)
+
+// SaveCursorPosition (SCP or SCOSC) is a sequence for saving the cursor
+// position.
+//
+// CSI s
+//
+// This acts like Save, except the page number where the cursor is located is
+// not saved.
+//
+// See: https://vt100.net/docs/vt510-rm/SCOSC.html
+//
+// Deprecated: use [SaveCurrentCursorPosition] instead.
+const SaveCursorPosition = "\x1b[s"
+
+// RestoreCurrentCursorPosition (SCORC) is a sequence for restoring the current
+// cursor position for SCO console mode.
+//
+// CSI u
+//
+// This acts like [DECRC], except the page number where the cursor was saved is
+// not restored.
+//
+// See: https://vt100.net/docs/vt510-rm/SCORC.html
+const (
+ RestoreCurrentCursorPosition = "\x1b[u"
+ SCORC = RestoreCurrentCursorPosition
+)
+
+// RestoreCursorPosition (RCP or SCORC) is a sequence for restoring the cursor
+// position.
+//
+// CSI u
+//
+// This acts like Restore, except the cursor stays on the same page where the
+// cursor was saved.
+//
+// See: https://vt100.net/docs/vt510-rm/SCORC.html
+//
+// Deprecated: use [RestoreCurrentCursorPosition] instead.
+const RestoreCursorPosition = "\x1b[u"
+
+// SetCursorStyle (DECSCUSR) returns a sequence for changing the cursor style.
+//
+// Default is 1.
+//
+// CSI Ps SP q
+//
+// Where Ps is the cursor style:
+//
+// 0: Blinking block
+// 1: Blinking block (default)
+// 2: Steady block
+// 3: Blinking underline
+// 4: Steady underline
+// 5: Blinking bar (xterm)
+// 6: Steady bar (xterm)
+//
+// See: https://vt100.net/docs/vt510-rm/DECSCUSR.html
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps-SP-q.1D81
+func SetCursorStyle(style int) string {
+ if style < 0 {
+ style = 0
+ }
+ return "\x1b[" + strconv.Itoa(style) + " q"
+}
+
+// DECSCUSR is an alias for [SetCursorStyle].
+func DECSCUSR(style int) string {
+ return SetCursorStyle(style)
+}
+
+// SetPointerShape returns a sequence for changing the mouse pointer cursor
+// shape. Use "default" for the default pointer shape.
+//
+// OSC 22 ; Pt ST
+// OSC 22 ; Pt BEL
+//
+// Where Pt is the pointer shape name. The name can be anything that the
+// operating system can understand. Some common names are:
+//
+// - copy
+// - crosshair
+// - default
+// - ew-resize
+// - n-resize
+// - text
+// - wait
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands
+func SetPointerShape(shape string) string {
+ return "\x1b]22;" + shape + "\x07"
+}
+
+// ReverseIndex (RI) is an escape sequence for moving the cursor up one line in
+// the same column. If the cursor is at the top margin, the screen scrolls
+// down.
+//
+// This has the same effect as [RI].
+const ReverseIndex = "\x1bM"
+
+// HorizontalPositionAbsolute (HPA) returns a sequence for moving the cursor to
+// the given column. This has the same effect as [CUP].
+//
+// Default is 1.
+//
+// CSI n `
+//
+// See: https://vt100.net/docs/vt510-rm/HPA.html
+func HorizontalPositionAbsolute(col int) string {
+ var s string
+ if col > 0 {
+ s = strconv.Itoa(col)
+ }
+ return "\x1b[" + s + "`"
+}
+
+// HPA is an alias for [HorizontalPositionAbsolute].
+func HPA(col int) string {
+ return HorizontalPositionAbsolute(col)
+}
+
+// HorizontalPositionRelative (HPR) returns a sequence for moving the cursor
+// right n columns relative to the current position. This has the same effect
+// as [CUP].
+//
+// Default is 1.
+//
+// CSI n a
+//
+// See: https://vt100.net/docs/vt510-rm/HPR.html
+func HorizontalPositionRelative(n int) string {
+ var s string
+ if n > 0 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "a"
+}
+
+// HPR is an alias for [HorizontalPositionRelative].
+func HPR(n int) string {
+ return HorizontalPositionRelative(n)
+}
+
+// Index (IND) is an escape sequence for moving the cursor down one line in the
+// same column. If the cursor is at the bottom margin, the screen scrolls up.
+// This has the same effect as [IND].
+const Index = "\x1bD"
diff --git a/vendor/github.com/charmbracelet/x/ansi/cwd.go b/vendor/github.com/charmbracelet/x/ansi/cwd.go
new file mode 100644
index 00000000..b03ac1bb
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/cwd.go
@@ -0,0 +1,26 @@
+package ansi
+
+import (
+ "net/url"
+ "path"
+)
+
+// NotifyWorkingDirectory returns a sequence that notifies the terminal
+// of the current working directory.
+//
+// OSC 7 ; Pt BEL
+//
+// Where Pt is a URL in the format "file://[host]/[path]".
+// Set host to "localhost" if this is a path on the local computer.
+//
+// See: https://wezfurlong.org/wezterm/shell-integration.html#osc-7-escape-sequence-to-set-the-working-directory
+// See: https://iterm2.com/documentation-escape-codes.html#:~:text=RemoteHost%20and%20CurrentDir%3A-,OSC%207,-%3B%20%5BPs%5D%20ST
+func NotifyWorkingDirectory(host string, paths ...string) string {
+ path := path.Join(paths...)
+ u := &url.URL{
+ Scheme: "file",
+ Host: host,
+ Path: path,
+ }
+ return "\x1b]7;" + u.String() + "\x07"
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/doc.go b/vendor/github.com/charmbracelet/x/ansi/doc.go
new file mode 100644
index 00000000..e955e9f1
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/doc.go
@@ -0,0 +1,7 @@
+// Package ansi defines common ANSI escape sequences based on the ECMA-48
+// specs.
+//
+// All sequences use 7-bit C1 control codes, which are supported by most
+// terminal emulators. OSC sequences are terminated by a BEL for wider
+// compatibility with terminals.
+package ansi
diff --git a/vendor/github.com/charmbracelet/x/ansi/focus.go b/vendor/github.com/charmbracelet/x/ansi/focus.go
new file mode 100644
index 00000000..4e0207ce
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/focus.go
@@ -0,0 +1,9 @@
+package ansi
+
+// Focus is an escape sequence to notify the terminal that it has focus.
+// This is used with [FocusEventMode].
+const Focus = "\x1b[I"
+
+// Blur is an escape sequence to notify the terminal that it has lost focus.
+// This is used with [FocusEventMode].
+const Blur = "\x1b[O"
diff --git a/vendor/github.com/charmbracelet/x/ansi/graphics.go b/vendor/github.com/charmbracelet/x/ansi/graphics.go
new file mode 100644
index 00000000..604fef47
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/graphics.go
@@ -0,0 +1,199 @@
+package ansi
+
+import (
+ "bytes"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "image"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/charmbracelet/x/ansi/kitty"
+)
+
+// KittyGraphics returns a sequence that encodes the given image in the Kitty
+// graphics protocol.
+//
+// APC G [comma separated options] ; [base64 encoded payload] ST
+//
+// See https://sw.kovidgoyal.net/kitty/graphics-protocol/
+func KittyGraphics(payload []byte, opts ...string) string {
+ var buf bytes.Buffer
+ buf.WriteString("\x1b_G")
+ buf.WriteString(strings.Join(opts, ","))
+ if len(payload) > 0 {
+ buf.WriteString(";")
+ buf.Write(payload)
+ }
+ buf.WriteString("\x1b\\")
+ return buf.String()
+}
+
+var (
+ // KittyGraphicsTempDir is the directory where temporary files are stored.
+ // This is used in [WriteKittyGraphics] along with [os.CreateTemp].
+ KittyGraphicsTempDir = ""
+
+ // KittyGraphicsTempPattern is the pattern used to create temporary files.
+ // This is used in [WriteKittyGraphics] along with [os.CreateTemp].
+ // The Kitty Graphics protocol requires the file path to contain the
+ // substring "tty-graphics-protocol".
+ KittyGraphicsTempPattern = "tty-graphics-protocol-*"
+)
+
+// WriteKittyGraphics writes an image using the Kitty Graphics protocol with
+// the given options to w. It chunks the written data if o.Chunk is true.
+//
+// You can omit m and use nil when rendering an image from a file. In this
+// case, you must provide a file path in o.File and use o.Transmission =
+// [kitty.File]. You can also use o.Transmission = [kitty.TempFile] to write
+// the image to a temporary file. In that case, the file path is ignored, and
+// the image is written to a temporary file that is automatically deleted by
+// the terminal.
+//
+// See https://sw.kovidgoyal.net/kitty/graphics-protocol/
+func WriteKittyGraphics(w io.Writer, m image.Image, o *kitty.Options) error {
+ if o == nil {
+ o = &kitty.Options{}
+ }
+
+ if o.Transmission == 0 && len(o.File) != 0 {
+ o.Transmission = kitty.File
+ }
+
+ var data bytes.Buffer // the data to be encoded into base64
+ e := &kitty.Encoder{
+ Compress: o.Compression == kitty.Zlib,
+ Format: o.Format,
+ }
+
+ switch o.Transmission {
+ case kitty.Direct:
+ if err := e.Encode(&data, m); err != nil {
+ return fmt.Errorf("failed to encode direct image: %w", err)
+ }
+
+ case kitty.SharedMemory:
+ // TODO: Implement shared memory
+ return fmt.Errorf("shared memory transmission is not yet implemented")
+
+ case kitty.File:
+ if len(o.File) == 0 {
+ return kitty.ErrMissingFile
+ }
+
+ f, err := os.Open(o.File)
+ if err != nil {
+ return fmt.Errorf("failed to open file: %w", err)
+ }
+
+ defer f.Close() //nolint:errcheck
+
+ stat, err := f.Stat()
+ if err != nil {
+ return fmt.Errorf("failed to get file info: %w", err)
+ }
+
+ mode := stat.Mode()
+ if !mode.IsRegular() {
+ return fmt.Errorf("file is not a regular file")
+ }
+
+ // Write the file path to the buffer
+ if _, err := data.WriteString(f.Name()); err != nil {
+ return fmt.Errorf("failed to write file path to buffer: %w", err)
+ }
+
+ case kitty.TempFile:
+ f, err := os.CreateTemp(KittyGraphicsTempDir, KittyGraphicsTempPattern)
+ if err != nil {
+ return fmt.Errorf("failed to create file: %w", err)
+ }
+
+ defer f.Close() //nolint:errcheck
+
+ if err := e.Encode(f, m); err != nil {
+ return fmt.Errorf("failed to encode image to file: %w", err)
+ }
+
+ // Write the file path to the buffer
+ if _, err := data.WriteString(f.Name()); err != nil {
+ return fmt.Errorf("failed to write file path to buffer: %w", err)
+ }
+ }
+
+ // Encode image to base64
+ var payload bytes.Buffer // the base64 encoded image to be written to w
+ b64 := base64.NewEncoder(base64.StdEncoding, &payload)
+ if _, err := data.WriteTo(b64); err != nil {
+ return fmt.Errorf("failed to write base64 encoded image to payload: %w", err)
+ }
+ if err := b64.Close(); err != nil {
+ return err
+ }
+
+ // If not chunking, write all at once
+ if !o.Chunk {
+ _, err := io.WriteString(w, KittyGraphics(payload.Bytes(), o.Options()...))
+ return err
+ }
+
+ // Write in chunks
+ var (
+ err error
+ n int
+ )
+ chunk := make([]byte, kitty.MaxChunkSize)
+ isFirstChunk := true
+
+ for {
+ // Stop if we read less than the chunk size [kitty.MaxChunkSize].
+ n, err = io.ReadFull(&payload, chunk)
+ if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) {
+ break
+ }
+ if err != nil {
+ return fmt.Errorf("failed to read chunk: %w", err)
+ }
+
+ opts := buildChunkOptions(o, isFirstChunk, false)
+ if _, err := io.WriteString(w, KittyGraphics(chunk[:n], opts...)); err != nil {
+ return err
+ }
+
+ isFirstChunk = false
+ }
+
+ // Write the last chunk
+ opts := buildChunkOptions(o, isFirstChunk, true)
+ _, err = io.WriteString(w, KittyGraphics(chunk[:n], opts...))
+ return err
+}
+
+// buildChunkOptions creates the options slice for a chunk
+func buildChunkOptions(o *kitty.Options, isFirstChunk, isLastChunk bool) []string {
+ var opts []string
+ if isFirstChunk {
+ opts = o.Options()
+ } else {
+ // These options are allowed in subsequent chunks
+ if o.Quite > 0 {
+ opts = append(opts, fmt.Sprintf("q=%d", o.Quite))
+ }
+ if o.Action == kitty.Frame {
+ opts = append(opts, "a=f")
+ }
+ }
+
+ if !isFirstChunk || !isLastChunk {
+ // We don't need to encode the (m=) option when we only have one chunk.
+ if isLastChunk {
+ opts = append(opts, "m=0")
+ } else {
+ opts = append(opts, "m=1")
+ }
+ }
+ return opts
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/hyperlink.go b/vendor/github.com/charmbracelet/x/ansi/hyperlink.go
new file mode 100644
index 00000000..323bfe93
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/hyperlink.go
@@ -0,0 +1,28 @@
+package ansi
+
+import "strings"
+
+// SetHyperlink returns a sequence for starting a hyperlink.
+//
+// OSC 8 ; Params ; Uri ST
+// OSC 8 ; Params ; Uri BEL
+//
+// To reset the hyperlink, omit the URI.
+//
+// See: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
+func SetHyperlink(uri string, params ...string) string {
+ var p string
+ if len(params) > 0 {
+ p = strings.Join(params, ":")
+ }
+ return "\x1b]8;" + p + ";" + uri + "\x07"
+}
+
+// ResetHyperlink returns a sequence for resetting the hyperlink.
+//
+// This is equivalent to SetHyperlink("", params...).
+//
+// See: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
+func ResetHyperlink(params ...string) string {
+ return SetHyperlink("", params...)
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/iterm2.go b/vendor/github.com/charmbracelet/x/ansi/iterm2.go
new file mode 100644
index 00000000..0ecb336d
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/iterm2.go
@@ -0,0 +1,18 @@
+package ansi
+
+import "fmt"
+
+// ITerm2 returns a sequence that uses the iTerm2 proprietary protocol. Use the
+// iterm2 package for a more convenient API.
+//
+// OSC 1337 ; key = value ST
+//
+// Example:
+//
+// ITerm2(iterm2.File{...})
+//
+// See https://iterm2.com/documentation-escape-codes.html
+// See https://iterm2.com/documentation-images.html
+func ITerm2(data any) string {
+ return "\x1b]1337;" + fmt.Sprint(data) + "\x07"
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/keypad.go b/vendor/github.com/charmbracelet/x/ansi/keypad.go
new file mode 100644
index 00000000..9183c6a7
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/keypad.go
@@ -0,0 +1,28 @@
+package ansi
+
+// Keypad Application Mode (DECKPAM) is a mode that determines whether the
+// keypad sends application sequences or ANSI sequences.
+//
+// This works like enabling [DECNKM].
+// Use [NumericKeypadMode] to set the numeric keypad mode.
+//
+// ESC =
+//
+// See: https://vt100.net/docs/vt510-rm/DECKPAM.html
+const (
+ KeypadApplicationMode = "\x1b="
+ DECKPAM = KeypadApplicationMode
+)
+
+// Keypad Numeric Mode (DECKPNM) is a mode that determines whether the keypad
+// sends application sequences or ANSI sequences.
+//
+// This works the same as disabling [DECNKM].
+//
+// ESC >
+//
+// See: https://vt100.net/docs/vt510-rm/DECKPNM.html
+const (
+ KeypadNumericMode = "\x1b>"
+ DECKPNM = KeypadNumericMode
+)
diff --git a/vendor/github.com/charmbracelet/x/ansi/kitty.go b/vendor/github.com/charmbracelet/x/ansi/kitty.go
new file mode 100644
index 00000000..124ab839
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/kitty.go
@@ -0,0 +1,90 @@
+package ansi
+
+import "strconv"
+
+// Kitty keyboard protocol progressive enhancement flags.
+// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
+const (
+ KittyDisambiguateEscapeCodes = 1 << iota
+ KittyReportEventTypes
+ KittyReportAlternateKeys
+ KittyReportAllKeysAsEscapeCodes
+ KittyReportAssociatedKeys
+
+ KittyAllFlags = KittyDisambiguateEscapeCodes | KittyReportEventTypes |
+ KittyReportAlternateKeys | KittyReportAllKeysAsEscapeCodes | KittyReportAssociatedKeys
+)
+
+// RequestKittyKeyboard is a sequence to request the terminal Kitty keyboard
+// protocol enabled flags.
+//
+// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
+const RequestKittyKeyboard = "\x1b[?u"
+
+// KittyKeyboard returns a sequence to request keyboard enhancements from the terminal.
+// The flags argument is a bitmask of the Kitty keyboard protocol flags. While
+// mode specifies how the flags should be interpreted.
+//
+// Possible values for flags mask:
+//
+// 1: Disambiguate escape codes
+// 2: Report event types
+// 4: Report alternate keys
+// 8: Report all keys as escape codes
+// 16: Report associated text
+//
+// Possible values for mode:
+//
+// 1: Set given flags and unset all others
+// 2: Set given flags and keep existing flags unchanged
+// 3: Unset given flags and keep existing flags unchanged
+//
+// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
+func KittyKeyboard(flags, mode int) string {
+ return "\x1b[=" + strconv.Itoa(flags) + ";" + strconv.Itoa(mode) + "u"
+}
+
+// PushKittyKeyboard returns a sequence to push the given flags to the terminal
+// Kitty Keyboard stack.
+//
+// Possible values for flags mask:
+//
+// 0: Disable all features
+// 1: Disambiguate escape codes
+// 2: Report event types
+// 4: Report alternate keys
+// 8: Report all keys as escape codes
+// 16: Report associated text
+//
+// CSI > flags u
+//
+// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
+func PushKittyKeyboard(flags int) string {
+ var f string
+ if flags > 0 {
+ f = strconv.Itoa(flags)
+ }
+
+ return "\x1b[>" + f + "u"
+}
+
+// DisableKittyKeyboard is a sequence to push zero into the terminal Kitty
+// Keyboard stack to disable the protocol.
+//
+// This is equivalent to PushKittyKeyboard(0).
+const DisableKittyKeyboard = "\x1b[>u"
+
+// PopKittyKeyboard returns a sequence to pop n number of flags from the
+// terminal Kitty Keyboard stack.
+//
+// CSI < flags u
+//
+// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
+func PopKittyKeyboard(n int) string {
+ var num string
+ if n > 0 {
+ num = strconv.Itoa(n)
+ }
+
+ return "\x1b[<" + num + "u"
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/kitty/decoder.go b/vendor/github.com/charmbracelet/x/ansi/kitty/decoder.go
new file mode 100644
index 00000000..fbd08441
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/kitty/decoder.go
@@ -0,0 +1,85 @@
+package kitty
+
+import (
+ "compress/zlib"
+ "fmt"
+ "image"
+ "image/color"
+ "image/png"
+ "io"
+)
+
+// Decoder is a decoder for the Kitty graphics protocol. It supports decoding
+// images in the 24-bit [RGB], 32-bit [RGBA], and [PNG] formats. It can also
+// decompress data using zlib.
+// The default format is 32-bit [RGBA].
+type Decoder struct {
+ // Uses zlib decompression.
+ Decompress bool
+
+ // Can be one of [RGB], [RGBA], or [PNG].
+ Format int
+
+ // Width of the image in pixels. This can be omitted if the image is [PNG]
+ // formatted.
+ Width int
+
+ // Height of the image in pixels. This can be omitted if the image is [PNG]
+ // formatted.
+ Height int
+}
+
+// Decode decodes the image data from r in the specified format.
+func (d *Decoder) Decode(r io.Reader) (image.Image, error) {
+ if d.Decompress {
+ zr, err := zlib.NewReader(r)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create zlib reader: %w", err)
+ }
+
+ defer zr.Close() //nolint:errcheck
+ r = zr
+ }
+
+ if d.Format == 0 {
+ d.Format = RGBA
+ }
+
+ switch d.Format {
+ case RGBA, RGB:
+ return d.decodeRGBA(r, d.Format == RGBA)
+
+ case PNG:
+ return png.Decode(r)
+
+ default:
+ return nil, fmt.Errorf("unsupported format: %d", d.Format)
+ }
+}
+
+// decodeRGBA decodes the image data in 32-bit RGBA or 24-bit RGB formats.
+func (d *Decoder) decodeRGBA(r io.Reader, alpha bool) (image.Image, error) {
+ m := image.NewRGBA(image.Rect(0, 0, d.Width, d.Height))
+
+ var buf []byte
+ if alpha {
+ buf = make([]byte, 4)
+ } else {
+ buf = make([]byte, 3)
+ }
+
+ for y := 0; y < d.Height; y++ {
+ for x := 0; x < d.Width; x++ {
+ if _, err := io.ReadFull(r, buf[:]); err != nil {
+ return nil, fmt.Errorf("failed to read pixel data: %w", err)
+ }
+ if alpha {
+ m.SetRGBA(x, y, color.RGBA{buf[0], buf[1], buf[2], buf[3]})
+ } else {
+ m.SetRGBA(x, y, color.RGBA{buf[0], buf[1], buf[2], 0xff})
+ }
+ }
+ }
+
+ return m, nil
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/kitty/encoder.go b/vendor/github.com/charmbracelet/x/ansi/kitty/encoder.go
new file mode 100644
index 00000000..f668b9e3
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/kitty/encoder.go
@@ -0,0 +1,64 @@
+package kitty
+
+import (
+ "compress/zlib"
+ "fmt"
+ "image"
+ "image/png"
+ "io"
+)
+
+// Encoder is an encoder for the Kitty graphics protocol. It supports encoding
+// images in the 24-bit [RGB], 32-bit [RGBA], and [PNG] formats, and
+// compressing the data using zlib.
+// The default format is 32-bit [RGBA].
+type Encoder struct {
+ // Uses zlib compression.
+ Compress bool
+
+ // Can be one of [RGBA], [RGB], or [PNG].
+ Format int
+}
+
+// Encode encodes the image data in the specified format and writes it to w.
+func (e *Encoder) Encode(w io.Writer, m image.Image) error {
+ if m == nil {
+ return nil
+ }
+
+ if e.Compress {
+ zw := zlib.NewWriter(w)
+ defer zw.Close() //nolint:errcheck
+ w = zw
+ }
+
+ if e.Format == 0 {
+ e.Format = RGBA
+ }
+
+ switch e.Format {
+ case RGBA, RGB:
+ bounds := m.Bounds()
+ for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ r, g, b, a := m.At(x, y).RGBA()
+ switch e.Format {
+ case RGBA:
+ w.Write([]byte{byte(r >> 8), byte(g >> 8), byte(b >> 8), byte(a >> 8)}) //nolint:errcheck
+ case RGB:
+ w.Write([]byte{byte(r >> 8), byte(g >> 8), byte(b >> 8)}) //nolint:errcheck
+ }
+ }
+ }
+
+ case PNG:
+ if err := png.Encode(w, m); err != nil {
+ return fmt.Errorf("failed to encode PNG: %w", err)
+ }
+
+ default:
+ return fmt.Errorf("unsupported format: %d", e.Format)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/kitty/graphics.go b/vendor/github.com/charmbracelet/x/ansi/kitty/graphics.go
new file mode 100644
index 00000000..490e7a8a
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/kitty/graphics.go
@@ -0,0 +1,414 @@
+package kitty
+
+import "errors"
+
+// ErrMissingFile is returned when the file path is missing.
+var ErrMissingFile = errors.New("missing file path")
+
+// MaxChunkSize is the maximum chunk size for the image data.
+const MaxChunkSize = 1024 * 4
+
+// Placeholder is a special Unicode character that can be used as a placeholder
+// for an image.
+const Placeholder = '\U0010EEEE'
+
+// Graphics image format.
+const (
+ // 32-bit RGBA format.
+ RGBA = 32
+
+ // 24-bit RGB format.
+ RGB = 24
+
+ // PNG format.
+ PNG = 100
+)
+
+// Compression types.
+const (
+ Zlib = 'z'
+)
+
+// Transmission types.
+const (
+ // The data transmitted directly in the escape sequence.
+ Direct = 'd'
+
+ // The data transmitted in a regular file.
+ File = 'f'
+
+ // A temporary file is used and deleted after transmission.
+ TempFile = 't'
+
+ // A shared memory object.
+ // For POSIX see https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html
+ // For Windows see https://docs.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory
+ SharedMemory = 's'
+)
+
+// Action types.
+const (
+ // Transmit image data.
+ Transmit = 't'
+ // TransmitAndPut transmit image data and display (put) it.
+ TransmitAndPut = 'T'
+ // Query terminal for image info.
+ Query = 'q'
+ // Put (display) previously transmitted image.
+ Put = 'p'
+ // Delete image.
+ Delete = 'd'
+ // Frame transmits data for animation frames.
+ Frame = 'f'
+ // Animate controls animation.
+ Animate = 'a'
+ // Compose composes animation frames.
+ Compose = 'c'
+)
+
+// Delete types.
+const (
+ // Delete all placements visible on screen
+ DeleteAll = 'a'
+ // Delete all images with the specified id, specified using the i key. If
+ // you specify a p key for the placement id as well, then only the
+ // placement with the specified image id and placement id will be deleted.
+ DeleteID = 'i'
+ // Delete newest image with the specified number, specified using the I
+ // key. If you specify a p key for the placement id as well, then only the
+ // placement with the specified number and placement id will be deleted.
+ DeleteNumber = 'n'
+ // Delete all placements that intersect with the current cursor position.
+ DeleteCursor = 'c'
+ // Delete animation frames.
+ DeleteFrames = 'f'
+ // Delete all placements that intersect a specific cell, the cell is
+ // specified using the x and y keys
+ DeleteCell = 'p'
+ // Delete all placements that intersect a specific cell having a specific
+ // z-index. The cell and z-index is specified using the x, y and z keys.
+ DeleteCellZ = 'q'
+ // Delete all images whose id is greater than or equal to the value of the x
+ // key and less than or equal to the value of the y.
+ DeleteRange = 'r'
+ // Delete all placements that intersect the specified column, specified using
+ // the x key.
+ DeleteColumn = 'x'
+ // Delete all placements that intersect the specified row, specified using
+ // the y key.
+ DeleteRow = 'y'
+ // Delete all placements that have the specified z-index, specified using the
+ // z key.
+ DeleteZ = 'z'
+)
+
+// Diacritic returns the diacritic rune at the specified index. If the index is
+// out of bounds, the first diacritic rune is returned.
+func Diacritic(i int) rune {
+ if i < 0 || i >= len(diacritics) {
+ return diacritics[0]
+ }
+ return diacritics[i]
+}
+
+// From https://sw.kovidgoyal.net/kitty/_downloads/f0a0de9ec8d9ff4456206db8e0814937/rowcolumn-diacritics.txt
+// See https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders for further explanation.
+var diacritics = []rune{
+ '\u0305',
+ '\u030D',
+ '\u030E',
+ '\u0310',
+ '\u0312',
+ '\u033D',
+ '\u033E',
+ '\u033F',
+ '\u0346',
+ '\u034A',
+ '\u034B',
+ '\u034C',
+ '\u0350',
+ '\u0351',
+ '\u0352',
+ '\u0357',
+ '\u035B',
+ '\u0363',
+ '\u0364',
+ '\u0365',
+ '\u0366',
+ '\u0367',
+ '\u0368',
+ '\u0369',
+ '\u036A',
+ '\u036B',
+ '\u036C',
+ '\u036D',
+ '\u036E',
+ '\u036F',
+ '\u0483',
+ '\u0484',
+ '\u0485',
+ '\u0486',
+ '\u0487',
+ '\u0592',
+ '\u0593',
+ '\u0594',
+ '\u0595',
+ '\u0597',
+ '\u0598',
+ '\u0599',
+ '\u059C',
+ '\u059D',
+ '\u059E',
+ '\u059F',
+ '\u05A0',
+ '\u05A1',
+ '\u05A8',
+ '\u05A9',
+ '\u05AB',
+ '\u05AC',
+ '\u05AF',
+ '\u05C4',
+ '\u0610',
+ '\u0611',
+ '\u0612',
+ '\u0613',
+ '\u0614',
+ '\u0615',
+ '\u0616',
+ '\u0617',
+ '\u0657',
+ '\u0658',
+ '\u0659',
+ '\u065A',
+ '\u065B',
+ '\u065D',
+ '\u065E',
+ '\u06D6',
+ '\u06D7',
+ '\u06D8',
+ '\u06D9',
+ '\u06DA',
+ '\u06DB',
+ '\u06DC',
+ '\u06DF',
+ '\u06E0',
+ '\u06E1',
+ '\u06E2',
+ '\u06E4',
+ '\u06E7',
+ '\u06E8',
+ '\u06EB',
+ '\u06EC',
+ '\u0730',
+ '\u0732',
+ '\u0733',
+ '\u0735',
+ '\u0736',
+ '\u073A',
+ '\u073D',
+ '\u073F',
+ '\u0740',
+ '\u0741',
+ '\u0743',
+ '\u0745',
+ '\u0747',
+ '\u0749',
+ '\u074A',
+ '\u07EB',
+ '\u07EC',
+ '\u07ED',
+ '\u07EE',
+ '\u07EF',
+ '\u07F0',
+ '\u07F1',
+ '\u07F3',
+ '\u0816',
+ '\u0817',
+ '\u0818',
+ '\u0819',
+ '\u081B',
+ '\u081C',
+ '\u081D',
+ '\u081E',
+ '\u081F',
+ '\u0820',
+ '\u0821',
+ '\u0822',
+ '\u0823',
+ '\u0825',
+ '\u0826',
+ '\u0827',
+ '\u0829',
+ '\u082A',
+ '\u082B',
+ '\u082C',
+ '\u082D',
+ '\u0951',
+ '\u0953',
+ '\u0954',
+ '\u0F82',
+ '\u0F83',
+ '\u0F86',
+ '\u0F87',
+ '\u135D',
+ '\u135E',
+ '\u135F',
+ '\u17DD',
+ '\u193A',
+ '\u1A17',
+ '\u1A75',
+ '\u1A76',
+ '\u1A77',
+ '\u1A78',
+ '\u1A79',
+ '\u1A7A',
+ '\u1A7B',
+ '\u1A7C',
+ '\u1B6B',
+ '\u1B6D',
+ '\u1B6E',
+ '\u1B6F',
+ '\u1B70',
+ '\u1B71',
+ '\u1B72',
+ '\u1B73',
+ '\u1CD0',
+ '\u1CD1',
+ '\u1CD2',
+ '\u1CDA',
+ '\u1CDB',
+ '\u1CE0',
+ '\u1DC0',
+ '\u1DC1',
+ '\u1DC3',
+ '\u1DC4',
+ '\u1DC5',
+ '\u1DC6',
+ '\u1DC7',
+ '\u1DC8',
+ '\u1DC9',
+ '\u1DCB',
+ '\u1DCC',
+ '\u1DD1',
+ '\u1DD2',
+ '\u1DD3',
+ '\u1DD4',
+ '\u1DD5',
+ '\u1DD6',
+ '\u1DD7',
+ '\u1DD8',
+ '\u1DD9',
+ '\u1DDA',
+ '\u1DDB',
+ '\u1DDC',
+ '\u1DDD',
+ '\u1DDE',
+ '\u1DDF',
+ '\u1DE0',
+ '\u1DE1',
+ '\u1DE2',
+ '\u1DE3',
+ '\u1DE4',
+ '\u1DE5',
+ '\u1DE6',
+ '\u1DFE',
+ '\u20D0',
+ '\u20D1',
+ '\u20D4',
+ '\u20D5',
+ '\u20D6',
+ '\u20D7',
+ '\u20DB',
+ '\u20DC',
+ '\u20E1',
+ '\u20E7',
+ '\u20E9',
+ '\u20F0',
+ '\u2CEF',
+ '\u2CF0',
+ '\u2CF1',
+ '\u2DE0',
+ '\u2DE1',
+ '\u2DE2',
+ '\u2DE3',
+ '\u2DE4',
+ '\u2DE5',
+ '\u2DE6',
+ '\u2DE7',
+ '\u2DE8',
+ '\u2DE9',
+ '\u2DEA',
+ '\u2DEB',
+ '\u2DEC',
+ '\u2DED',
+ '\u2DEE',
+ '\u2DEF',
+ '\u2DF0',
+ '\u2DF1',
+ '\u2DF2',
+ '\u2DF3',
+ '\u2DF4',
+ '\u2DF5',
+ '\u2DF6',
+ '\u2DF7',
+ '\u2DF8',
+ '\u2DF9',
+ '\u2DFA',
+ '\u2DFB',
+ '\u2DFC',
+ '\u2DFD',
+ '\u2DFE',
+ '\u2DFF',
+ '\uA66F',
+ '\uA67C',
+ '\uA67D',
+ '\uA6F0',
+ '\uA6F1',
+ '\uA8E0',
+ '\uA8E1',
+ '\uA8E2',
+ '\uA8E3',
+ '\uA8E4',
+ '\uA8E5',
+ '\uA8E6',
+ '\uA8E7',
+ '\uA8E8',
+ '\uA8E9',
+ '\uA8EA',
+ '\uA8EB',
+ '\uA8EC',
+ '\uA8ED',
+ '\uA8EE',
+ '\uA8EF',
+ '\uA8F0',
+ '\uA8F1',
+ '\uAAB0',
+ '\uAAB2',
+ '\uAAB3',
+ '\uAAB7',
+ '\uAAB8',
+ '\uAABE',
+ '\uAABF',
+ '\uAAC1',
+ '\uFE20',
+ '\uFE21',
+ '\uFE22',
+ '\uFE23',
+ '\uFE24',
+ '\uFE25',
+ '\uFE26',
+ '\U00010A0F',
+ '\U00010A38',
+ '\U0001D185',
+ '\U0001D186',
+ '\U0001D187',
+ '\U0001D188',
+ '\U0001D189',
+ '\U0001D1AA',
+ '\U0001D1AB',
+ '\U0001D1AC',
+ '\U0001D1AD',
+ '\U0001D242',
+ '\U0001D243',
+ '\U0001D244',
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/kitty/options.go b/vendor/github.com/charmbracelet/x/ansi/kitty/options.go
new file mode 100644
index 00000000..a8d907bd
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/kitty/options.go
@@ -0,0 +1,367 @@
+package kitty
+
+import (
+ "encoding"
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+var (
+ _ encoding.TextMarshaler = Options{}
+ _ encoding.TextUnmarshaler = &Options{}
+)
+
+// Options represents a Kitty Graphics Protocol options.
+type Options struct {
+ // Common options.
+
+ // Action (a=t) is the action to be performed on the image. Can be one of
+ // [Transmit], [TransmitDisplay], [Query], [Put], [Delete], [Frame],
+ // [Animate], [Compose].
+ Action byte
+
+ // Quite mode (q=0) is the quiet mode. Can be either zero, one, or two
+ // where zero is the default, 1 suppresses OK responses, and 2 suppresses
+ // both OK and error responses.
+ Quite byte
+
+ // Transmission options.
+
+ // ID (i=) is the image ID. The ID is a unique identifier for the image.
+ // Must be a positive integer up to [math.MaxUint32].
+ ID int
+
+ // PlacementID (p=) is the placement ID. The placement ID is a unique
+ // identifier for the placement of the image. Must be a positive integer up
+ // to [math.MaxUint32].
+ PlacementID int
+
+ // Number (I=0) is the number of images to be transmitted.
+ Number int
+
+ // Format (f=32) is the image format. One of [RGBA], [RGB], [PNG].
+ Format int
+
+ // ImageWidth (s=0) is the transmitted image width.
+ ImageWidth int
+
+ // ImageHeight (v=0) is the transmitted image height.
+ ImageHeight int
+
+ // Compression (o=) is the image compression type. Can be [Zlib] or zero.
+ Compression byte
+
+ // Transmission (t=d) is the image transmission type. Can be [Direct], [File],
+ // [TempFile], or[SharedMemory].
+ Transmission byte
+
+ // File is the file path to be used when the transmission type is [File].
+ // If [Options.Transmission] is omitted i.e. zero and this is non-empty,
+ // the transmission type is set to [File].
+ File string
+
+ // Size (S=0) is the size to be read from the transmission medium.
+ Size int
+
+ // Offset (O=0) is the offset byte to start reading from the transmission
+ // medium.
+ Offset int
+
+ // Chunk (m=) whether the image is transmitted in chunks. Can be either
+ // zero or one. When true, the image is transmitted in chunks. Each chunk
+ // must be a multiple of 4, and up to [MaxChunkSize] bytes. Each chunk must
+ // have the m=1 option except for the last chunk which must have m=0.
+ Chunk bool
+
+ // Display options.
+
+ // X (x=0) is the pixel X coordinate of the image to start displaying.
+ X int
+
+ // Y (y=0) is the pixel Y coordinate of the image to start displaying.
+ Y int
+
+ // Z (z=0) is the Z coordinate of the image to display.
+ Z int
+
+ // Width (w=0) is the width of the image to display.
+ Width int
+
+ // Height (h=0) is the height of the image to display.
+ Height int
+
+ // OffsetX (X=0) is the OffsetX coordinate of the cursor cell to start
+ // displaying the image. OffsetX=0 is the leftmost cell. This must be
+ // smaller than the terminal cell width.
+ OffsetX int
+
+ // OffsetY (Y=0) is the OffsetY coordinate of the cursor cell to start
+ // displaying the image. OffsetY=0 is the topmost cell. This must be
+ // smaller than the terminal cell height.
+ OffsetY int
+
+ // Columns (c=0) is the number of columns to display the image. The image
+ // will be scaled to fit the number of columns.
+ Columns int
+
+ // Rows (r=0) is the number of rows to display the image. The image will be
+ // scaled to fit the number of rows.
+ Rows int
+
+ // VirtualPlacement (U=0) whether to use virtual placement. This is used
+ // with Unicode [Placeholder] to display images.
+ VirtualPlacement bool
+
+ // DoNotMoveCursor (C=0) whether to move the cursor after displaying the
+ // image.
+ DoNotMoveCursor bool
+
+ // ParentID (P=0) is the parent image ID. The parent ID is the ID of the
+ // image that is the parent of the current image. This is used with Unicode
+ // [Placeholder] to display images relative to the parent image.
+ ParentID int
+
+ // ParentPlacementID (Q=0) is the parent placement ID. The parent placement
+ // ID is the ID of the placement of the parent image. This is used with
+ // Unicode [Placeholder] to display images relative to the parent image.
+ ParentPlacementID int
+
+ // Delete options.
+
+ // Delete (d=a) is the delete action. Can be one of [DeleteAll],
+ // [DeleteID], [DeleteNumber], [DeleteCursor], [DeleteFrames],
+ // [DeleteCell], [DeleteCellZ], [DeleteRange], [DeleteColumn], [DeleteRow],
+ // [DeleteZ].
+ Delete byte
+
+ // DeleteResources indicates whether to delete the resources associated
+ // with the image.
+ DeleteResources bool
+}
+
+// Options returns the options as a slice of a key-value pairs.
+func (o *Options) Options() (opts []string) {
+ opts = []string{}
+ if o.Format == 0 {
+ o.Format = RGBA
+ }
+
+ if o.Action == 0 {
+ o.Action = Transmit
+ }
+
+ if o.Delete == 0 {
+ o.Delete = DeleteAll
+ }
+
+ if o.Transmission == 0 {
+ if len(o.File) > 0 {
+ o.Transmission = File
+ } else {
+ o.Transmission = Direct
+ }
+ }
+
+ if o.Format != RGBA {
+ opts = append(opts, fmt.Sprintf("f=%d", o.Format))
+ }
+
+ if o.Quite > 0 {
+ opts = append(opts, fmt.Sprintf("q=%d", o.Quite))
+ }
+
+ if o.ID > 0 {
+ opts = append(opts, fmt.Sprintf("i=%d", o.ID))
+ }
+
+ if o.PlacementID > 0 {
+ opts = append(opts, fmt.Sprintf("p=%d", o.PlacementID))
+ }
+
+ if o.Number > 0 {
+ opts = append(opts, fmt.Sprintf("I=%d", o.Number))
+ }
+
+ if o.ImageWidth > 0 {
+ opts = append(opts, fmt.Sprintf("s=%d", o.ImageWidth))
+ }
+
+ if o.ImageHeight > 0 {
+ opts = append(opts, fmt.Sprintf("v=%d", o.ImageHeight))
+ }
+
+ if o.Transmission != Direct {
+ opts = append(opts, fmt.Sprintf("t=%c", o.Transmission))
+ }
+
+ if o.Size > 0 {
+ opts = append(opts, fmt.Sprintf("S=%d", o.Size))
+ }
+
+ if o.Offset > 0 {
+ opts = append(opts, fmt.Sprintf("O=%d", o.Offset))
+ }
+
+ if o.Compression == Zlib {
+ opts = append(opts, fmt.Sprintf("o=%c", o.Compression))
+ }
+
+ if o.VirtualPlacement {
+ opts = append(opts, "U=1")
+ }
+
+ if o.DoNotMoveCursor {
+ opts = append(opts, "C=1")
+ }
+
+ if o.ParentID > 0 {
+ opts = append(opts, fmt.Sprintf("P=%d", o.ParentID))
+ }
+
+ if o.ParentPlacementID > 0 {
+ opts = append(opts, fmt.Sprintf("Q=%d", o.ParentPlacementID))
+ }
+
+ if o.X > 0 {
+ opts = append(opts, fmt.Sprintf("x=%d", o.X))
+ }
+
+ if o.Y > 0 {
+ opts = append(opts, fmt.Sprintf("y=%d", o.Y))
+ }
+
+ if o.Z > 0 {
+ opts = append(opts, fmt.Sprintf("z=%d", o.Z))
+ }
+
+ if o.Width > 0 {
+ opts = append(opts, fmt.Sprintf("w=%d", o.Width))
+ }
+
+ if o.Height > 0 {
+ opts = append(opts, fmt.Sprintf("h=%d", o.Height))
+ }
+
+ if o.OffsetX > 0 {
+ opts = append(opts, fmt.Sprintf("X=%d", o.OffsetX))
+ }
+
+ if o.OffsetY > 0 {
+ opts = append(opts, fmt.Sprintf("Y=%d", o.OffsetY))
+ }
+
+ if o.Columns > 0 {
+ opts = append(opts, fmt.Sprintf("c=%d", o.Columns))
+ }
+
+ if o.Rows > 0 {
+ opts = append(opts, fmt.Sprintf("r=%d", o.Rows))
+ }
+
+ if o.Delete != DeleteAll || o.DeleteResources {
+ da := o.Delete
+ if o.DeleteResources {
+ da = da - ' ' // to uppercase
+ }
+
+ opts = append(opts, fmt.Sprintf("d=%c", da))
+ }
+
+ if o.Action != Transmit {
+ opts = append(opts, fmt.Sprintf("a=%c", o.Action))
+ }
+
+ return
+}
+
+// String returns the string representation of the options.
+func (o Options) String() string {
+ return strings.Join(o.Options(), ",")
+}
+
+// MarshalText returns the string representation of the options.
+func (o Options) MarshalText() ([]byte, error) {
+ return []byte(o.String()), nil
+}
+
+// UnmarshalText parses the options from the given string.
+func (o *Options) UnmarshalText(text []byte) error {
+ opts := strings.Split(string(text), ",")
+ for _, opt := range opts {
+ ps := strings.SplitN(opt, "=", 2)
+ if len(ps) != 2 || len(ps[1]) == 0 {
+ continue
+ }
+
+ switch ps[0] {
+ case "a":
+ o.Action = ps[1][0]
+ case "o":
+ o.Compression = ps[1][0]
+ case "t":
+ o.Transmission = ps[1][0]
+ case "d":
+ d := ps[1][0]
+ if d >= 'A' && d <= 'Z' {
+ o.DeleteResources = true
+ d = d + ' ' // to lowercase
+ }
+ o.Delete = d
+ case "i", "q", "p", "I", "f", "s", "v", "S", "O", "m", "x", "y", "z", "w", "h", "X", "Y", "c", "r", "U", "P", "Q":
+ v, err := strconv.Atoi(ps[1])
+ if err != nil {
+ continue
+ }
+
+ switch ps[0] {
+ case "i":
+ o.ID = v
+ case "q":
+ o.Quite = byte(v)
+ case "p":
+ o.PlacementID = v
+ case "I":
+ o.Number = v
+ case "f":
+ o.Format = v
+ case "s":
+ o.ImageWidth = v
+ case "v":
+ o.ImageHeight = v
+ case "S":
+ o.Size = v
+ case "O":
+ o.Offset = v
+ case "m":
+ o.Chunk = v == 0 || v == 1
+ case "x":
+ o.X = v
+ case "y":
+ o.Y = v
+ case "z":
+ o.Z = v
+ case "w":
+ o.Width = v
+ case "h":
+ o.Height = v
+ case "X":
+ o.OffsetX = v
+ case "Y":
+ o.OffsetY = v
+ case "c":
+ o.Columns = v
+ case "r":
+ o.Rows = v
+ case "U":
+ o.VirtualPlacement = v == 1
+ case "P":
+ o.ParentID = v
+ case "Q":
+ o.ParentPlacementID = v
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/method.go b/vendor/github.com/charmbracelet/x/ansi/method.go
new file mode 100644
index 00000000..0218809c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/method.go
@@ -0,0 +1,172 @@
+package ansi
+
+// Method is a type that represents the how the renderer should calculate the
+// display width of cells.
+type Method uint8
+
+// Display width modes.
+const (
+ WcWidth Method = iota
+ GraphemeWidth
+)
+
+// StringWidth returns the width of a string in cells. This is the number of
+// cells that the string will occupy when printed in a terminal. ANSI escape
+// codes are ignored and wide characters (such as East Asians and emojis) are
+// accounted for.
+func (m Method) StringWidth(s string) int {
+ return stringWidth(m, s)
+}
+
+// Truncate truncates a string to a given length, adding a tail to the end if
+// the string is longer than the given length. This function is aware of ANSI
+// escape codes and will not break them, and accounts for wide-characters (such
+// as East-Asian characters and emojis).
+func (m Method) Truncate(s string, length int, tail string) string {
+ return truncate(m, s, length, tail)
+}
+
+// TruncateLeft truncates a string to a given length, adding a prefix to the
+// beginning if the string is longer than the given length. This function is
+// aware of ANSI escape codes and will not break them, and accounts for
+// wide-characters (such as East-Asian characters and emojis).
+func (m Method) TruncateLeft(s string, length int, prefix string) string {
+ return truncateLeft(m, s, length, prefix)
+}
+
+// Cut the string, without adding any prefix or tail strings. This function is
+// aware of ANSI escape codes and will not break them, and accounts for
+// wide-characters (such as East-Asian characters and emojis). Note that the
+// [left] parameter is inclusive, while [right] isn't.
+func (m Method) Cut(s string, left, right int) string {
+ return cut(m, s, left, right)
+}
+
+// Hardwrap wraps a string or a block of text to a given line length, breaking
+// word boundaries. This will preserve ANSI escape codes and will account for
+// wide-characters in the string.
+// When preserveSpace is true, spaces at the beginning of a line will be
+// preserved.
+// This treats the text as a sequence of graphemes.
+func (m Method) Hardwrap(s string, length int, preserveSpace bool) string {
+ return hardwrap(m, s, length, preserveSpace)
+}
+
+// Wordwrap wraps a string or a block of text to a given line length, not
+// breaking word boundaries. This will preserve ANSI escape codes and will
+// account for wide-characters in the string.
+// The breakpoints string is a list of characters that are considered
+// breakpoints for word wrapping. A hyphen (-) is always considered a
+// breakpoint.
+//
+// Note: breakpoints must be a string of 1-cell wide rune characters.
+func (m Method) Wordwrap(s string, length int, breakpoints string) string {
+ return wordwrap(m, s, length, breakpoints)
+}
+
+// Wrap wraps a string or a block of text to a given line length, breaking word
+// boundaries if necessary. This will preserve ANSI escape codes and will
+// account for wide-characters in the string. The breakpoints string is a list
+// of characters that are considered breakpoints for word wrapping. A hyphen
+// (-) is always considered a breakpoint.
+//
+// Note: breakpoints must be a string of 1-cell wide rune characters.
+func (m Method) Wrap(s string, length int, breakpoints string) string {
+ return wrap(m, s, length, breakpoints)
+}
+
+// DecodeSequence decodes the first ANSI escape sequence or a printable
+// grapheme from the given data. It returns the sequence slice, the number of
+// bytes read, the cell width for each sequence, and the new state.
+//
+// The cell width will always be 0 for control and escape sequences, 1 for
+// ASCII printable characters, and the number of cells other Unicode characters
+// occupy. It uses the uniseg package to calculate the width of Unicode
+// graphemes and characters. This means it will always do grapheme clustering
+// (mode 2027).
+//
+// Passing a non-nil [*Parser] as the last argument will allow the decoder to
+// collect sequence parameters, data, and commands. The parser cmd will have
+// the packed command value that contains intermediate and prefix characters.
+// In the case of a OSC sequence, the cmd will be the OSC command number. Use
+// [Cmd] and [Param] types to unpack command intermediates and prefixes as well
+// as parameters.
+//
+// Zero [Cmd] means the CSI, DCS, or ESC sequence is invalid. Moreover, checking the
+// validity of other data sequences, OSC, DCS, etc, will require checking for
+// the returned sequence terminator bytes such as ST (ESC \\) and BEL).
+//
+// We store the command byte in [Cmd] in the most significant byte, the
+// prefix byte in the next byte, and the intermediate byte in the least
+// significant byte. This is done to avoid using a struct to store the command
+// and its intermediates and prefixes. The command byte is always the least
+// significant byte i.e. [Cmd & 0xff]. Use the [Cmd] type to unpack the
+// command, intermediate, and prefix bytes. Note that we only collect the last
+// prefix character and intermediate byte.
+//
+// The [p.Params] slice will contain the parameters of the sequence. Any
+// sub-parameter will have the [parser.HasMoreFlag] set. Use the [Param] type
+// to unpack the parameters.
+//
+// Example:
+//
+// var state byte // the initial state is always zero [NormalState]
+// p := NewParser(32, 1024) // create a new parser with a 32 params buffer and 1024 data buffer (optional)
+// input := []byte("\x1b[31mHello, World!\x1b[0m")
+// for len(input) > 0 {
+// seq, width, n, newState := DecodeSequence(input, state, p)
+// log.Printf("seq: %q, width: %d", seq, width)
+// state = newState
+// input = input[n:]
+// }
+func (m Method) DecodeSequence(data []byte, state byte, p *Parser) (seq []byte, width, n int, newState byte) {
+ return decodeSequence(m, data, state, p)
+}
+
+// DecodeSequenceInString decodes the first ANSI escape sequence or a printable
+// grapheme from the given data. It returns the sequence slice, the number of
+// bytes read, the cell width for each sequence, and the new state.
+//
+// The cell width will always be 0 for control and escape sequences, 1 for
+// ASCII printable characters, and the number of cells other Unicode characters
+// occupy. It uses the uniseg package to calculate the width of Unicode
+// graphemes and characters. This means it will always do grapheme clustering
+// (mode 2027).
+//
+// Passing a non-nil [*Parser] as the last argument will allow the decoder to
+// collect sequence parameters, data, and commands. The parser cmd will have
+// the packed command value that contains intermediate and prefix characters.
+// In the case of a OSC sequence, the cmd will be the OSC command number. Use
+// [Cmd] and [Param] types to unpack command intermediates and prefixes as well
+// as parameters.
+//
+// Zero [Cmd] means the CSI, DCS, or ESC sequence is invalid. Moreover, checking the
+// validity of other data sequences, OSC, DCS, etc, will require checking for
+// the returned sequence terminator bytes such as ST (ESC \\) and BEL).
+//
+// We store the command byte in [Cmd] in the most significant byte, the
+// prefix byte in the next byte, and the intermediate byte in the least
+// significant byte. This is done to avoid using a struct to store the command
+// and its intermediates and prefixes. The command byte is always the least
+// significant byte i.e. [Cmd & 0xff]. Use the [Cmd] type to unpack the
+// command, intermediate, and prefix bytes. Note that we only collect the last
+// prefix character and intermediate byte.
+//
+// The [p.Params] slice will contain the parameters of the sequence. Any
+// sub-parameter will have the [parser.HasMoreFlag] set. Use the [Param] type
+// to unpack the parameters.
+//
+// Example:
+//
+// var state byte // the initial state is always zero [NormalState]
+// p := NewParser(32, 1024) // create a new parser with a 32 params buffer and 1024 data buffer (optional)
+// input := []byte("\x1b[31mHello, World!\x1b[0m")
+// for len(input) > 0 {
+// seq, width, n, newState := DecodeSequenceInString(input, state, p)
+// log.Printf("seq: %q, width: %d", seq, width)
+// state = newState
+// input = input[n:]
+// }
+func (m Method) DecodeSequenceInString(data string, state byte, p *Parser) (seq string, width, n int, newState byte) {
+ return decodeSequence(m, data, state, p)
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/mode.go b/vendor/github.com/charmbracelet/x/ansi/mode.go
new file mode 100644
index 00000000..57f3f0a8
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/mode.go
@@ -0,0 +1,763 @@
+package ansi
+
+import (
+ "strconv"
+ "strings"
+)
+
+// ModeSetting represents a mode setting.
+type ModeSetting byte
+
+// ModeSetting constants.
+const (
+ ModeNotRecognized ModeSetting = iota
+ ModeSet
+ ModeReset
+ ModePermanentlySet
+ ModePermanentlyReset
+)
+
+// IsNotRecognized returns true if the mode is not recognized.
+func (m ModeSetting) IsNotRecognized() bool {
+ return m == ModeNotRecognized
+}
+
+// IsSet returns true if the mode is set or permanently set.
+func (m ModeSetting) IsSet() bool {
+ return m == ModeSet || m == ModePermanentlySet
+}
+
+// IsReset returns true if the mode is reset or permanently reset.
+func (m ModeSetting) IsReset() bool {
+ return m == ModeReset || m == ModePermanentlyReset
+}
+
+// IsPermanentlySet returns true if the mode is permanently set.
+func (m ModeSetting) IsPermanentlySet() bool {
+ return m == ModePermanentlySet
+}
+
+// IsPermanentlyReset returns true if the mode is permanently reset.
+func (m ModeSetting) IsPermanentlyReset() bool {
+ return m == ModePermanentlyReset
+}
+
+// Mode represents an interface for terminal modes.
+// Modes can be set, reset, and requested.
+type Mode interface {
+ Mode() int
+}
+
+// SetMode (SM) returns a sequence to set a mode.
+// The mode arguments are a list of modes to set.
+//
+// If one of the modes is a [DECMode], the function will returns two escape
+// sequences.
+//
+// ANSI format:
+//
+// CSI Pd ; ... ; Pd h
+//
+// DEC format:
+//
+// CSI ? Pd ; ... ; Pd h
+//
+// See: https://vt100.net/docs/vt510-rm/SM.html
+func SetMode(modes ...Mode) string {
+ return setMode(false, modes...)
+}
+
+// SM is an alias for [SetMode].
+func SM(modes ...Mode) string {
+ return SetMode(modes...)
+}
+
+// ResetMode (RM) returns a sequence to reset a mode.
+// The mode arguments are a list of modes to reset.
+//
+// If one of the modes is a [DECMode], the function will returns two escape
+// sequences.
+//
+// ANSI format:
+//
+// CSI Pd ; ... ; Pd l
+//
+// DEC format:
+//
+// CSI ? Pd ; ... ; Pd l
+//
+// See: https://vt100.net/docs/vt510-rm/RM.html
+func ResetMode(modes ...Mode) string {
+ return setMode(true, modes...)
+}
+
+// RM is an alias for [ResetMode].
+func RM(modes ...Mode) string {
+ return ResetMode(modes...)
+}
+
+func setMode(reset bool, modes ...Mode) (s string) {
+ if len(modes) == 0 {
+ return
+ }
+
+ cmd := "h"
+ if reset {
+ cmd = "l"
+ }
+
+ seq := "\x1b["
+ if len(modes) == 1 {
+ switch modes[0].(type) {
+ case DECMode:
+ seq += "?"
+ }
+ return seq + strconv.Itoa(modes[0].Mode()) + cmd
+ }
+
+ dec := make([]string, 0, len(modes)/2)
+ ansi := make([]string, 0, len(modes)/2)
+ for _, m := range modes {
+ switch m.(type) {
+ case DECMode:
+ dec = append(dec, strconv.Itoa(m.Mode()))
+ case ANSIMode:
+ ansi = append(ansi, strconv.Itoa(m.Mode()))
+ }
+ }
+
+ if len(ansi) > 0 {
+ s += seq + strings.Join(ansi, ";") + cmd
+ }
+ if len(dec) > 0 {
+ s += seq + "?" + strings.Join(dec, ";") + cmd
+ }
+ return
+}
+
+// RequestMode (DECRQM) returns a sequence to request a mode from the terminal.
+// The terminal responds with a report mode function [DECRPM].
+//
+// ANSI format:
+//
+// CSI Pa $ p
+//
+// DEC format:
+//
+// CSI ? Pa $ p
+//
+// See: https://vt100.net/docs/vt510-rm/DECRQM.html
+func RequestMode(m Mode) string {
+ seq := "\x1b["
+ switch m.(type) {
+ case DECMode:
+ seq += "?"
+ }
+ return seq + strconv.Itoa(m.Mode()) + "$p"
+}
+
+// DECRQM is an alias for [RequestMode].
+func DECRQM(m Mode) string {
+ return RequestMode(m)
+}
+
+// ReportMode (DECRPM) returns a sequence that the terminal sends to the host
+// in response to a mode request [DECRQM].
+//
+// ANSI format:
+//
+// CSI Pa ; Ps ; $ y
+//
+// DEC format:
+//
+// CSI ? Pa ; Ps $ y
+//
+// Where Pa is the mode number, and Ps is the mode value.
+//
+// 0: Not recognized
+// 1: Set
+// 2: Reset
+// 3: Permanent set
+// 4: Permanent reset
+//
+// See: https://vt100.net/docs/vt510-rm/DECRPM.html
+func ReportMode(mode Mode, value ModeSetting) string {
+ if value > 4 {
+ value = 0
+ }
+ switch mode.(type) {
+ case DECMode:
+ return "\x1b[?" + strconv.Itoa(mode.Mode()) + ";" + strconv.Itoa(int(value)) + "$y"
+ }
+ return "\x1b[" + strconv.Itoa(mode.Mode()) + ";" + strconv.Itoa(int(value)) + "$y"
+}
+
+// DECRPM is an alias for [ReportMode].
+func DECRPM(mode Mode, value ModeSetting) string {
+ return ReportMode(mode, value)
+}
+
+// ANSIMode represents an ANSI terminal mode.
+type ANSIMode int //nolint:revive
+
+// Mode returns the ANSI mode as an integer.
+func (m ANSIMode) Mode() int {
+ return int(m)
+}
+
+// DECMode represents a private DEC terminal mode.
+type DECMode int
+
+// Mode returns the DEC mode as an integer.
+func (m DECMode) Mode() int {
+ return int(m)
+}
+
+// Keyboard Action Mode (KAM) is a mode that controls locking of the keyboard.
+// When the keyboard is locked, it cannot send data to the terminal.
+//
+// See: https://vt100.net/docs/vt510-rm/KAM.html
+const (
+ KeyboardActionMode = ANSIMode(2)
+ KAM = KeyboardActionMode
+
+ SetKeyboardActionMode = "\x1b[2h"
+ ResetKeyboardActionMode = "\x1b[2l"
+ RequestKeyboardActionMode = "\x1b[2$p"
+)
+
+// Insert/Replace Mode (IRM) is a mode that determines whether characters are
+// inserted or replaced when typed.
+//
+// When enabled, characters are inserted at the cursor position pushing the
+// characters to the right. When disabled, characters replace the character at
+// the cursor position.
+//
+// See: https://vt100.net/docs/vt510-rm/IRM.html
+const (
+ InsertReplaceMode = ANSIMode(4)
+ IRM = InsertReplaceMode
+
+ SetInsertReplaceMode = "\x1b[4h"
+ ResetInsertReplaceMode = "\x1b[4l"
+ RequestInsertReplaceMode = "\x1b[4$p"
+)
+
+// Send Receive Mode (SRM) or Local Echo Mode is a mode that determines whether
+// the terminal echoes characters back to the host. When enabled, the terminal
+// sends characters to the host as they are typed.
+//
+// See: https://vt100.net/docs/vt510-rm/SRM.html
+const (
+ SendReceiveMode = ANSIMode(12)
+ LocalEchoMode = SendReceiveMode
+ SRM = SendReceiveMode
+
+ SetSendReceiveMode = "\x1b[12h"
+ ResetSendReceiveMode = "\x1b[12l"
+ RequestSendReceiveMode = "\x1b[12$p"
+
+ SetLocalEchoMode = "\x1b[12h"
+ ResetLocalEchoMode = "\x1b[12l"
+ RequestLocalEchoMode = "\x1b[12$p"
+)
+
+// Line Feed/New Line Mode (LNM) is a mode that determines whether the terminal
+// interprets the line feed character as a new line.
+//
+// When enabled, the terminal interprets the line feed character as a new line.
+// When disabled, the terminal interprets the line feed character as a line feed.
+//
+// A new line moves the cursor to the first position of the next line.
+// A line feed moves the cursor down one line without changing the column
+// scrolling the screen if necessary.
+//
+// See: https://vt100.net/docs/vt510-rm/LNM.html
+const (
+ LineFeedNewLineMode = ANSIMode(20)
+ LNM = LineFeedNewLineMode
+
+ SetLineFeedNewLineMode = "\x1b[20h"
+ ResetLineFeedNewLineMode = "\x1b[20l"
+ RequestLineFeedNewLineMode = "\x1b[20$p"
+)
+
+// Cursor Keys Mode (DECCKM) is a mode that determines whether the cursor keys
+// send ANSI cursor sequences or application sequences.
+//
+// See: https://vt100.net/docs/vt510-rm/DECCKM.html
+const (
+ CursorKeysMode = DECMode(1)
+ DECCKM = CursorKeysMode
+
+ SetCursorKeysMode = "\x1b[?1h"
+ ResetCursorKeysMode = "\x1b[?1l"
+ RequestCursorKeysMode = "\x1b[?1$p"
+)
+
+// Deprecated: use [SetCursorKeysMode] and [ResetCursorKeysMode] instead.
+const (
+ EnableCursorKeys = "\x1b[?1h"
+ DisableCursorKeys = "\x1b[?1l"
+)
+
+// Origin Mode (DECOM) is a mode that determines whether the cursor moves to the
+// home position or the margin position.
+//
+// See: https://vt100.net/docs/vt510-rm/DECOM.html
+const (
+ OriginMode = DECMode(6)
+ DECOM = OriginMode
+
+ SetOriginMode = "\x1b[?6h"
+ ResetOriginMode = "\x1b[?6l"
+ RequestOriginMode = "\x1b[?6$p"
+)
+
+// Auto Wrap Mode (DECAWM) is a mode that determines whether the cursor wraps
+// to the next line when it reaches the right margin.
+//
+// See: https://vt100.net/docs/vt510-rm/DECAWM.html
+const (
+ AutoWrapMode = DECMode(7)
+ DECAWM = AutoWrapMode
+
+ SetAutoWrapMode = "\x1b[?7h"
+ ResetAutoWrapMode = "\x1b[?7l"
+ RequestAutoWrapMode = "\x1b[?7$p"
+)
+
+// X10 Mouse Mode is a mode that determines whether the mouse reports on button
+// presses.
+//
+// The terminal responds with the following encoding:
+//
+// CSI M CbCxCy
+//
+// Where Cb is the button-1, where it can be 1, 2, or 3.
+// Cx and Cy are the x and y coordinates of the mouse event.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+const (
+ X10MouseMode = DECMode(9)
+
+ SetX10MouseMode = "\x1b[?9h"
+ ResetX10MouseMode = "\x1b[?9l"
+ RequestX10MouseMode = "\x1b[?9$p"
+)
+
+// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
+//
+// See: https://vt100.net/docs/vt510-rm/DECTCEM.html
+const (
+ TextCursorEnableMode = DECMode(25)
+ DECTCEM = TextCursorEnableMode
+
+ SetTextCursorEnableMode = "\x1b[?25h"
+ ResetTextCursorEnableMode = "\x1b[?25l"
+ RequestTextCursorEnableMode = "\x1b[?25$p"
+)
+
+// These are aliases for [SetTextCursorEnableMode] and [ResetTextCursorEnableMode].
+const (
+ ShowCursor = SetTextCursorEnableMode
+ HideCursor = ResetTextCursorEnableMode
+)
+
+// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
+//
+// See: https://vt100.net/docs/vt510-rm/DECTCEM.html
+//
+// Deprecated: use [SetTextCursorEnableMode] and [ResetTextCursorEnableMode] instead.
+const (
+ CursorEnableMode = DECMode(25)
+ RequestCursorVisibility = "\x1b[?25$p"
+)
+
+// Numeric Keypad Mode (DECNKM) is a mode that determines whether the keypad
+// sends application sequences or numeric sequences.
+//
+// This works like [DECKPAM] and [DECKPNM], but uses different sequences.
+//
+// See: https://vt100.net/docs/vt510-rm/DECNKM.html
+const (
+ NumericKeypadMode = DECMode(66)
+ DECNKM = NumericKeypadMode
+
+ SetNumericKeypadMode = "\x1b[?66h"
+ ResetNumericKeypadMode = "\x1b[?66l"
+ RequestNumericKeypadMode = "\x1b[?66$p"
+)
+
+// Backarrow Key Mode (DECBKM) is a mode that determines whether the backspace
+// key sends a backspace or delete character. Disabled by default.
+//
+// See: https://vt100.net/docs/vt510-rm/DECBKM.html
+const (
+ BackarrowKeyMode = DECMode(67)
+ DECBKM = BackarrowKeyMode
+
+ SetBackarrowKeyMode = "\x1b[?67h"
+ ResetBackarrowKeyMode = "\x1b[?67l"
+ RequestBackarrowKeyMode = "\x1b[?67$p"
+)
+
+// Left Right Margin Mode (DECLRMM) is a mode that determines whether the left
+// and right margins can be set with [DECSLRM].
+//
+// See: https://vt100.net/docs/vt510-rm/DECLRMM.html
+const (
+ LeftRightMarginMode = DECMode(69)
+ DECLRMM = LeftRightMarginMode
+
+ SetLeftRightMarginMode = "\x1b[?69h"
+ ResetLeftRightMarginMode = "\x1b[?69l"
+ RequestLeftRightMarginMode = "\x1b[?69$p"
+)
+
+// Normal Mouse Mode is a mode that determines whether the mouse reports on
+// button presses and releases. It will also report modifier keys, wheel
+// events, and extra buttons.
+//
+// It uses the same encoding as [X10MouseMode] with a few differences:
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+const (
+ NormalMouseMode = DECMode(1000)
+
+ SetNormalMouseMode = "\x1b[?1000h"
+ ResetNormalMouseMode = "\x1b[?1000l"
+ RequestNormalMouseMode = "\x1b[?1000$p"
+)
+
+// VT Mouse Tracking is a mode that determines whether the mouse reports on
+// button press and release.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+//
+// Deprecated: use [NormalMouseMode] instead.
+const (
+ MouseMode = DECMode(1000)
+
+ EnableMouse = "\x1b[?1000h"
+ DisableMouse = "\x1b[?1000l"
+ RequestMouse = "\x1b[?1000$p"
+)
+
+// Highlight Mouse Tracking is a mode that determines whether the mouse reports
+// on button presses, releases, and highlighted cells.
+//
+// It uses the same encoding as [NormalMouseMode] with a few differences:
+//
+// On highlight events, the terminal responds with the following encoding:
+//
+// CSI t CxCy
+// CSI T CxCyCxCyCxCy
+//
+// Where the parameters are startx, starty, endx, endy, mousex, and mousey.
+const (
+ HighlightMouseMode = DECMode(1001)
+
+ SetHighlightMouseMode = "\x1b[?1001h"
+ ResetHighlightMouseMode = "\x1b[?1001l"
+ RequestHighlightMouseMode = "\x1b[?1001$p"
+)
+
+// VT Hilite Mouse Tracking is a mode that determines whether the mouse reports on
+// button presses, releases, and highlighted cells.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+//
+// Deprecated: use [HighlightMouseMode] instead.
+const (
+ MouseHiliteMode = DECMode(1001)
+
+ EnableMouseHilite = "\x1b[?1001h"
+ DisableMouseHilite = "\x1b[?1001l"
+ RequestMouseHilite = "\x1b[?1001$p"
+)
+
+// Button Event Mouse Tracking is essentially the same as [NormalMouseMode],
+// but it also reports button-motion events when a button is pressed.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+const (
+ ButtonEventMouseMode = DECMode(1002)
+
+ SetButtonEventMouseMode = "\x1b[?1002h"
+ ResetButtonEventMouseMode = "\x1b[?1002l"
+ RequestButtonEventMouseMode = "\x1b[?1002$p"
+)
+
+// Cell Motion Mouse Tracking is a mode that determines whether the mouse
+// reports on button press, release, and motion events.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+//
+// Deprecated: use [ButtonEventMouseMode] instead.
+const (
+ MouseCellMotionMode = DECMode(1002)
+
+ EnableMouseCellMotion = "\x1b[?1002h"
+ DisableMouseCellMotion = "\x1b[?1002l"
+ RequestMouseCellMotion = "\x1b[?1002$p"
+)
+
+// Any Event Mouse Tracking is the same as [ButtonEventMouseMode], except that
+// all motion events are reported even if no mouse buttons are pressed.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+const (
+ AnyEventMouseMode = DECMode(1003)
+
+ SetAnyEventMouseMode = "\x1b[?1003h"
+ ResetAnyEventMouseMode = "\x1b[?1003l"
+ RequestAnyEventMouseMode = "\x1b[?1003$p"
+)
+
+// All Mouse Tracking is a mode that determines whether the mouse reports on
+// button press, release, motion, and highlight events.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+//
+// Deprecated: use [AnyEventMouseMode] instead.
+const (
+ MouseAllMotionMode = DECMode(1003)
+
+ EnableMouseAllMotion = "\x1b[?1003h"
+ DisableMouseAllMotion = "\x1b[?1003l"
+ RequestMouseAllMotion = "\x1b[?1003$p"
+)
+
+// Focus Event Mode is a mode that determines whether the terminal reports focus
+// and blur events.
+//
+// The terminal sends the following encoding:
+//
+// CSI I // Focus In
+// CSI O // Focus Out
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Focus-Tracking
+const (
+ FocusEventMode = DECMode(1004)
+
+ SetFocusEventMode = "\x1b[?1004h"
+ ResetFocusEventMode = "\x1b[?1004l"
+ RequestFocusEventMode = "\x1b[?1004$p"
+)
+
+// Deprecated: use [SetFocusEventMode], [ResetFocusEventMode], and
+// [RequestFocusEventMode] instead.
+const (
+ ReportFocusMode = DECMode(1004)
+
+ EnableReportFocus = "\x1b[?1004h"
+ DisableReportFocus = "\x1b[?1004l"
+ RequestReportFocus = "\x1b[?1004$p"
+)
+
+// SGR Extended Mouse Mode is a mode that changes the mouse tracking encoding
+// to use SGR parameters.
+//
+// The terminal responds with the following encoding:
+//
+// CSI < Cb ; Cx ; Cy M
+//
+// Where Cb is the same as [NormalMouseMode], and Cx and Cy are the x and y.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+const (
+ SgrExtMouseMode = DECMode(1006)
+
+ SetSgrExtMouseMode = "\x1b[?1006h"
+ ResetSgrExtMouseMode = "\x1b[?1006l"
+ RequestSgrExtMouseMode = "\x1b[?1006$p"
+)
+
+// Deprecated: use [SgrExtMouseMode] [SetSgrExtMouseMode],
+// [ResetSgrExtMouseMode], and [RequestSgrExtMouseMode] instead.
+const (
+ MouseSgrExtMode = DECMode(1006)
+ EnableMouseSgrExt = "\x1b[?1006h"
+ DisableMouseSgrExt = "\x1b[?1006l"
+ RequestMouseSgrExt = "\x1b[?1006$p"
+)
+
+// UTF-8 Extended Mouse Mode is a mode that changes the mouse tracking encoding
+// to use UTF-8 parameters.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+const (
+ Utf8ExtMouseMode = DECMode(1005)
+
+ SetUtf8ExtMouseMode = "\x1b[?1005h"
+ ResetUtf8ExtMouseMode = "\x1b[?1005l"
+ RequestUtf8ExtMouseMode = "\x1b[?1005$p"
+)
+
+// URXVT Extended Mouse Mode is a mode that changes the mouse tracking encoding
+// to use an alternate encoding.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+const (
+ UrxvtExtMouseMode = DECMode(1015)
+
+ SetUrxvtExtMouseMode = "\x1b[?1015h"
+ ResetUrxvtExtMouseMode = "\x1b[?1015l"
+ RequestUrxvtExtMouseMode = "\x1b[?1015$p"
+)
+
+// SGR Pixel Extended Mouse Mode is a mode that changes the mouse tracking
+// encoding to use SGR parameters with pixel coordinates.
+//
+// This is similar to [SgrExtMouseMode], but also reports pixel coordinates.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
+const (
+ SgrPixelExtMouseMode = DECMode(1016)
+
+ SetSgrPixelExtMouseMode = "\x1b[?1016h"
+ ResetSgrPixelExtMouseMode = "\x1b[?1016l"
+ RequestSgrPixelExtMouseMode = "\x1b[?1016$p"
+)
+
+// Alternate Screen Mode is a mode that determines whether the alternate screen
+// buffer is active. When this mode is enabled, the alternate screen buffer is
+// cleared.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
+const (
+ AltScreenMode = DECMode(1047)
+
+ SetAltScreenMode = "\x1b[?1047h"
+ ResetAltScreenMode = "\x1b[?1047l"
+ RequestAltScreenMode = "\x1b[?1047$p"
+)
+
+// Save Cursor Mode is a mode that saves the cursor position.
+// This is equivalent to [SaveCursor] and [RestoreCursor].
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
+const (
+ SaveCursorMode = DECMode(1048)
+
+ SetSaveCursorMode = "\x1b[?1048h"
+ ResetSaveCursorMode = "\x1b[?1048l"
+ RequestSaveCursorMode = "\x1b[?1048$p"
+)
+
+// Alternate Screen Save Cursor Mode is a mode that saves the cursor position as in
+// [SaveCursorMode], switches to the alternate screen buffer as in [AltScreenMode],
+// and clears the screen on switch.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
+const (
+ AltScreenSaveCursorMode = DECMode(1049)
+
+ SetAltScreenSaveCursorMode = "\x1b[?1049h"
+ ResetAltScreenSaveCursorMode = "\x1b[?1049l"
+ RequestAltScreenSaveCursorMode = "\x1b[?1049$p"
+)
+
+// Alternate Screen Buffer is a mode that determines whether the alternate screen
+// buffer is active.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
+//
+// Deprecated: use [AltScreenSaveCursorMode] instead.
+const (
+ AltScreenBufferMode = DECMode(1049)
+
+ SetAltScreenBufferMode = "\x1b[?1049h"
+ ResetAltScreenBufferMode = "\x1b[?1049l"
+ RequestAltScreenBufferMode = "\x1b[?1049$p"
+
+ EnableAltScreenBuffer = "\x1b[?1049h"
+ DisableAltScreenBuffer = "\x1b[?1049l"
+ RequestAltScreenBuffer = "\x1b[?1049$p"
+)
+
+// Bracketed Paste Mode is a mode that determines whether pasted text is
+// bracketed with escape sequences.
+//
+// See: https://cirw.in/blog/bracketed-paste
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode
+const (
+ BracketedPasteMode = DECMode(2004)
+
+ SetBracketedPasteMode = "\x1b[?2004h"
+ ResetBracketedPasteMode = "\x1b[?2004l"
+ RequestBracketedPasteMode = "\x1b[?2004$p"
+)
+
+// Deprecated: use [SetBracketedPasteMode], [ResetBracketedPasteMode], and
+// [RequestBracketedPasteMode] instead.
+const (
+ EnableBracketedPaste = "\x1b[?2004h"
+ DisableBracketedPaste = "\x1b[?2004l"
+ RequestBracketedPaste = "\x1b[?2004$p"
+)
+
+// Synchronized Output Mode is a mode that determines whether output is
+// synchronized with the terminal.
+//
+// See: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
+const (
+ SynchronizedOutputMode = DECMode(2026)
+
+ SetSynchronizedOutputMode = "\x1b[?2026h"
+ ResetSynchronizedOutputMode = "\x1b[?2026l"
+ RequestSynchronizedOutputMode = "\x1b[?2026$p"
+)
+
+// Deprecated: use [SynchronizedOutputMode], [SetSynchronizedOutputMode], and
+// [ResetSynchronizedOutputMode], and [RequestSynchronizedOutputMode] instead.
+const (
+ SyncdOutputMode = DECMode(2026)
+
+ EnableSyncdOutput = "\x1b[?2026h"
+ DisableSyncdOutput = "\x1b[?2026l"
+ RequestSyncdOutput = "\x1b[?2026$p"
+)
+
+// Grapheme Clustering Mode is a mode that determines whether the terminal
+// should look for grapheme clusters instead of single runes in the rendered
+// text. This makes the terminal properly render combining characters such as
+// emojis.
+//
+// See: https://github.com/contour-terminal/terminal-unicode-core
+const (
+ GraphemeClusteringMode = DECMode(2027)
+
+ SetGraphemeClusteringMode = "\x1b[?2027h"
+ ResetGraphemeClusteringMode = "\x1b[?2027l"
+ RequestGraphemeClusteringMode = "\x1b[?2027$p"
+)
+
+// Deprecated: use [SetGraphemeClusteringMode], [ResetGraphemeClusteringMode], and
+// [RequestGraphemeClusteringMode] instead.
+const (
+ EnableGraphemeClustering = "\x1b[?2027h"
+ DisableGraphemeClustering = "\x1b[?2027l"
+ RequestGraphemeClustering = "\x1b[?2027$p"
+)
+
+// Win32Input is a mode that determines whether input is processed by the
+// Win32 console and Conpty.
+//
+// See: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md
+const (
+ Win32InputMode = DECMode(9001)
+
+ SetWin32InputMode = "\x1b[?9001h"
+ ResetWin32InputMode = "\x1b[?9001l"
+ RequestWin32InputMode = "\x1b[?9001$p"
+)
+
+// Deprecated: use [SetWin32InputMode], [ResetWin32InputMode], and
+// [RequestWin32InputMode] instead.
+const (
+ EnableWin32Input = "\x1b[?9001h"
+ DisableWin32Input = "\x1b[?9001l"
+ RequestWin32Input = "\x1b[?9001$p"
+)
diff --git a/vendor/github.com/charmbracelet/x/ansi/modes.go b/vendor/github.com/charmbracelet/x/ansi/modes.go
new file mode 100644
index 00000000..1bec5bc8
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/modes.go
@@ -0,0 +1,71 @@
+package ansi
+
+// Modes represents the terminal modes that can be set or reset. By default,
+// all modes are [ModeNotRecognized].
+type Modes map[Mode]ModeSetting
+
+// NewModes creates a new Modes map. By default, all modes are
+// [ModeNotRecognized].
+func NewModes() Modes {
+ return make(Modes)
+}
+
+// Get returns the setting of a terminal mode. If the mode is not set, it
+// returns [ModeNotRecognized].
+func (m Modes) Get(mode Mode) ModeSetting {
+ return m[mode]
+}
+
+// Delete deletes a terminal mode. This has the same effect as setting the mode
+// to [ModeNotRecognized].
+func (m Modes) Delete(mode Mode) {
+ delete(m, mode)
+}
+
+// Set sets a terminal mode to [ModeSet].
+func (m Modes) Set(modes ...Mode) {
+ for _, mode := range modes {
+ m[mode] = ModeSet
+ }
+}
+
+// PermanentlySet sets a terminal mode to [ModePermanentlySet].
+func (m Modes) PermanentlySet(modes ...Mode) {
+ for _, mode := range modes {
+ m[mode] = ModePermanentlySet
+ }
+}
+
+// Reset sets a terminal mode to [ModeReset].
+func (m Modes) Reset(modes ...Mode) {
+ for _, mode := range modes {
+ m[mode] = ModeReset
+ }
+}
+
+// PermanentlyReset sets a terminal mode to [ModePermanentlyReset].
+func (m Modes) PermanentlyReset(modes ...Mode) {
+ for _, mode := range modes {
+ m[mode] = ModePermanentlyReset
+ }
+}
+
+// IsSet returns true if the mode is set to [ModeSet] or [ModePermanentlySet].
+func (m Modes) IsSet(mode Mode) bool {
+ return m[mode].IsSet()
+}
+
+// IsPermanentlySet returns true if the mode is set to [ModePermanentlySet].
+func (m Modes) IsPermanentlySet(mode Mode) bool {
+ return m[mode].IsPermanentlySet()
+}
+
+// IsReset returns true if the mode is set to [ModeReset] or [ModePermanentlyReset].
+func (m Modes) IsReset(mode Mode) bool {
+ return m[mode].IsReset()
+}
+
+// IsPermanentlyReset returns true if the mode is set to [ModePermanentlyReset].
+func (m Modes) IsPermanentlyReset(mode Mode) bool {
+ return m[mode].IsPermanentlyReset()
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/mouse.go b/vendor/github.com/charmbracelet/x/ansi/mouse.go
new file mode 100644
index 00000000..95b0127f
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/mouse.go
@@ -0,0 +1,172 @@
+package ansi
+
+import (
+ "fmt"
+)
+
+// MouseButton represents the button that was pressed during a mouse message.
+type MouseButton byte
+
+// Mouse event buttons
+//
+// This is based on X11 mouse button codes.
+//
+// 1 = left button
+// 2 = middle button (pressing the scroll wheel)
+// 3 = right button
+// 4 = turn scroll wheel up
+// 5 = turn scroll wheel down
+// 6 = push scroll wheel left
+// 7 = push scroll wheel right
+// 8 = 4th button (aka browser backward button)
+// 9 = 5th button (aka browser forward button)
+// 10
+// 11
+//
+// Other buttons are not supported.
+const (
+ MouseNone MouseButton = iota
+ MouseButton1
+ MouseButton2
+ MouseButton3
+ MouseButton4
+ MouseButton5
+ MouseButton6
+ MouseButton7
+ MouseButton8
+ MouseButton9
+ MouseButton10
+ MouseButton11
+
+ MouseLeft = MouseButton1
+ MouseMiddle = MouseButton2
+ MouseRight = MouseButton3
+ MouseWheelUp = MouseButton4
+ MouseWheelDown = MouseButton5
+ MouseWheelLeft = MouseButton6
+ MouseWheelRight = MouseButton7
+ MouseBackward = MouseButton8
+ MouseForward = MouseButton9
+ MouseRelease = MouseNone
+)
+
+var mouseButtons = map[MouseButton]string{
+ MouseNone: "none",
+ MouseLeft: "left",
+ MouseMiddle: "middle",
+ MouseRight: "right",
+ MouseWheelUp: "wheelup",
+ MouseWheelDown: "wheeldown",
+ MouseWheelLeft: "wheelleft",
+ MouseWheelRight: "wheelright",
+ MouseBackward: "backward",
+ MouseForward: "forward",
+ MouseButton10: "button10",
+ MouseButton11: "button11",
+}
+
+// String returns a string representation of the mouse button.
+func (b MouseButton) String() string {
+ return mouseButtons[b]
+}
+
+// EncodeMouseButton returns a byte representing a mouse button.
+// The button is a bitmask of the following leftmost values:
+//
+// - The first two bits are the button number:
+// 0 = left button, wheel up, or button no. 8 aka (backwards)
+// 1 = middle button, wheel down, or button no. 9 aka (forwards)
+// 2 = right button, wheel left, or button no. 10
+// 3 = release event, wheel right, or button no. 11
+//
+// - The third bit indicates whether the shift key was pressed.
+//
+// - The fourth bit indicates the alt key was pressed.
+//
+// - The fifth bit indicates the control key was pressed.
+//
+// - The sixth bit indicates motion events. Combined with button number 3, i.e.
+// release event, it represents a drag event.
+//
+// - The seventh bit indicates a wheel event.
+//
+// - The eighth bit indicates additional buttons.
+//
+// If button is [MouseNone], and motion is false, this returns a release event.
+// If button is undefined, this function returns 0xff.
+func EncodeMouseButton(b MouseButton, motion, shift, alt, ctrl bool) (m byte) {
+ // mouse bit shifts
+ const (
+ bitShift = 0b0000_0100
+ bitAlt = 0b0000_1000
+ bitCtrl = 0b0001_0000
+ bitMotion = 0b0010_0000
+ bitWheel = 0b0100_0000
+ bitAdd = 0b1000_0000 // additional buttons 8-11
+
+ bitsMask = 0b0000_0011
+ )
+
+ if b == MouseNone {
+ m = bitsMask
+ } else if b >= MouseLeft && b <= MouseRight {
+ m = byte(b - MouseLeft)
+ } else if b >= MouseWheelUp && b <= MouseWheelRight {
+ m = byte(b - MouseWheelUp)
+ m |= bitWheel
+ } else if b >= MouseBackward && b <= MouseButton11 {
+ m = byte(b - MouseBackward)
+ m |= bitAdd
+ } else {
+ m = 0xff // invalid button
+ }
+
+ if shift {
+ m |= bitShift
+ }
+ if alt {
+ m |= bitAlt
+ }
+ if ctrl {
+ m |= bitCtrl
+ }
+ if motion {
+ m |= bitMotion
+ }
+
+ return
+}
+
+// x10Offset is the offset for X10 mouse events.
+// See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
+const x10Offset = 32
+
+// MouseX10 returns an escape sequence representing a mouse event in X10 mode.
+// Note that this requires the terminal support X10 mouse modes.
+//
+// CSI M Cb Cx Cy
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
+func MouseX10(b byte, x, y int) string {
+ return "\x1b[M" + string(b+x10Offset) + string(byte(x)+x10Offset+1) + string(byte(y)+x10Offset+1)
+}
+
+// MouseSgr returns an escape sequence representing a mouse event in SGR mode.
+//
+// CSI < Cb ; Cx ; Cy M
+// CSI < Cb ; Cx ; Cy m (release)
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
+func MouseSgr(b byte, x, y int, release bool) string {
+ s := 'M'
+ if release {
+ s = 'm'
+ }
+ if x < 0 {
+ x = -x
+ }
+ if y < 0 {
+ y = -y
+ }
+ return fmt.Sprintf("\x1b[<%d;%d;%d%c", b, x+1, y+1, s)
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/notification.go b/vendor/github.com/charmbracelet/x/ansi/notification.go
new file mode 100644
index 00000000..c712f340
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/notification.go
@@ -0,0 +1,13 @@
+package ansi
+
+// Notify sends a desktop notification using iTerm's OSC 9.
+//
+// OSC 9 ; Mc ST
+// OSC 9 ; Mc BEL
+//
+// Where Mc is the notification body.
+//
+// See: https://iterm2.com/documentation-escape-codes.html
+func Notify(s string) string {
+ return "\x1b]9;" + s + "\x07"
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/parser.go b/vendor/github.com/charmbracelet/x/ansi/parser.go
new file mode 100644
index 00000000..882e1ed7
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/parser.go
@@ -0,0 +1,417 @@
+package ansi
+
+import (
+ "unicode/utf8"
+ "unsafe"
+
+ "github.com/charmbracelet/x/ansi/parser"
+)
+
+// Parser represents a DEC ANSI compatible sequence parser.
+//
+// It uses a state machine to parse ANSI escape sequences and control
+// characters. The parser is designed to be used with a terminal emulator or
+// similar application that needs to parse ANSI escape sequences and control
+// characters.
+// See package [parser] for more information.
+//
+//go:generate go run ./gen.go
+type Parser struct {
+ handler Handler
+
+ // params contains the raw parameters of the sequence.
+ // These parameters used when constructing CSI and DCS sequences.
+ params []int
+
+ // data contains the raw data of the sequence.
+ // These data used when constructing OSC, DCS, SOS, PM, and APC sequences.
+ data []byte
+
+ // dataLen keeps track of the length of the data buffer.
+ // If dataLen is -1, the data buffer is unlimited and will grow as needed.
+ // Otherwise, dataLen is limited by the size of the data buffer.
+ dataLen int
+
+ // paramsLen keeps track of the number of parameters.
+ // This is limited by the size of the params buffer.
+ //
+ // This is also used when collecting UTF-8 runes to keep track of the
+ // number of rune bytes collected.
+ paramsLen int
+
+ // cmd contains the raw command along with the private prefix and
+ // intermediate bytes of the sequence.
+ // The first lower byte contains the command byte, the next byte contains
+ // the private prefix, and the next byte contains the intermediate byte.
+ //
+ // This is also used when collecting UTF-8 runes treating it as a slice of
+ // 4 bytes.
+ cmd int
+
+ // state is the current state of the parser.
+ state byte
+}
+
+// NewParser returns a new parser with the default settings.
+// The [Parser] uses a default size of 32 for the parameters and 64KB for the
+// data buffer. Use [Parser.SetParamsSize] and [Parser.SetDataSize] to set the
+// size of the parameters and data buffer respectively.
+func NewParser() *Parser {
+ p := new(Parser)
+ p.SetParamsSize(parser.MaxParamsSize)
+ p.SetDataSize(1024 * 64) // 64KB data buffer
+ return p
+}
+
+// SetParamsSize sets the size of the parameters buffer.
+// This is used when constructing CSI and DCS sequences.
+func (p *Parser) SetParamsSize(size int) {
+ p.params = make([]int, size)
+}
+
+// SetDataSize sets the size of the data buffer.
+// This is used when constructing OSC, DCS, SOS, PM, and APC sequences.
+// If size is less than or equal to 0, the data buffer is unlimited and will
+// grow as needed.
+func (p *Parser) SetDataSize(size int) {
+ if size <= 0 {
+ size = 0
+ p.dataLen = -1
+ }
+ p.data = make([]byte, size)
+}
+
+// Params returns the list of parsed packed parameters.
+func (p *Parser) Params() Params {
+ return unsafe.Slice((*Param)(unsafe.Pointer(&p.params[0])), p.paramsLen)
+}
+
+// Param returns the parameter at the given index and falls back to the default
+// value if the parameter is missing. If the index is out of bounds, it returns
+// the default value and false.
+func (p *Parser) Param(i, def int) (int, bool) {
+ if i < 0 || i >= p.paramsLen {
+ return def, false
+ }
+ return Param(p.params[i]).Param(def), true
+}
+
+// Command returns the packed command of the last dispatched sequence. Use
+// [Cmd] to unpack the command.
+func (p *Parser) Command() int {
+ return p.cmd
+}
+
+// Rune returns the last dispatched sequence as a rune.
+func (p *Parser) Rune() rune {
+ rw := utf8ByteLen(byte(p.cmd & 0xff))
+ if rw == -1 {
+ return utf8.RuneError
+ }
+ r, _ := utf8.DecodeRune((*[utf8.UTFMax]byte)(unsafe.Pointer(&p.cmd))[:rw])
+ return r
+}
+
+// Control returns the last dispatched sequence as a control code.
+func (p *Parser) Control() byte {
+ return byte(p.cmd & 0xff)
+}
+
+// Data returns the raw data of the last dispatched sequence.
+func (p *Parser) Data() []byte {
+ return p.data[:p.dataLen]
+}
+
+// Reset resets the parser to its initial state.
+func (p *Parser) Reset() {
+ p.clear()
+ p.state = parser.GroundState
+}
+
+// clear clears the parser parameters and command.
+func (p *Parser) clear() {
+ if len(p.params) > 0 {
+ p.params[0] = parser.MissingParam
+ }
+ p.paramsLen = 0
+ p.cmd = 0
+}
+
+// State returns the current state of the parser.
+func (p *Parser) State() parser.State {
+ return p.state
+}
+
+// StateName returns the name of the current state.
+func (p *Parser) StateName() string {
+ return parser.StateNames[p.state]
+}
+
+// Parse parses the given dispatcher and byte buffer.
+// Deprecated: Loop over the buffer and call [Parser.Advance] instead.
+func (p *Parser) Parse(b []byte) {
+ for i := 0; i < len(b); i++ {
+ p.Advance(b[i])
+ }
+}
+
+// Advance advances the parser using the given byte. It returns the action
+// performed by the parser.
+func (p *Parser) Advance(b byte) parser.Action {
+ switch p.state {
+ case parser.Utf8State:
+ // We handle UTF-8 here.
+ return p.advanceUtf8(b)
+ default:
+ return p.advance(b)
+ }
+}
+
+func (p *Parser) collectRune(b byte) {
+ if p.paramsLen >= utf8.UTFMax {
+ return
+ }
+
+ shift := p.paramsLen * 8
+ p.cmd &^= 0xff << shift
+ p.cmd |= int(b) << shift
+ p.paramsLen++
+}
+
+func (p *Parser) advanceUtf8(b byte) parser.Action {
+ // Collect UTF-8 rune bytes.
+ p.collectRune(b)
+ rw := utf8ByteLen(byte(p.cmd & 0xff))
+ if rw == -1 {
+ // We panic here because the first byte comes from the state machine,
+ // if this panics, it means there is a bug in the state machine!
+ panic("invalid rune") // unreachable
+ }
+
+ if p.paramsLen < rw {
+ return parser.CollectAction
+ }
+
+ // We have enough bytes to decode the rune using unsafe
+ if p.handler.Print != nil {
+ p.handler.Print(p.Rune())
+ }
+
+ p.state = parser.GroundState
+ p.paramsLen = 0
+
+ return parser.PrintAction
+}
+
+func (p *Parser) advance(b byte) parser.Action {
+ state, action := parser.Table.Transition(p.state, b)
+
+ // We need to clear the parser state if the state changes from EscapeState.
+ // This is because when we enter the EscapeState, we don't get a chance to
+ // clear the parser state. For example, when a sequence terminates with a
+ // ST (\x1b\\ or \x9c), we dispatch the current sequence and transition to
+ // EscapeState. However, the parser state is not cleared in this case and
+ // we need to clear it here before dispatching the esc sequence.
+ if p.state != state {
+ if p.state == parser.EscapeState {
+ p.performAction(parser.ClearAction, state, b)
+ }
+ if action == parser.PutAction &&
+ p.state == parser.DcsEntryState && state == parser.DcsStringState {
+ // XXX: This is a special case where we need to start collecting
+ // non-string parameterized data i.e. doesn't follow the ECMA-48 §
+ // 5.4.1 string parameters format.
+ p.performAction(parser.StartAction, state, 0)
+ }
+ }
+
+ // Handle special cases
+ switch {
+ case b == ESC && p.state == parser.EscapeState:
+ // Two ESCs in a row
+ p.performAction(parser.ExecuteAction, state, b)
+ default:
+ p.performAction(action, state, b)
+ }
+
+ p.state = state
+
+ return action
+}
+
+func (p *Parser) parseStringCmd() {
+ // Try to parse the command
+ datalen := len(p.data)
+ if p.dataLen >= 0 {
+ datalen = p.dataLen
+ }
+ for i := 0; i < datalen; i++ {
+ d := p.data[i]
+ if d < '0' || d > '9' {
+ break
+ }
+ if p.cmd == parser.MissingCommand {
+ p.cmd = 0
+ }
+ p.cmd *= 10
+ p.cmd += int(d - '0')
+ }
+}
+
+func (p *Parser) performAction(action parser.Action, state parser.State, b byte) {
+ switch action {
+ case parser.IgnoreAction:
+ break
+
+ case parser.ClearAction:
+ p.clear()
+
+ case parser.PrintAction:
+ p.cmd = int(b)
+ if p.handler.Print != nil {
+ p.handler.Print(rune(b))
+ }
+
+ case parser.ExecuteAction:
+ p.cmd = int(b)
+ if p.handler.Execute != nil {
+ p.handler.Execute(b)
+ }
+
+ case parser.PrefixAction:
+ // Collect private prefix
+ // we only store the last prefix
+ p.cmd &^= 0xff << parser.PrefixShift
+ p.cmd |= int(b) << parser.PrefixShift
+
+ case parser.CollectAction:
+ if state == parser.Utf8State {
+ // Reset the UTF-8 counter
+ p.paramsLen = 0
+ p.collectRune(b)
+ } else {
+ // Collect intermediate bytes
+ // we only store the last intermediate byte
+ p.cmd &^= 0xff << parser.IntermedShift
+ p.cmd |= int(b) << parser.IntermedShift
+ }
+
+ case parser.ParamAction:
+ // Collect parameters
+ if p.paramsLen >= len(p.params) {
+ break
+ }
+
+ if b >= '0' && b <= '9' {
+ if p.params[p.paramsLen] == parser.MissingParam {
+ p.params[p.paramsLen] = 0
+ }
+
+ p.params[p.paramsLen] *= 10
+ p.params[p.paramsLen] += int(b - '0')
+ }
+
+ if b == ':' {
+ p.params[p.paramsLen] |= parser.HasMoreFlag
+ }
+
+ if b == ';' || b == ':' {
+ p.paramsLen++
+ if p.paramsLen < len(p.params) {
+ p.params[p.paramsLen] = parser.MissingParam
+ }
+ }
+
+ case parser.StartAction:
+ if p.dataLen < 0 && p.data != nil {
+ p.data = p.data[:0]
+ } else {
+ p.dataLen = 0
+ }
+ if p.state >= parser.DcsEntryState && p.state <= parser.DcsStringState {
+ // Collect the command byte for DCS
+ p.cmd |= int(b)
+ } else {
+ p.cmd = parser.MissingCommand
+ }
+
+ case parser.PutAction:
+ switch p.state {
+ case parser.OscStringState:
+ if b == ';' && p.cmd == parser.MissingCommand {
+ p.parseStringCmd()
+ }
+ }
+
+ if p.dataLen < 0 {
+ p.data = append(p.data, b)
+ } else {
+ if p.dataLen < len(p.data) {
+ p.data[p.dataLen] = b
+ p.dataLen++
+ }
+ }
+
+ case parser.DispatchAction:
+ // Increment the last parameter
+ if p.paramsLen > 0 && p.paramsLen < len(p.params)-1 ||
+ p.paramsLen == 0 && len(p.params) > 0 && p.params[0] != parser.MissingParam {
+ p.paramsLen++
+ }
+
+ if p.state == parser.OscStringState && p.cmd == parser.MissingCommand {
+ // Ensure we have a command for OSC
+ p.parseStringCmd()
+ }
+
+ data := p.data
+ if p.dataLen >= 0 {
+ data = data[:p.dataLen]
+ }
+ switch p.state {
+ case parser.CsiEntryState, parser.CsiParamState, parser.CsiIntermediateState:
+ p.cmd |= int(b)
+ if p.handler.HandleCsi != nil {
+ p.handler.HandleCsi(Cmd(p.cmd), p.Params())
+ }
+ case parser.EscapeState, parser.EscapeIntermediateState:
+ p.cmd |= int(b)
+ if p.handler.HandleEsc != nil {
+ p.handler.HandleEsc(Cmd(p.cmd))
+ }
+ case parser.DcsEntryState, parser.DcsParamState, parser.DcsIntermediateState, parser.DcsStringState:
+ if p.handler.HandleDcs != nil {
+ p.handler.HandleDcs(Cmd(p.cmd), p.Params(), data)
+ }
+ case parser.OscStringState:
+ if p.handler.HandleOsc != nil {
+ p.handler.HandleOsc(p.cmd, data)
+ }
+ case parser.SosStringState:
+ if p.handler.HandleSos != nil {
+ p.handler.HandleSos(data)
+ }
+ case parser.PmStringState:
+ if p.handler.HandlePm != nil {
+ p.handler.HandlePm(data)
+ }
+ case parser.ApcStringState:
+ if p.handler.HandleApc != nil {
+ p.handler.HandleApc(data)
+ }
+ }
+ }
+}
+
+func utf8ByteLen(b byte) int {
+ if b <= 0b0111_1111 { // 0x00-0x7F
+ return 1
+ } else if b >= 0b1100_0000 && b <= 0b1101_1111 { // 0xC0-0xDF
+ return 2
+ } else if b >= 0b1110_0000 && b <= 0b1110_1111 { // 0xE0-0xEF
+ return 3
+ } else if b >= 0b1111_0000 && b <= 0b1111_0111 { // 0xF0-0xF7
+ return 4
+ }
+ return -1
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/parser/const.go b/vendor/github.com/charmbracelet/x/ansi/parser/const.go
new file mode 100644
index 00000000..d62dbf37
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/parser/const.go
@@ -0,0 +1,78 @@
+package parser
+
+// Action is a DEC ANSI parser action.
+type Action = byte
+
+// These are the actions that the parser can take.
+const (
+ NoneAction Action = iota
+ ClearAction
+ CollectAction
+ PrefixAction
+ DispatchAction
+ ExecuteAction
+ StartAction // Start of a data string
+ PutAction // Put into the data string
+ ParamAction
+ PrintAction
+
+ IgnoreAction = NoneAction
+)
+
+// nolint: unused
+var ActionNames = []string{
+ "NoneAction",
+ "ClearAction",
+ "CollectAction",
+ "PrefixAction",
+ "DispatchAction",
+ "ExecuteAction",
+ "StartAction",
+ "PutAction",
+ "ParamAction",
+ "PrintAction",
+}
+
+// State is a DEC ANSI parser state.
+type State = byte
+
+// These are the states that the parser can be in.
+const (
+ GroundState State = iota
+ CsiEntryState
+ CsiIntermediateState
+ CsiParamState
+ DcsEntryState
+ DcsIntermediateState
+ DcsParamState
+ DcsStringState
+ EscapeState
+ EscapeIntermediateState
+ OscStringState
+ SosStringState
+ PmStringState
+ ApcStringState
+
+ // Utf8State is not part of the DEC ANSI standard. It is used to handle
+ // UTF-8 sequences.
+ Utf8State
+)
+
+// nolint: unused
+var StateNames = []string{
+ "GroundState",
+ "CsiEntryState",
+ "CsiIntermediateState",
+ "CsiParamState",
+ "DcsEntryState",
+ "DcsIntermediateState",
+ "DcsParamState",
+ "DcsStringState",
+ "EscapeState",
+ "EscapeIntermediateState",
+ "OscStringState",
+ "SosStringState",
+ "PmStringState",
+ "ApcStringState",
+ "Utf8State",
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/parser/seq.go b/vendor/github.com/charmbracelet/x/ansi/parser/seq.go
new file mode 100644
index 00000000..29f491d1
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/parser/seq.go
@@ -0,0 +1,136 @@
+package parser
+
+import "math"
+
+// Shift and masks for sequence parameters and intermediates.
+const (
+ PrefixShift = 8
+ IntermedShift = 16
+ FinalMask = 0xff
+ HasMoreFlag = math.MinInt32
+ ParamMask = ^HasMoreFlag
+ MissingParam = ParamMask
+ MissingCommand = MissingParam
+ MaxParam = math.MaxUint16 // the maximum value a parameter can have
+)
+
+const (
+ // MaxParamsSize is the maximum number of parameters a sequence can have.
+ MaxParamsSize = 32
+
+ // DefaultParamValue is the default value used for missing parameters.
+ DefaultParamValue = 0
+)
+
+// Prefix returns the prefix byte of the sequence.
+// This is always gonna be one of the following '<' '=' '>' '?' and in the
+// range of 0x3C-0x3F.
+// Zero is returned if the sequence does not have a prefix.
+func Prefix(cmd int) int {
+ return (cmd >> PrefixShift) & FinalMask
+}
+
+// Intermediate returns the intermediate byte of the sequence.
+// An intermediate byte is in the range of 0x20-0x2F. This includes these
+// characters from ' ', '!', '"', '#', '$', '%', '&', ”', '(', ')', '*', '+',
+// ',', '-', '.', '/'.
+// Zero is returned if the sequence does not have an intermediate byte.
+func Intermediate(cmd int) int {
+ return (cmd >> IntermedShift) & FinalMask
+}
+
+// Command returns the command byte of the CSI sequence.
+func Command(cmd int) int {
+ return cmd & FinalMask
+}
+
+// Param returns the parameter at the given index.
+// It returns -1 if the parameter does not exist.
+func Param(params []int, i int) int {
+ if len(params) == 0 || i < 0 || i >= len(params) {
+ return -1
+ }
+
+ p := params[i] & ParamMask
+ if p == MissingParam {
+ return -1
+ }
+
+ return p
+}
+
+// HasMore returns true if the parameter has more sub-parameters.
+func HasMore(params []int, i int) bool {
+ if len(params) == 0 || i >= len(params) {
+ return false
+ }
+
+ return params[i]&HasMoreFlag != 0
+}
+
+// Subparams returns the sub-parameters of the given parameter.
+// It returns nil if the parameter does not exist.
+func Subparams(params []int, i int) []int {
+ if len(params) == 0 || i < 0 || i >= len(params) {
+ return nil
+ }
+
+ // Count the number of parameters before the given parameter index.
+ var count int
+ var j int
+ for j = 0; j < len(params); j++ {
+ if count == i {
+ break
+ }
+ if !HasMore(params, j) {
+ count++
+ }
+ }
+
+ if count > i || j >= len(params) {
+ return nil
+ }
+
+ var subs []int
+ for ; j < len(params); j++ {
+ if !HasMore(params, j) {
+ break
+ }
+ p := Param(params, j)
+ if p == -1 {
+ p = DefaultParamValue
+ }
+ subs = append(subs, p)
+ }
+
+ p := Param(params, j)
+ if p == -1 {
+ p = DefaultParamValue
+ }
+
+ return append(subs, p)
+}
+
+// Len returns the number of parameters in the sequence.
+// This will return the number of parameters in the sequence, excluding any
+// sub-parameters.
+func Len(params []int) int {
+ var n int
+ for i := 0; i < len(params); i++ {
+ if !HasMore(params, i) {
+ n++
+ }
+ }
+ return n
+}
+
+// Range iterates over the parameters of the sequence and calls the given
+// function for each parameter.
+// The function should return false to stop the iteration.
+func Range(params []int, fn func(i int, param int, hasMore bool) bool) {
+ for i := 0; i < len(params); i++ {
+ if !fn(i, Param(params, i), HasMore(params, i)) {
+ break
+ }
+ }
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/parser/transition_table.go b/vendor/github.com/charmbracelet/x/ansi/parser/transition_table.go
new file mode 100644
index 00000000..558a5eac
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/parser/transition_table.go
@@ -0,0 +1,273 @@
+package parser
+
+// Table values are generated like this:
+//
+// index: currentState << IndexStateShift | charCode
+// value: action << TransitionActionShift | nextState
+const (
+ TransitionActionShift = 4
+ TransitionStateMask = 15
+ IndexStateShift = 8
+
+ // DefaultTableSize is the default size of the transition table.
+ DefaultTableSize = 4096
+)
+
+// Table is a DEC ANSI transition table.
+var Table = GenerateTransitionTable()
+
+// TransitionTable is a DEC ANSI transition table.
+// https://vt100.net/emu/dec_ansi_parser
+type TransitionTable []byte
+
+// NewTransitionTable returns a new DEC ANSI transition table.
+func NewTransitionTable(size int) TransitionTable {
+ if size <= 0 {
+ size = DefaultTableSize
+ }
+ return TransitionTable(make([]byte, size))
+}
+
+// SetDefault sets default transition.
+func (t TransitionTable) SetDefault(action Action, state State) {
+ for i := 0; i < len(t); i++ {
+ t[i] = action<> TransitionActionShift
+}
+
+// byte range macro
+func r(start, end byte) []byte {
+ var a []byte
+ for i := int(start); i <= int(end); i++ {
+ a = append(a, byte(i))
+ }
+ return a
+}
+
+// GenerateTransitionTable generates a DEC ANSI transition table compatible
+// with the VT500-series of terminals. This implementation includes a few
+// modifications that include:
+// - A new Utf8State is introduced to handle UTF8 sequences.
+// - Osc and Dcs data accept UTF8 sequences by extending the printable range
+// to 0xFF and 0xFE respectively.
+// - We don't ignore 0x3A (':') when building Csi and Dcs parameters and
+// instead use it to denote sub-parameters.
+// - Support dispatching SosPmApc sequences.
+// - The DEL (0x7F) character is executed in the Ground state.
+// - The DEL (0x7F) character is collected in the DcsPassthrough string state.
+// - The ST C1 control character (0x9C) is executed and not ignored.
+func GenerateTransitionTable() TransitionTable {
+ table := NewTransitionTable(DefaultTableSize)
+ table.SetDefault(NoneAction, GroundState)
+
+ // Anywhere
+ for _, state := range r(GroundState, Utf8State) {
+ // Anywhere -> Ground
+ table.AddMany([]byte{0x18, 0x1a, 0x99, 0x9a}, state, ExecuteAction, GroundState)
+ table.AddRange(0x80, 0x8F, state, ExecuteAction, GroundState)
+ table.AddRange(0x90, 0x97, state, ExecuteAction, GroundState)
+ table.AddOne(0x9C, state, ExecuteAction, GroundState)
+ // Anywhere -> Escape
+ table.AddOne(0x1B, state, ClearAction, EscapeState)
+ // Anywhere -> SosStringState
+ table.AddOne(0x98, state, StartAction, SosStringState)
+ // Anywhere -> PmStringState
+ table.AddOne(0x9E, state, StartAction, PmStringState)
+ // Anywhere -> ApcStringState
+ table.AddOne(0x9F, state, StartAction, ApcStringState)
+ // Anywhere -> CsiEntry
+ table.AddOne(0x9B, state, ClearAction, CsiEntryState)
+ // Anywhere -> DcsEntry
+ table.AddOne(0x90, state, ClearAction, DcsEntryState)
+ // Anywhere -> OscString
+ table.AddOne(0x9D, state, StartAction, OscStringState)
+ // Anywhere -> Utf8
+ table.AddRange(0xC2, 0xDF, state, CollectAction, Utf8State) // UTF8 2 byte sequence
+ table.AddRange(0xE0, 0xEF, state, CollectAction, Utf8State) // UTF8 3 byte sequence
+ table.AddRange(0xF0, 0xF4, state, CollectAction, Utf8State) // UTF8 4 byte sequence
+ }
+
+ // Ground
+ table.AddRange(0x00, 0x17, GroundState, ExecuteAction, GroundState)
+ table.AddOne(0x19, GroundState, ExecuteAction, GroundState)
+ table.AddRange(0x1C, 0x1F, GroundState, ExecuteAction, GroundState)
+ table.AddRange(0x20, 0x7E, GroundState, PrintAction, GroundState)
+ table.AddOne(0x7F, GroundState, ExecuteAction, GroundState)
+
+ // EscapeIntermediate
+ table.AddRange(0x00, 0x17, EscapeIntermediateState, ExecuteAction, EscapeIntermediateState)
+ table.AddOne(0x19, EscapeIntermediateState, ExecuteAction, EscapeIntermediateState)
+ table.AddRange(0x1C, 0x1F, EscapeIntermediateState, ExecuteAction, EscapeIntermediateState)
+ table.AddRange(0x20, 0x2F, EscapeIntermediateState, CollectAction, EscapeIntermediateState)
+ table.AddOne(0x7F, EscapeIntermediateState, IgnoreAction, EscapeIntermediateState)
+ // EscapeIntermediate -> Ground
+ table.AddRange(0x30, 0x7E, EscapeIntermediateState, DispatchAction, GroundState)
+
+ // Escape
+ table.AddRange(0x00, 0x17, EscapeState, ExecuteAction, EscapeState)
+ table.AddOne(0x19, EscapeState, ExecuteAction, EscapeState)
+ table.AddRange(0x1C, 0x1F, EscapeState, ExecuteAction, EscapeState)
+ table.AddOne(0x7F, EscapeState, IgnoreAction, EscapeState)
+ // Escape -> Ground
+ table.AddRange(0x30, 0x4F, EscapeState, DispatchAction, GroundState)
+ table.AddRange(0x51, 0x57, EscapeState, DispatchAction, GroundState)
+ table.AddOne(0x59, EscapeState, DispatchAction, GroundState)
+ table.AddOne(0x5A, EscapeState, DispatchAction, GroundState)
+ table.AddOne(0x5C, EscapeState, DispatchAction, GroundState)
+ table.AddRange(0x60, 0x7E, EscapeState, DispatchAction, GroundState)
+ // Escape -> Escape_intermediate
+ table.AddRange(0x20, 0x2F, EscapeState, CollectAction, EscapeIntermediateState)
+ // Escape -> Sos_pm_apc_string
+ table.AddOne('X', EscapeState, StartAction, SosStringState) // SOS
+ table.AddOne('^', EscapeState, StartAction, PmStringState) // PM
+ table.AddOne('_', EscapeState, StartAction, ApcStringState) // APC
+ // Escape -> Dcs_entry
+ table.AddOne('P', EscapeState, ClearAction, DcsEntryState)
+ // Escape -> Csi_entry
+ table.AddOne('[', EscapeState, ClearAction, CsiEntryState)
+ // Escape -> Osc_string
+ table.AddOne(']', EscapeState, StartAction, OscStringState)
+
+ // Sos_pm_apc_string
+ for _, state := range r(SosStringState, ApcStringState) {
+ table.AddRange(0x00, 0x17, state, PutAction, state)
+ table.AddOne(0x19, state, PutAction, state)
+ table.AddRange(0x1C, 0x1F, state, PutAction, state)
+ table.AddRange(0x20, 0x7F, state, PutAction, state)
+ // ESC, ST, CAN, and SUB terminate the sequence
+ table.AddOne(0x1B, state, DispatchAction, EscapeState)
+ table.AddOne(0x9C, state, DispatchAction, GroundState)
+ table.AddMany([]byte{0x18, 0x1A}, state, IgnoreAction, GroundState)
+ }
+
+ // Dcs_entry
+ table.AddRange(0x00, 0x07, DcsEntryState, IgnoreAction, DcsEntryState)
+ table.AddRange(0x0E, 0x17, DcsEntryState, IgnoreAction, DcsEntryState)
+ table.AddOne(0x19, DcsEntryState, IgnoreAction, DcsEntryState)
+ table.AddRange(0x1C, 0x1F, DcsEntryState, IgnoreAction, DcsEntryState)
+ table.AddOne(0x7F, DcsEntryState, IgnoreAction, DcsEntryState)
+ // Dcs_entry -> Dcs_intermediate
+ table.AddRange(0x20, 0x2F, DcsEntryState, CollectAction, DcsIntermediateState)
+ // Dcs_entry -> Dcs_param
+ table.AddRange(0x30, 0x3B, DcsEntryState, ParamAction, DcsParamState)
+ table.AddRange(0x3C, 0x3F, DcsEntryState, PrefixAction, DcsParamState)
+ // Dcs_entry -> Dcs_passthrough
+ table.AddRange(0x08, 0x0D, DcsEntryState, PutAction, DcsStringState) // Follows ECMA-48 § 8.3.27
+ // XXX: allows passing ESC (not a ECMA-48 standard) this to allow for
+ // passthrough of ANSI sequences like in Screen or Tmux passthrough mode.
+ table.AddOne(0x1B, DcsEntryState, PutAction, DcsStringState)
+ table.AddRange(0x40, 0x7E, DcsEntryState, StartAction, DcsStringState)
+
+ // Dcs_intermediate
+ table.AddRange(0x00, 0x17, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
+ table.AddOne(0x19, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
+ table.AddRange(0x1C, 0x1F, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
+ table.AddRange(0x20, 0x2F, DcsIntermediateState, CollectAction, DcsIntermediateState)
+ table.AddOne(0x7F, DcsIntermediateState, IgnoreAction, DcsIntermediateState)
+ // Dcs_intermediate -> Dcs_passthrough
+ table.AddRange(0x30, 0x3F, DcsIntermediateState, StartAction, DcsStringState)
+ table.AddRange(0x40, 0x7E, DcsIntermediateState, StartAction, DcsStringState)
+
+ // Dcs_param
+ table.AddRange(0x00, 0x17, DcsParamState, IgnoreAction, DcsParamState)
+ table.AddOne(0x19, DcsParamState, IgnoreAction, DcsParamState)
+ table.AddRange(0x1C, 0x1F, DcsParamState, IgnoreAction, DcsParamState)
+ table.AddRange(0x30, 0x3B, DcsParamState, ParamAction, DcsParamState)
+ table.AddOne(0x7F, DcsParamState, IgnoreAction, DcsParamState)
+ table.AddRange(0x3C, 0x3F, DcsParamState, IgnoreAction, DcsParamState)
+ // Dcs_param -> Dcs_intermediate
+ table.AddRange(0x20, 0x2F, DcsParamState, CollectAction, DcsIntermediateState)
+ // Dcs_param -> Dcs_passthrough
+ table.AddRange(0x40, 0x7E, DcsParamState, StartAction, DcsStringState)
+
+ // Dcs_passthrough
+ table.AddRange(0x00, 0x17, DcsStringState, PutAction, DcsStringState)
+ table.AddOne(0x19, DcsStringState, PutAction, DcsStringState)
+ table.AddRange(0x1C, 0x1F, DcsStringState, PutAction, DcsStringState)
+ table.AddRange(0x20, 0x7E, DcsStringState, PutAction, DcsStringState)
+ table.AddOne(0x7F, DcsStringState, PutAction, DcsStringState)
+ table.AddRange(0x80, 0xFF, DcsStringState, PutAction, DcsStringState) // Allow Utf8 characters by extending the printable range from 0x7F to 0xFF
+ // ST, CAN, SUB, and ESC terminate the sequence
+ table.AddOne(0x1B, DcsStringState, DispatchAction, EscapeState)
+ table.AddOne(0x9C, DcsStringState, DispatchAction, GroundState)
+ table.AddMany([]byte{0x18, 0x1A}, DcsStringState, IgnoreAction, GroundState)
+
+ // Csi_param
+ table.AddRange(0x00, 0x17, CsiParamState, ExecuteAction, CsiParamState)
+ table.AddOne(0x19, CsiParamState, ExecuteAction, CsiParamState)
+ table.AddRange(0x1C, 0x1F, CsiParamState, ExecuteAction, CsiParamState)
+ table.AddRange(0x30, 0x3B, CsiParamState, ParamAction, CsiParamState)
+ table.AddOne(0x7F, CsiParamState, IgnoreAction, CsiParamState)
+ table.AddRange(0x3C, 0x3F, CsiParamState, IgnoreAction, CsiParamState)
+ // Csi_param -> Ground
+ table.AddRange(0x40, 0x7E, CsiParamState, DispatchAction, GroundState)
+ // Csi_param -> Csi_intermediate
+ table.AddRange(0x20, 0x2F, CsiParamState, CollectAction, CsiIntermediateState)
+
+ // Csi_intermediate
+ table.AddRange(0x00, 0x17, CsiIntermediateState, ExecuteAction, CsiIntermediateState)
+ table.AddOne(0x19, CsiIntermediateState, ExecuteAction, CsiIntermediateState)
+ table.AddRange(0x1C, 0x1F, CsiIntermediateState, ExecuteAction, CsiIntermediateState)
+ table.AddRange(0x20, 0x2F, CsiIntermediateState, CollectAction, CsiIntermediateState)
+ table.AddOne(0x7F, CsiIntermediateState, IgnoreAction, CsiIntermediateState)
+ // Csi_intermediate -> Ground
+ table.AddRange(0x40, 0x7E, CsiIntermediateState, DispatchAction, GroundState)
+ // Csi_intermediate -> Csi_ignore
+ table.AddRange(0x30, 0x3F, CsiIntermediateState, IgnoreAction, GroundState)
+
+ // Csi_entry
+ table.AddRange(0x00, 0x17, CsiEntryState, ExecuteAction, CsiEntryState)
+ table.AddOne(0x19, CsiEntryState, ExecuteAction, CsiEntryState)
+ table.AddRange(0x1C, 0x1F, CsiEntryState, ExecuteAction, CsiEntryState)
+ table.AddOne(0x7F, CsiEntryState, IgnoreAction, CsiEntryState)
+ // Csi_entry -> Ground
+ table.AddRange(0x40, 0x7E, CsiEntryState, DispatchAction, GroundState)
+ // Csi_entry -> Csi_intermediate
+ table.AddRange(0x20, 0x2F, CsiEntryState, CollectAction, CsiIntermediateState)
+ // Csi_entry -> Csi_param
+ table.AddRange(0x30, 0x3B, CsiEntryState, ParamAction, CsiParamState)
+ table.AddRange(0x3C, 0x3F, CsiEntryState, PrefixAction, CsiParamState)
+
+ // Osc_string
+ table.AddRange(0x00, 0x06, OscStringState, IgnoreAction, OscStringState)
+ table.AddRange(0x08, 0x17, OscStringState, IgnoreAction, OscStringState)
+ table.AddOne(0x19, OscStringState, IgnoreAction, OscStringState)
+ table.AddRange(0x1C, 0x1F, OscStringState, IgnoreAction, OscStringState)
+ table.AddRange(0x20, 0xFF, OscStringState, PutAction, OscStringState) // Allow Utf8 characters by extending the printable range from 0x7F to 0xFF
+
+ // ST, CAN, SUB, ESC, and BEL terminate the sequence
+ table.AddOne(0x1B, OscStringState, DispatchAction, EscapeState)
+ table.AddOne(0x07, OscStringState, DispatchAction, GroundState)
+ table.AddOne(0x9C, OscStringState, DispatchAction, GroundState)
+ table.AddMany([]byte{0x18, 0x1A}, OscStringState, IgnoreAction, GroundState)
+
+ return table
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/parser_decode.go b/vendor/github.com/charmbracelet/x/ansi/parser_decode.go
new file mode 100644
index 00000000..3e504739
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/parser_decode.go
@@ -0,0 +1,524 @@
+package ansi
+
+import (
+ "unicode/utf8"
+
+ "github.com/charmbracelet/x/ansi/parser"
+ "github.com/mattn/go-runewidth"
+ "github.com/rivo/uniseg"
+)
+
+// State represents the state of the ANSI escape sequence parser used by
+// [DecodeSequence].
+type State = byte
+
+// ANSI escape sequence states used by [DecodeSequence].
+const (
+ NormalState State = iota
+ PrefixState
+ ParamsState
+ IntermedState
+ EscapeState
+ StringState
+)
+
+// DecodeSequence decodes the first ANSI escape sequence or a printable
+// grapheme from the given data. It returns the sequence slice, the number of
+// bytes read, the cell width for each sequence, and the new state.
+//
+// The cell width will always be 0 for control and escape sequences, 1 for
+// ASCII printable characters, and the number of cells other Unicode characters
+// occupy. It uses the uniseg package to calculate the width of Unicode
+// graphemes and characters. This means it will always do grapheme clustering
+// (mode 2027).
+//
+// Passing a non-nil [*Parser] as the last argument will allow the decoder to
+// collect sequence parameters, data, and commands. The parser cmd will have
+// the packed command value that contains intermediate and prefix characters.
+// In the case of a OSC sequence, the cmd will be the OSC command number. Use
+// [Cmd] and [Param] types to unpack command intermediates and prefixes as well
+// as parameters.
+//
+// Zero [Cmd] means the CSI, DCS, or ESC sequence is invalid. Moreover, checking the
+// validity of other data sequences, OSC, DCS, etc, will require checking for
+// the returned sequence terminator bytes such as ST (ESC \\) and BEL).
+//
+// We store the command byte in [Cmd] in the most significant byte, the
+// prefix byte in the next byte, and the intermediate byte in the least
+// significant byte. This is done to avoid using a struct to store the command
+// and its intermediates and prefixes. The command byte is always the least
+// significant byte i.e. [Cmd & 0xff]. Use the [Cmd] type to unpack the
+// command, intermediate, and prefix bytes. Note that we only collect the last
+// prefix character and intermediate byte.
+//
+// The [p.Params] slice will contain the parameters of the sequence. Any
+// sub-parameter will have the [parser.HasMoreFlag] set. Use the [Param] type
+// to unpack the parameters.
+//
+// Example:
+//
+// var state byte // the initial state is always zero [NormalState]
+// p := NewParser(32, 1024) // create a new parser with a 32 params buffer and 1024 data buffer (optional)
+// input := []byte("\x1b[31mHello, World!\x1b[0m")
+// for len(input) > 0 {
+// seq, width, n, newState := DecodeSequence(input, state, p)
+// log.Printf("seq: %q, width: %d", seq, width)
+// state = newState
+// input = input[n:]
+// }
+//
+// This function treats the text as a sequence of grapheme clusters.
+func DecodeSequence[T string | []byte](b T, state byte, p *Parser) (seq T, width int, n int, newState byte) {
+ return decodeSequence(GraphemeWidth, b, state, p)
+}
+
+// DecodeSequenceWc decodes the first ANSI escape sequence or a printable
+// grapheme from the given data. It returns the sequence slice, the number of
+// bytes read, the cell width for each sequence, and the new state.
+//
+// The cell width will always be 0 for control and escape sequences, 1 for
+// ASCII printable characters, and the number of cells other Unicode characters
+// occupy. It uses the uniseg package to calculate the width of Unicode
+// graphemes and characters. This means it will always do grapheme clustering
+// (mode 2027).
+//
+// Passing a non-nil [*Parser] as the last argument will allow the decoder to
+// collect sequence parameters, data, and commands. The parser cmd will have
+// the packed command value that contains intermediate and prefix characters.
+// In the case of a OSC sequence, the cmd will be the OSC command number. Use
+// [Cmd] and [Param] types to unpack command intermediates and prefixes as well
+// as parameters.
+//
+// Zero [Cmd] means the CSI, DCS, or ESC sequence is invalid. Moreover, checking the
+// validity of other data sequences, OSC, DCS, etc, will require checking for
+// the returned sequence terminator bytes such as ST (ESC \\) and BEL).
+//
+// We store the command byte in [Cmd] in the most significant byte, the
+// prefix byte in the next byte, and the intermediate byte in the least
+// significant byte. This is done to avoid using a struct to store the command
+// and its intermediates and prefixes. The command byte is always the least
+// significant byte i.e. [Cmd & 0xff]. Use the [Cmd] type to unpack the
+// command, intermediate, and prefix bytes. Note that we only collect the last
+// prefix character and intermediate byte.
+//
+// The [p.Params] slice will contain the parameters of the sequence. Any
+// sub-parameter will have the [parser.HasMoreFlag] set. Use the [Param] type
+// to unpack the parameters.
+//
+// Example:
+//
+// var state byte // the initial state is always zero [NormalState]
+// p := NewParser(32, 1024) // create a new parser with a 32 params buffer and 1024 data buffer (optional)
+// input := []byte("\x1b[31mHello, World!\x1b[0m")
+// for len(input) > 0 {
+// seq, width, n, newState := DecodeSequenceWc(input, state, p)
+// log.Printf("seq: %q, width: %d", seq, width)
+// state = newState
+// input = input[n:]
+// }
+//
+// This function treats the text as a sequence of wide characters and runes.
+func DecodeSequenceWc[T string | []byte](b T, state byte, p *Parser) (seq T, width int, n int, newState byte) {
+ return decodeSequence(WcWidth, b, state, p)
+}
+
+func decodeSequence[T string | []byte](m Method, b T, state State, p *Parser) (seq T, width int, n int, newState byte) {
+ for i := 0; i < len(b); i++ {
+ c := b[i]
+
+ switch state {
+ case NormalState:
+ switch c {
+ case ESC:
+ if p != nil {
+ if len(p.params) > 0 {
+ p.params[0] = parser.MissingParam
+ }
+ p.cmd = 0
+ p.paramsLen = 0
+ p.dataLen = 0
+ }
+ state = EscapeState
+ continue
+ case CSI, DCS:
+ if p != nil {
+ if len(p.params) > 0 {
+ p.params[0] = parser.MissingParam
+ }
+ p.cmd = 0
+ p.paramsLen = 0
+ p.dataLen = 0
+ }
+ state = PrefixState
+ continue
+ case OSC, APC, SOS, PM:
+ if p != nil {
+ p.cmd = parser.MissingCommand
+ p.dataLen = 0
+ }
+ state = StringState
+ continue
+ }
+
+ if p != nil {
+ p.dataLen = 0
+ p.paramsLen = 0
+ p.cmd = 0
+ }
+ if c > US && c < DEL {
+ // ASCII printable characters
+ return b[i : i+1], 1, 1, NormalState
+ }
+
+ if c <= US || c == DEL || c < 0xC0 {
+ // C0 & C1 control characters & DEL
+ return b[i : i+1], 0, 1, NormalState
+ }
+
+ if utf8.RuneStart(c) {
+ seq, _, width, _ = FirstGraphemeCluster(b, -1)
+ if m == WcWidth {
+ width = runewidth.StringWidth(string(seq))
+ }
+ i += len(seq)
+ return b[:i], width, i, NormalState
+ }
+
+ // Invalid UTF-8 sequence
+ return b[:i], 0, i, NormalState
+ case PrefixState:
+ if c >= '<' && c <= '?' {
+ if p != nil {
+ // We only collect the last prefix character.
+ p.cmd &^= 0xff << parser.PrefixShift
+ p.cmd |= int(c) << parser.PrefixShift
+ }
+ break
+ }
+
+ state = ParamsState
+ fallthrough
+ case ParamsState:
+ if c >= '0' && c <= '9' {
+ if p != nil {
+ if p.params[p.paramsLen] == parser.MissingParam {
+ p.params[p.paramsLen] = 0
+ }
+
+ p.params[p.paramsLen] *= 10
+ p.params[p.paramsLen] += int(c - '0')
+ }
+ break
+ }
+
+ if c == ':' {
+ if p != nil {
+ p.params[p.paramsLen] |= parser.HasMoreFlag
+ }
+ }
+
+ if c == ';' || c == ':' {
+ if p != nil {
+ p.paramsLen++
+ if p.paramsLen < len(p.params) {
+ p.params[p.paramsLen] = parser.MissingParam
+ }
+ }
+ break
+ }
+
+ state = IntermedState
+ fallthrough
+ case IntermedState:
+ if c >= ' ' && c <= '/' {
+ if p != nil {
+ p.cmd &^= 0xff << parser.IntermedShift
+ p.cmd |= int(c) << parser.IntermedShift
+ }
+ break
+ }
+
+ if p != nil {
+ // Increment the last parameter
+ if p.paramsLen > 0 && p.paramsLen < len(p.params)-1 ||
+ p.paramsLen == 0 && len(p.params) > 0 && p.params[0] != parser.MissingParam {
+ p.paramsLen++
+ }
+ }
+
+ if c >= '@' && c <= '~' {
+ if p != nil {
+ p.cmd &^= 0xff
+ p.cmd |= int(c)
+ }
+
+ if HasDcsPrefix(b) {
+ // Continue to collect DCS data
+ if p != nil {
+ p.dataLen = 0
+ }
+ state = StringState
+ continue
+ }
+
+ return b[:i+1], 0, i + 1, NormalState
+ }
+
+ // Invalid CSI/DCS sequence
+ return b[:i], 0, i, NormalState
+ case EscapeState:
+ switch c {
+ case '[', 'P':
+ if p != nil {
+ if len(p.params) > 0 {
+ p.params[0] = parser.MissingParam
+ }
+ p.paramsLen = 0
+ p.cmd = 0
+ }
+ state = PrefixState
+ continue
+ case ']', 'X', '^', '_':
+ if p != nil {
+ p.cmd = parser.MissingCommand
+ p.dataLen = 0
+ }
+ state = StringState
+ continue
+ }
+
+ if c >= ' ' && c <= '/' {
+ if p != nil {
+ p.cmd &^= 0xff << parser.IntermedShift
+ p.cmd |= int(c) << parser.IntermedShift
+ }
+ continue
+ } else if c >= '0' && c <= '~' {
+ if p != nil {
+ p.cmd &^= 0xff
+ p.cmd |= int(c)
+ }
+ return b[:i+1], 0, i + 1, NormalState
+ }
+
+ // Invalid escape sequence
+ return b[:i], 0, i, NormalState
+ case StringState:
+ switch c {
+ case BEL:
+ if HasOscPrefix(b) {
+ parseOscCmd(p)
+ return b[:i+1], 0, i + 1, NormalState
+ }
+ case CAN, SUB:
+ if HasOscPrefix(b) {
+ // Ensure we parse the OSC command number
+ parseOscCmd(p)
+ }
+
+ // Cancel the sequence
+ return b[:i], 0, i, NormalState
+ case ST:
+ if HasOscPrefix(b) {
+ // Ensure we parse the OSC command number
+ parseOscCmd(p)
+ }
+
+ return b[:i+1], 0, i + 1, NormalState
+ case ESC:
+ if HasStPrefix(b[i:]) {
+ if HasOscPrefix(b) {
+ // Ensure we parse the OSC command number
+ parseOscCmd(p)
+ }
+
+ // End of string 7-bit (ST)
+ return b[:i+2], 0, i + 2, NormalState
+ }
+
+ // Otherwise, cancel the sequence
+ return b[:i], 0, i, NormalState
+ }
+
+ if p != nil && p.dataLen < len(p.data) {
+ p.data[p.dataLen] = c
+ p.dataLen++
+
+ // Parse the OSC command number
+ if c == ';' && HasOscPrefix(b) {
+ parseOscCmd(p)
+ }
+ }
+ }
+ }
+
+ return b, 0, len(b), state
+}
+
+func parseOscCmd(p *Parser) {
+ if p == nil || p.cmd != parser.MissingCommand {
+ return
+ }
+ for j := 0; j < p.dataLen; j++ {
+ d := p.data[j]
+ if d < '0' || d > '9' {
+ break
+ }
+ if p.cmd == parser.MissingCommand {
+ p.cmd = 0
+ }
+ p.cmd *= 10
+ p.cmd += int(d - '0')
+ }
+}
+
+// Equal returns true if the given byte slices are equal.
+func Equal[T string | []byte](a, b T) bool {
+ return string(a) == string(b)
+}
+
+// HasPrefix returns true if the given byte slice has prefix.
+func HasPrefix[T string | []byte](b, prefix T) bool {
+ return len(b) >= len(prefix) && Equal(b[0:len(prefix)], prefix)
+}
+
+// HasSuffix returns true if the given byte slice has suffix.
+func HasSuffix[T string | []byte](b, suffix T) bool {
+ return len(b) >= len(suffix) && Equal(b[len(b)-len(suffix):], suffix)
+}
+
+// HasCsiPrefix returns true if the given byte slice has a CSI prefix.
+func HasCsiPrefix[T string | []byte](b T) bool {
+ return (len(b) > 0 && b[0] == CSI) ||
+ (len(b) > 1 && b[0] == ESC && b[1] == '[')
+}
+
+// HasOscPrefix returns true if the given byte slice has an OSC prefix.
+func HasOscPrefix[T string | []byte](b T) bool {
+ return (len(b) > 0 && b[0] == OSC) ||
+ (len(b) > 1 && b[0] == ESC && b[1] == ']')
+}
+
+// HasApcPrefix returns true if the given byte slice has an APC prefix.
+func HasApcPrefix[T string | []byte](b T) bool {
+ return (len(b) > 0 && b[0] == APC) ||
+ (len(b) > 1 && b[0] == ESC && b[1] == '_')
+}
+
+// HasDcsPrefix returns true if the given byte slice has a DCS prefix.
+func HasDcsPrefix[T string | []byte](b T) bool {
+ return (len(b) > 0 && b[0] == DCS) ||
+ (len(b) > 1 && b[0] == ESC && b[1] == 'P')
+}
+
+// HasSosPrefix returns true if the given byte slice has a SOS prefix.
+func HasSosPrefix[T string | []byte](b T) bool {
+ return (len(b) > 0 && b[0] == SOS) ||
+ (len(b) > 1 && b[0] == ESC && b[1] == 'X')
+}
+
+// HasPmPrefix returns true if the given byte slice has a PM prefix.
+func HasPmPrefix[T string | []byte](b T) bool {
+ return (len(b) > 0 && b[0] == PM) ||
+ (len(b) > 1 && b[0] == ESC && b[1] == '^')
+}
+
+// HasStPrefix returns true if the given byte slice has a ST prefix.
+func HasStPrefix[T string | []byte](b T) bool {
+ return (len(b) > 0 && b[0] == ST) ||
+ (len(b) > 1 && b[0] == ESC && b[1] == '\\')
+}
+
+// HasEscPrefix returns true if the given byte slice has an ESC prefix.
+func HasEscPrefix[T string | []byte](b T) bool {
+ return len(b) > 0 && b[0] == ESC
+}
+
+// FirstGraphemeCluster returns the first grapheme cluster in the given string or byte slice.
+// This is a syntactic sugar function that wraps
+// uniseg.FirstGraphemeClusterInString and uniseg.FirstGraphemeCluster.
+func FirstGraphemeCluster[T string | []byte](b T, state int) (T, T, int, int) {
+ switch b := any(b).(type) {
+ case string:
+ cluster, rest, width, newState := uniseg.FirstGraphemeClusterInString(b, state)
+ return T(cluster), T(rest), width, newState
+ case []byte:
+ cluster, rest, width, newState := uniseg.FirstGraphemeCluster(b, state)
+ return T(cluster), T(rest), width, newState
+ }
+ panic("unreachable")
+}
+
+// Cmd represents a sequence command. This is used to pack/unpack a sequence
+// command with its intermediate and prefix characters. Those are commonly
+// found in CSI and DCS sequences.
+type Cmd int
+
+// Prefix returns the unpacked prefix byte of the CSI sequence.
+// This is always gonna be one of the following '<' '=' '>' '?' and in the
+// range of 0x3C-0x3F.
+// Zero is returned if the sequence does not have a prefix.
+func (c Cmd) Prefix() byte {
+ return byte(parser.Prefix(int(c)))
+}
+
+// Intermediate returns the unpacked intermediate byte of the CSI sequence.
+// An intermediate byte is in the range of 0x20-0x2F. This includes these
+// characters from ' ', '!', '"', '#', '$', '%', '&', ”', '(', ')', '*', '+',
+// ',', '-', '.', '/'.
+// Zero is returned if the sequence does not have an intermediate byte.
+func (c Cmd) Intermediate() byte {
+ return byte(parser.Intermediate(int(c)))
+}
+
+// Final returns the unpacked command byte of the CSI sequence.
+func (c Cmd) Final() byte {
+ return byte(parser.Command(int(c)))
+}
+
+// Command packs a command with the given prefix, intermediate, and final. A
+// zero byte means the sequence does not have a prefix or intermediate.
+//
+// Prefixes are in the range of 0x3C-0x3F that is one of `<=>?`.
+//
+// Intermediates are in the range of 0x20-0x2F that is anything in
+// `!"#$%&'()*+,-./`.
+//
+// Final bytes are in the range of 0x40-0x7E that is anything in the range
+// `@A–Z[\]^_`a–z{|}~`.
+func Command(prefix, inter, final byte) (c int) {
+ c = int(final)
+ c |= int(prefix) << parser.PrefixShift
+ c |= int(inter) << parser.IntermedShift
+ return
+}
+
+// Param represents a sequence parameter. Sequence parameters with
+// sub-parameters are packed with the HasMoreFlag set. This is used to unpack
+// the parameters from a CSI and DCS sequences.
+type Param int
+
+// Param returns the unpacked parameter at the given index.
+// It returns the default value if the parameter is missing.
+func (s Param) Param(def int) int {
+ p := int(s) & parser.ParamMask
+ if p == parser.MissingParam {
+ return def
+ }
+ return p
+}
+
+// HasMore unpacks the HasMoreFlag from the parameter.
+func (s Param) HasMore() bool {
+ return s&parser.HasMoreFlag != 0
+}
+
+// Parameter packs an escape code parameter with the given parameter and
+// whether this parameter has following sub-parameters.
+func Parameter(p int, hasMore bool) (s int) {
+ s = p & parser.ParamMask
+ if hasMore {
+ s |= parser.HasMoreFlag
+ }
+ return
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/parser_handler.go b/vendor/github.com/charmbracelet/x/ansi/parser_handler.go
new file mode 100644
index 00000000..03f9ed4c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/parser_handler.go
@@ -0,0 +1,60 @@
+package ansi
+
+import "unsafe"
+
+// Params represents a list of packed parameters.
+type Params []Param
+
+// Param returns the parameter at the given index and if it is part of a
+// sub-parameters. It falls back to the default value if the parameter is
+// missing. If the index is out of bounds, it returns the default value and
+// false.
+func (p Params) Param(i, def int) (int, bool, bool) {
+ if i < 0 || i >= len(p) {
+ return def, false, false
+ }
+ return p[i].Param(def), p[i].HasMore(), true
+}
+
+// ForEach iterates over the parameters and calls the given function for each
+// parameter. If a parameter is part of a sub-parameter, it will be called with
+// hasMore set to true.
+// Use def to set a default value for missing parameters.
+func (p Params) ForEach(def int, f func(i, param int, hasMore bool)) {
+ for i := range p {
+ f(i, p[i].Param(def), p[i].HasMore())
+ }
+}
+
+// ToParams converts a list of integers to a list of parameters.
+func ToParams(params []int) Params {
+ return unsafe.Slice((*Param)(unsafe.Pointer(¶ms[0])), len(params))
+}
+
+// Handler handles actions performed by the parser.
+// It is used to handle ANSI escape sequences, control characters, and runes.
+type Handler struct {
+ // Print is called when a printable rune is encountered.
+ Print func(r rune)
+ // Execute is called when a control character is encountered.
+ Execute func(b byte)
+ // HandleCsi is called when a CSI sequence is encountered.
+ HandleCsi func(cmd Cmd, params Params)
+ // HandleEsc is called when an ESC sequence is encountered.
+ HandleEsc func(cmd Cmd)
+ // HandleDcs is called when a DCS sequence is encountered.
+ HandleDcs func(cmd Cmd, params Params, data []byte)
+ // HandleOsc is called when an OSC sequence is encountered.
+ HandleOsc func(cmd int, data []byte)
+ // HandlePm is called when a PM sequence is encountered.
+ HandlePm func(data []byte)
+ // HandleApc is called when an APC sequence is encountered.
+ HandleApc func(data []byte)
+ // HandleSos is called when a SOS sequence is encountered.
+ HandleSos func(data []byte)
+}
+
+// SetHandler sets the handler for the parser.
+func (p *Parser) SetHandler(h Handler) {
+ p.handler = h
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/parser_sync.go b/vendor/github.com/charmbracelet/x/ansi/parser_sync.go
new file mode 100644
index 00000000..65d25a9a
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/parser_sync.go
@@ -0,0 +1,29 @@
+package ansi
+
+import (
+ "sync"
+
+ "github.com/charmbracelet/x/ansi/parser"
+)
+
+var parserPool = sync.Pool{
+ New: func() any {
+ p := NewParser()
+ p.SetParamsSize(parser.MaxParamsSize)
+ p.SetDataSize(1024 * 1024 * 4) // 4MB of data buffer
+ return p
+ },
+}
+
+// GetParser returns a parser from a sync pool.
+func GetParser() *Parser {
+ return parserPool.Get().(*Parser)
+}
+
+// PutParser returns a parser to a sync pool. The parser is reset
+// automatically.
+func PutParser(p *Parser) {
+ p.Reset()
+ p.dataLen = 0
+ parserPool.Put(p)
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/passthrough.go b/vendor/github.com/charmbracelet/x/ansi/passthrough.go
new file mode 100644
index 00000000..14a74522
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/passthrough.go
@@ -0,0 +1,63 @@
+package ansi
+
+import (
+ "bytes"
+)
+
+// ScreenPassthrough wraps the given ANSI sequence in a DCS passthrough
+// sequence to be sent to the outer terminal. This is used to send raw escape
+// sequences to the outer terminal when running inside GNU Screen.
+//
+// DCS ST
+//
+// Note: Screen limits the length of string sequences to 768 bytes (since 2014).
+// Use zero to indicate no limit, otherwise, this will chunk the returned
+// string into limit sized chunks.
+//
+// See: https://www.gnu.org/software/screen/manual/screen.html#String-Escapes
+// See: https://git.savannah.gnu.org/cgit/screen.git/tree/src/screen.h?id=c184c6ec27683ff1a860c45be5cf520d896fd2ef#n44
+func ScreenPassthrough(seq string, limit int) string {
+ var b bytes.Buffer
+ b.WriteString("\x1bP")
+ if limit > 0 {
+ for i := 0; i < len(seq); i += limit {
+ end := i + limit
+ if end > len(seq) {
+ end = len(seq)
+ }
+ b.WriteString(seq[i:end])
+ if end < len(seq) {
+ b.WriteString("\x1b\\\x1bP")
+ }
+ }
+ } else {
+ b.WriteString(seq)
+ }
+ b.WriteString("\x1b\\")
+ return b.String()
+}
+
+// TmuxPassthrough wraps the given ANSI sequence in a special DCS passthrough
+// sequence to be sent to the outer terminal. This is used to send raw escape
+// sequences to the outer terminal when running inside Tmux.
+//
+// DCS tmux ; ST
+//
+// Where is the given sequence in which all occurrences of ESC
+// (0x1b) are doubled i.e. replaced with ESC ESC (0x1b 0x1b).
+//
+// Note: this needs the `allow-passthrough` option to be set to `on`.
+//
+// See: https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it
+func TmuxPassthrough(seq string) string {
+ var b bytes.Buffer
+ b.WriteString("\x1bPtmux;")
+ for i := 0; i < len(seq); i++ {
+ if seq[i] == ESC {
+ b.WriteByte(ESC)
+ }
+ b.WriteByte(seq[i])
+ }
+ b.WriteString("\x1b\\")
+ return b.String()
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/paste.go b/vendor/github.com/charmbracelet/x/ansi/paste.go
new file mode 100644
index 00000000..2f9ea6f7
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/paste.go
@@ -0,0 +1,7 @@
+package ansi
+
+// BracketedPasteStart is the control sequence to enable bracketed paste mode.
+const BracketedPasteStart = "\x1b[200~"
+
+// BracketedPasteEnd is the control sequence to disable bracketed paste mode.
+const BracketedPasteEnd = "\x1b[201~"
diff --git a/vendor/github.com/charmbracelet/x/ansi/reset.go b/vendor/github.com/charmbracelet/x/ansi/reset.go
new file mode 100644
index 00000000..c1b89ea4
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/reset.go
@@ -0,0 +1,11 @@
+package ansi
+
+// ResetInitialState (RIS) resets the terminal to its initial state.
+//
+// ESC c
+//
+// See: https://vt100.net/docs/vt510-rm/RIS.html
+const (
+ ResetInitialState = "\x1bc"
+ RIS = ResetInitialState
+)
diff --git a/vendor/github.com/charmbracelet/x/ansi/screen.go b/vendor/github.com/charmbracelet/x/ansi/screen.go
new file mode 100644
index 00000000..c76e4f0d
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/screen.go
@@ -0,0 +1,410 @@
+package ansi
+
+import (
+ "strconv"
+ "strings"
+)
+
+// EraseDisplay (ED) clears the display or parts of the display. A screen is
+// the shown part of the terminal display excluding the scrollback buffer.
+// Possible values:
+//
+// Default is 0.
+//
+// 0: Clear from cursor to end of screen.
+// 1: Clear from cursor to beginning of the screen.
+// 2: Clear entire screen (and moves cursor to upper left on DOS).
+// 3: Clear entire display which delete all lines saved in the scrollback buffer (xterm).
+//
+// CSI J
+//
+// See: https://vt100.net/docs/vt510-rm/ED.html
+func EraseDisplay(n int) string {
+ var s string
+ if n > 0 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "J"
+}
+
+// ED is an alias for [EraseDisplay].
+func ED(n int) string {
+ return EraseDisplay(n)
+}
+
+// EraseDisplay constants.
+// These are the possible values for the EraseDisplay function.
+const (
+ EraseScreenBelow = "\x1b[J"
+ EraseScreenAbove = "\x1b[1J"
+ EraseEntireScreen = "\x1b[2J"
+ EraseEntireDisplay = "\x1b[3J"
+)
+
+// EraseLine (EL) clears the current line or parts of the line. Possible values:
+//
+// 0: Clear from cursor to end of line.
+// 1: Clear from cursor to beginning of the line.
+// 2: Clear entire line.
+//
+// The cursor position is not affected.
+//
+// CSI K
+//
+// See: https://vt100.net/docs/vt510-rm/EL.html
+func EraseLine(n int) string {
+ var s string
+ if n > 0 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "K"
+}
+
+// EL is an alias for [EraseLine].
+func EL(n int) string {
+ return EraseLine(n)
+}
+
+// EraseLine constants.
+// These are the possible values for the EraseLine function.
+const (
+ EraseLineRight = "\x1b[K"
+ EraseLineLeft = "\x1b[1K"
+ EraseEntireLine = "\x1b[2K"
+)
+
+// ScrollUp (SU) scrolls the screen up n lines. New lines are added at the
+// bottom of the screen.
+//
+// CSI Pn S
+//
+// See: https://vt100.net/docs/vt510-rm/SU.html
+func ScrollUp(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "S"
+}
+
+// PanDown is an alias for [ScrollUp].
+func PanDown(n int) string {
+ return ScrollUp(n)
+}
+
+// SU is an alias for [ScrollUp].
+func SU(n int) string {
+ return ScrollUp(n)
+}
+
+// ScrollDown (SD) scrolls the screen down n lines. New lines are added at the
+// top of the screen.
+//
+// CSI Pn T
+//
+// See: https://vt100.net/docs/vt510-rm/SD.html
+func ScrollDown(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "T"
+}
+
+// PanUp is an alias for [ScrollDown].
+func PanUp(n int) string {
+ return ScrollDown(n)
+}
+
+// SD is an alias for [ScrollDown].
+func SD(n int) string {
+ return ScrollDown(n)
+}
+
+// InsertLine (IL) inserts n blank lines at the current cursor position.
+// Existing lines are moved down.
+//
+// CSI Pn L
+//
+// See: https://vt100.net/docs/vt510-rm/IL.html
+func InsertLine(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "L"
+}
+
+// IL is an alias for [InsertLine].
+func IL(n int) string {
+ return InsertLine(n)
+}
+
+// DeleteLine (DL) deletes n lines at the current cursor position. Existing
+// lines are moved up.
+//
+// CSI Pn M
+//
+// See: https://vt100.net/docs/vt510-rm/DL.html
+func DeleteLine(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "M"
+}
+
+// DL is an alias for [DeleteLine].
+func DL(n int) string {
+ return DeleteLine(n)
+}
+
+// SetTopBottomMargins (DECSTBM) sets the top and bottom margins for the scrolling
+// region. The default is the entire screen.
+//
+// Default is 1 and the bottom of the screen.
+//
+// CSI Pt ; Pb r
+//
+// See: https://vt100.net/docs/vt510-rm/DECSTBM.html
+func SetTopBottomMargins(top, bot int) string {
+ var t, b string
+ if top > 0 {
+ t = strconv.Itoa(top)
+ }
+ if bot > 0 {
+ b = strconv.Itoa(bot)
+ }
+ return "\x1b[" + t + ";" + b + "r"
+}
+
+// DECSTBM is an alias for [SetTopBottomMargins].
+func DECSTBM(top, bot int) string {
+ return SetTopBottomMargins(top, bot)
+}
+
+// SetLeftRightMargins (DECSLRM) sets the left and right margins for the scrolling
+// region.
+//
+// Default is 1 and the right of the screen.
+//
+// CSI Pl ; Pr s
+//
+// See: https://vt100.net/docs/vt510-rm/DECSLRM.html
+func SetLeftRightMargins(left, right int) string {
+ var l, r string
+ if left > 0 {
+ l = strconv.Itoa(left)
+ }
+ if right > 0 {
+ r = strconv.Itoa(right)
+ }
+ return "\x1b[" + l + ";" + r + "s"
+}
+
+// DECSLRM is an alias for [SetLeftRightMargins].
+func DECSLRM(left, right int) string {
+ return SetLeftRightMargins(left, right)
+}
+
+// SetScrollingRegion (DECSTBM) sets the top and bottom margins for the scrolling
+// region. The default is the entire screen.
+//
+// CSI ; r
+//
+// See: https://vt100.net/docs/vt510-rm/DECSTBM.html
+//
+// Deprecated: use [SetTopBottomMargins] instead.
+func SetScrollingRegion(t, b int) string {
+ if t < 0 {
+ t = 0
+ }
+ if b < 0 {
+ b = 0
+ }
+ return "\x1b[" + strconv.Itoa(t) + ";" + strconv.Itoa(b) + "r"
+}
+
+// InsertCharacter (ICH) inserts n blank characters at the current cursor
+// position. Existing characters move to the right. Characters moved past the
+// right margin are lost. ICH has no effect outside the scrolling margins.
+//
+// Default is 1.
+//
+// CSI Pn @
+//
+// See: https://vt100.net/docs/vt510-rm/ICH.html
+func InsertCharacter(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "@"
+}
+
+// ICH is an alias for [InsertCharacter].
+func ICH(n int) string {
+ return InsertCharacter(n)
+}
+
+// DeleteCharacter (DCH) deletes n characters at the current cursor position.
+// As the characters are deleted, the remaining characters move to the left and
+// the cursor remains at the same position.
+//
+// Default is 1.
+//
+// CSI Pn P
+//
+// See: https://vt100.net/docs/vt510-rm/DCH.html
+func DeleteCharacter(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "P"
+}
+
+// DCH is an alias for [DeleteCharacter].
+func DCH(n int) string {
+ return DeleteCharacter(n)
+}
+
+// SetTabEvery8Columns (DECST8C) sets the tab stops at every 8 columns.
+//
+// CSI ? 5 W
+//
+// See: https://vt100.net/docs/vt510-rm/DECST8C.html
+const (
+ SetTabEvery8Columns = "\x1b[?5W"
+ DECST8C = SetTabEvery8Columns
+)
+
+// HorizontalTabSet (HTS) sets a horizontal tab stop at the current cursor
+// column.
+//
+// This is equivalent to [HTS].
+//
+// ESC H
+//
+// See: https://vt100.net/docs/vt510-rm/HTS.html
+const HorizontalTabSet = "\x1bH"
+
+// TabClear (TBC) clears tab stops.
+//
+// Default is 0.
+//
+// Possible values:
+// 0: Clear tab stop at the current column. (default)
+// 3: Clear all tab stops.
+//
+// CSI Pn g
+//
+// See: https://vt100.net/docs/vt510-rm/TBC.html
+func TabClear(n int) string {
+ var s string
+ if n > 0 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "g"
+}
+
+// TBC is an alias for [TabClear].
+func TBC(n int) string {
+ return TabClear(n)
+}
+
+// RequestPresentationStateReport (DECRQPSR) requests the terminal to send a
+// report of the presentation state. This includes the cursor information [DECCIR],
+// and tab stop [DECTABSR] reports.
+//
+// Default is 0.
+//
+// Possible values:
+// 0: Error, request ignored.
+// 1: Cursor information report [DECCIR].
+// 2: Tab stop report [DECTABSR].
+//
+// CSI Ps $ w
+//
+// See: https://vt100.net/docs/vt510-rm/DECRQPSR.html
+func RequestPresentationStateReport(n int) string {
+ var s string
+ if n > 0 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "$w"
+}
+
+// DECRQPSR is an alias for [RequestPresentationStateReport].
+func DECRQPSR(n int) string {
+ return RequestPresentationStateReport(n)
+}
+
+// TabStopReport (DECTABSR) is the response to a tab stop report request.
+// It reports the tab stops set in the terminal.
+//
+// The response is a list of tab stops separated by a slash (/) character.
+//
+// DCS 2 $ u D ... D ST
+//
+// Where D is a decimal number representing a tab stop.
+//
+// See: https://vt100.net/docs/vt510-rm/DECTABSR.html
+func TabStopReport(stops ...int) string {
+ var s []string
+ for _, v := range stops {
+ s = append(s, strconv.Itoa(v))
+ }
+ return "\x1bP2$u" + strings.Join(s, "/") + "\x1b\\"
+}
+
+// DECTABSR is an alias for [TabStopReport].
+func DECTABSR(stops ...int) string {
+ return TabStopReport(stops...)
+}
+
+// CursorInformationReport (DECCIR) is the response to a cursor information
+// report request. It reports the cursor position, visual attributes, and
+// character protection attributes. It also reports the status of origin mode
+// [DECOM] and the current active character set.
+//
+// The response is a list of values separated by a semicolon (;) character.
+//
+// DCS 1 $ u D ... D ST
+//
+// Where D is a decimal number representing a value.
+//
+// See: https://vt100.net/docs/vt510-rm/DECCIR.html
+func CursorInformationReport(values ...int) string {
+ var s []string
+ for _, v := range values {
+ s = append(s, strconv.Itoa(v))
+ }
+ return "\x1bP1$u" + strings.Join(s, ";") + "\x1b\\"
+}
+
+// DECCIR is an alias for [CursorInformationReport].
+func DECCIR(values ...int) string {
+ return CursorInformationReport(values...)
+}
+
+// RepeatPreviousCharacter (REP) repeats the previous character n times.
+// This is identical to typing the same character n times.
+//
+// Default is 1.
+//
+// CSI Pn b
+//
+// See: ECMA-48 § 8.3.103
+func RepeatPreviousCharacter(n int) string {
+ var s string
+ if n > 1 {
+ s = strconv.Itoa(n)
+ }
+ return "\x1b[" + s + "b"
+}
+
+// REP is an alias for [RepeatPreviousCharacter].
+func REP(n int) string {
+ return RepeatPreviousCharacter(n)
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/sgr.go b/vendor/github.com/charmbracelet/x/ansi/sgr.go
new file mode 100644
index 00000000..1a18c98e
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/sgr.go
@@ -0,0 +1,95 @@
+package ansi
+
+import "strconv"
+
+// Select Graphic Rendition (SGR) is a command that sets display attributes.
+//
+// Default is 0.
+//
+// CSI Ps ; Ps ... m
+//
+// See: https://vt100.net/docs/vt510-rm/SGR.html
+func SelectGraphicRendition(ps ...Attr) string {
+ if len(ps) == 0 {
+ return ResetStyle
+ }
+
+ var s Style
+ for _, p := range ps {
+ attr, ok := attrStrings[p]
+ if ok {
+ s = append(s, attr)
+ } else {
+ if p < 0 {
+ p = 0
+ }
+ s = append(s, strconv.Itoa(p))
+ }
+ }
+
+ return s.String()
+}
+
+// SGR is an alias for [SelectGraphicRendition].
+func SGR(ps ...Attr) string {
+ return SelectGraphicRendition(ps...)
+}
+
+var attrStrings = map[int]string{
+ ResetAttr: "0",
+ BoldAttr: "1",
+ FaintAttr: "2",
+ ItalicAttr: "3",
+ UnderlineAttr: "4",
+ SlowBlinkAttr: "5",
+ RapidBlinkAttr: "6",
+ ReverseAttr: "7",
+ ConcealAttr: "8",
+ StrikethroughAttr: "9",
+ NoBoldAttr: "21",
+ NormalIntensityAttr: "22",
+ NoItalicAttr: "23",
+ NoUnderlineAttr: "24",
+ NoBlinkAttr: "25",
+ NoReverseAttr: "27",
+ NoConcealAttr: "28",
+ NoStrikethroughAttr: "29",
+ BlackForegroundColorAttr: "30",
+ RedForegroundColorAttr: "31",
+ GreenForegroundColorAttr: "32",
+ YellowForegroundColorAttr: "33",
+ BlueForegroundColorAttr: "34",
+ MagentaForegroundColorAttr: "35",
+ CyanForegroundColorAttr: "36",
+ WhiteForegroundColorAttr: "37",
+ ExtendedForegroundColorAttr: "38",
+ DefaultForegroundColorAttr: "39",
+ BlackBackgroundColorAttr: "40",
+ RedBackgroundColorAttr: "41",
+ GreenBackgroundColorAttr: "42",
+ YellowBackgroundColorAttr: "43",
+ BlueBackgroundColorAttr: "44",
+ MagentaBackgroundColorAttr: "45",
+ CyanBackgroundColorAttr: "46",
+ WhiteBackgroundColorAttr: "47",
+ ExtendedBackgroundColorAttr: "48",
+ DefaultBackgroundColorAttr: "49",
+ ExtendedUnderlineColorAttr: "58",
+ DefaultUnderlineColorAttr: "59",
+ BrightBlackForegroundColorAttr: "90",
+ BrightRedForegroundColorAttr: "91",
+ BrightGreenForegroundColorAttr: "92",
+ BrightYellowForegroundColorAttr: "93",
+ BrightBlueForegroundColorAttr: "94",
+ BrightMagentaForegroundColorAttr: "95",
+ BrightCyanForegroundColorAttr: "96",
+ BrightWhiteForegroundColorAttr: "97",
+ BrightBlackBackgroundColorAttr: "100",
+ BrightRedBackgroundColorAttr: "101",
+ BrightGreenBackgroundColorAttr: "102",
+ BrightYellowBackgroundColorAttr: "103",
+ BrightBlueBackgroundColorAttr: "104",
+ BrightMagentaBackgroundColorAttr: "105",
+ BrightCyanBackgroundColorAttr: "106",
+ BrightWhiteBackgroundColorAttr: "107",
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/status.go b/vendor/github.com/charmbracelet/x/ansi/status.go
new file mode 100644
index 00000000..4337e189
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/status.go
@@ -0,0 +1,144 @@
+package ansi
+
+import (
+ "strconv"
+ "strings"
+)
+
+// StatusReport represents a terminal status report.
+type StatusReport interface {
+ // StatusReport returns the status report identifier.
+ StatusReport() int
+}
+
+// ANSIReport represents an ANSI terminal status report.
+type ANSIStatusReport int //nolint:revive
+
+// Report returns the status report identifier.
+func (s ANSIStatusReport) StatusReport() int {
+ return int(s)
+}
+
+// DECStatusReport represents a DEC terminal status report.
+type DECStatusReport int
+
+// Status returns the status report identifier.
+func (s DECStatusReport) StatusReport() int {
+ return int(s)
+}
+
+// DeviceStatusReport (DSR) is a control sequence that reports the terminal's
+// status.
+// The terminal responds with a DSR sequence.
+//
+// CSI Ps n
+// CSI ? Ps n
+//
+// If one of the statuses is a [DECStatus], the sequence will use the DEC
+// format.
+//
+// See also https://vt100.net/docs/vt510-rm/DSR.html
+func DeviceStatusReport(statues ...StatusReport) string {
+ var dec bool
+ list := make([]string, len(statues))
+ seq := "\x1b["
+ for i, status := range statues {
+ list[i] = strconv.Itoa(status.StatusReport())
+ switch status.(type) {
+ case DECStatusReport:
+ dec = true
+ }
+ }
+ if dec {
+ seq += "?"
+ }
+ return seq + strings.Join(list, ";") + "n"
+}
+
+// DSR is an alias for [DeviceStatusReport].
+func DSR(status StatusReport) string {
+ return DeviceStatusReport(status)
+}
+
+// RequestCursorPositionReport is an escape sequence that requests the current
+// cursor position.
+//
+// CSI 6 n
+//
+// The terminal will report the cursor position as a CSI sequence in the
+// following format:
+//
+// CSI Pl ; Pc R
+//
+// Where Pl is the line number and Pc is the column number.
+// See: https://vt100.net/docs/vt510-rm/CPR.html
+const RequestCursorPositionReport = "\x1b[6n"
+
+// RequestExtendedCursorPositionReport (DECXCPR) is a sequence for requesting
+// the cursor position report including the current page number.
+//
+// CSI ? 6 n
+//
+// The terminal will report the cursor position as a CSI sequence in the
+// following format:
+//
+// CSI ? Pl ; Pc ; Pp R
+//
+// Where Pl is the line number, Pc is the column number, and Pp is the page
+// number.
+// See: https://vt100.net/docs/vt510-rm/DECXCPR.html
+const RequestExtendedCursorPositionReport = "\x1b[?6n"
+
+// CursorPositionReport (CPR) is a control sequence that reports the cursor's
+// position.
+//
+// CSI Pl ; Pc R
+//
+// Where Pl is the line number and Pc is the column number.
+//
+// See also https://vt100.net/docs/vt510-rm/CPR.html
+func CursorPositionReport(line, column int) string {
+ if line < 1 {
+ line = 1
+ }
+ if column < 1 {
+ column = 1
+ }
+ return "\x1b[" + strconv.Itoa(line) + ";" + strconv.Itoa(column) + "R"
+}
+
+// CPR is an alias for [CursorPositionReport].
+func CPR(line, column int) string {
+ return CursorPositionReport(line, column)
+}
+
+// ExtendedCursorPositionReport (DECXCPR) is a control sequence that reports the
+// cursor's position along with the page number (optional).
+//
+// CSI ? Pl ; Pc R
+// CSI ? Pl ; Pc ; Pv R
+//
+// Where Pl is the line number, Pc is the column number, and Pv is the page
+// number.
+//
+// If the page number is zero or negative, the returned sequence won't include
+// the page number.
+//
+// See also https://vt100.net/docs/vt510-rm/DECXCPR.html
+func ExtendedCursorPositionReport(line, column, page int) string {
+ if line < 1 {
+ line = 1
+ }
+ if column < 1 {
+ column = 1
+ }
+ if page < 1 {
+ return "\x1b[?" + strconv.Itoa(line) + ";" + strconv.Itoa(column) + "R"
+ }
+ return "\x1b[?" + strconv.Itoa(line) + ";" + strconv.Itoa(column) + ";" + strconv.Itoa(page) + "R"
+}
+
+// DECXCPR is an alias for [ExtendedCursorPositionReport].
+func DECXCPR(line, column, page int) string {
+ return ExtendedCursorPositionReport(line, column, page)
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/style.go b/vendor/github.com/charmbracelet/x/ansi/style.go
new file mode 100644
index 00000000..46ddcaa9
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/style.go
@@ -0,0 +1,660 @@
+package ansi
+
+import (
+ "image/color"
+ "strconv"
+ "strings"
+)
+
+// ResetStyle is a SGR (Select Graphic Rendition) style sequence that resets
+// all attributes.
+// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
+const ResetStyle = "\x1b[m"
+
+// Attr is a SGR (Select Graphic Rendition) style attribute.
+type Attr = int
+
+// Style represents an ANSI SGR (Select Graphic Rendition) style.
+type Style []string
+
+// String returns the ANSI SGR (Select Graphic Rendition) style sequence for
+// the given style.
+func (s Style) String() string {
+ if len(s) == 0 {
+ return ResetStyle
+ }
+ return "\x1b[" + strings.Join(s, ";") + "m"
+}
+
+// Styled returns a styled string with the given style applied.
+func (s Style) Styled(str string) string {
+ if len(s) == 0 {
+ return str
+ }
+ return s.String() + str + ResetStyle
+}
+
+// Reset appends the reset style attribute to the style.
+func (s Style) Reset() Style {
+ return append(s, resetAttr)
+}
+
+// Bold appends the bold style attribute to the style.
+func (s Style) Bold() Style {
+ return append(s, boldAttr)
+}
+
+// Faint appends the faint style attribute to the style.
+func (s Style) Faint() Style {
+ return append(s, faintAttr)
+}
+
+// Italic appends the italic style attribute to the style.
+func (s Style) Italic() Style {
+ return append(s, italicAttr)
+}
+
+// Underline appends the underline style attribute to the style.
+func (s Style) Underline() Style {
+ return append(s, underlineAttr)
+}
+
+// UnderlineStyle appends the underline style attribute to the style.
+func (s Style) UnderlineStyle(u UnderlineStyle) Style {
+ switch u {
+ case NoUnderlineStyle:
+ return s.NoUnderline()
+ case SingleUnderlineStyle:
+ return s.Underline()
+ case DoubleUnderlineStyle:
+ return append(s, doubleUnderlineStyle)
+ case CurlyUnderlineStyle:
+ return append(s, curlyUnderlineStyle)
+ case DottedUnderlineStyle:
+ return append(s, dottedUnderlineStyle)
+ case DashedUnderlineStyle:
+ return append(s, dashedUnderlineStyle)
+ }
+ return s
+}
+
+// DoubleUnderline appends the double underline style attribute to the style.
+// This is a convenience method for UnderlineStyle(DoubleUnderlineStyle).
+func (s Style) DoubleUnderline() Style {
+ return s.UnderlineStyle(DoubleUnderlineStyle)
+}
+
+// CurlyUnderline appends the curly underline style attribute to the style.
+// This is a convenience method for UnderlineStyle(CurlyUnderlineStyle).
+func (s Style) CurlyUnderline() Style {
+ return s.UnderlineStyle(CurlyUnderlineStyle)
+}
+
+// DottedUnderline appends the dotted underline style attribute to the style.
+// This is a convenience method for UnderlineStyle(DottedUnderlineStyle).
+func (s Style) DottedUnderline() Style {
+ return s.UnderlineStyle(DottedUnderlineStyle)
+}
+
+// DashedUnderline appends the dashed underline style attribute to the style.
+// This is a convenience method for UnderlineStyle(DashedUnderlineStyle).
+func (s Style) DashedUnderline() Style {
+ return s.UnderlineStyle(DashedUnderlineStyle)
+}
+
+// SlowBlink appends the slow blink style attribute to the style.
+func (s Style) SlowBlink() Style {
+ return append(s, slowBlinkAttr)
+}
+
+// RapidBlink appends the rapid blink style attribute to the style.
+func (s Style) RapidBlink() Style {
+ return append(s, rapidBlinkAttr)
+}
+
+// Reverse appends the reverse style attribute to the style.
+func (s Style) Reverse() Style {
+ return append(s, reverseAttr)
+}
+
+// Conceal appends the conceal style attribute to the style.
+func (s Style) Conceal() Style {
+ return append(s, concealAttr)
+}
+
+// Strikethrough appends the strikethrough style attribute to the style.
+func (s Style) Strikethrough() Style {
+ return append(s, strikethroughAttr)
+}
+
+// NoBold appends the no bold style attribute to the style.
+func (s Style) NoBold() Style {
+ return append(s, noBoldAttr)
+}
+
+// NormalIntensity appends the normal intensity style attribute to the style.
+func (s Style) NormalIntensity() Style {
+ return append(s, normalIntensityAttr)
+}
+
+// NoItalic appends the no italic style attribute to the style.
+func (s Style) NoItalic() Style {
+ return append(s, noItalicAttr)
+}
+
+// NoUnderline appends the no underline style attribute to the style.
+func (s Style) NoUnderline() Style {
+ return append(s, noUnderlineAttr)
+}
+
+// NoBlink appends the no blink style attribute to the style.
+func (s Style) NoBlink() Style {
+ return append(s, noBlinkAttr)
+}
+
+// NoReverse appends the no reverse style attribute to the style.
+func (s Style) NoReverse() Style {
+ return append(s, noReverseAttr)
+}
+
+// NoConceal appends the no conceal style attribute to the style.
+func (s Style) NoConceal() Style {
+ return append(s, noConcealAttr)
+}
+
+// NoStrikethrough appends the no strikethrough style attribute to the style.
+func (s Style) NoStrikethrough() Style {
+ return append(s, noStrikethroughAttr)
+}
+
+// DefaultForegroundColor appends the default foreground color style attribute to the style.
+func (s Style) DefaultForegroundColor() Style {
+ return append(s, defaultForegroundColorAttr)
+}
+
+// DefaultBackgroundColor appends the default background color style attribute to the style.
+func (s Style) DefaultBackgroundColor() Style {
+ return append(s, defaultBackgroundColorAttr)
+}
+
+// DefaultUnderlineColor appends the default underline color style attribute to the style.
+func (s Style) DefaultUnderlineColor() Style {
+ return append(s, defaultUnderlineColorAttr)
+}
+
+// ForegroundColor appends the foreground color style attribute to the style.
+func (s Style) ForegroundColor(c Color) Style {
+ return append(s, foregroundColorString(c))
+}
+
+// BackgroundColor appends the background color style attribute to the style.
+func (s Style) BackgroundColor(c Color) Style {
+ return append(s, backgroundColorString(c))
+}
+
+// UnderlineColor appends the underline color style attribute to the style.
+func (s Style) UnderlineColor(c Color) Style {
+ return append(s, underlineColorString(c))
+}
+
+// UnderlineStyle represents an ANSI SGR (Select Graphic Rendition) underline
+// style.
+type UnderlineStyle = byte
+
+const (
+ doubleUnderlineStyle = "4:2"
+ curlyUnderlineStyle = "4:3"
+ dottedUnderlineStyle = "4:4"
+ dashedUnderlineStyle = "4:5"
+)
+
+const (
+ // NoUnderlineStyle is the default underline style.
+ NoUnderlineStyle UnderlineStyle = iota
+ // SingleUnderlineStyle is a single underline style.
+ SingleUnderlineStyle
+ // DoubleUnderlineStyle is a double underline style.
+ DoubleUnderlineStyle
+ // CurlyUnderlineStyle is a curly underline style.
+ CurlyUnderlineStyle
+ // DottedUnderlineStyle is a dotted underline style.
+ DottedUnderlineStyle
+ // DashedUnderlineStyle is a dashed underline style.
+ DashedUnderlineStyle
+)
+
+// SGR (Select Graphic Rendition) style attributes.
+// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
+const (
+ ResetAttr Attr = 0
+ BoldAttr Attr = 1
+ FaintAttr Attr = 2
+ ItalicAttr Attr = 3
+ UnderlineAttr Attr = 4
+ SlowBlinkAttr Attr = 5
+ RapidBlinkAttr Attr = 6
+ ReverseAttr Attr = 7
+ ConcealAttr Attr = 8
+ StrikethroughAttr Attr = 9
+ NoBoldAttr Attr = 21 // Some terminals treat this as double underline.
+ NormalIntensityAttr Attr = 22
+ NoItalicAttr Attr = 23
+ NoUnderlineAttr Attr = 24
+ NoBlinkAttr Attr = 25
+ NoReverseAttr Attr = 27
+ NoConcealAttr Attr = 28
+ NoStrikethroughAttr Attr = 29
+ BlackForegroundColorAttr Attr = 30
+ RedForegroundColorAttr Attr = 31
+ GreenForegroundColorAttr Attr = 32
+ YellowForegroundColorAttr Attr = 33
+ BlueForegroundColorAttr Attr = 34
+ MagentaForegroundColorAttr Attr = 35
+ CyanForegroundColorAttr Attr = 36
+ WhiteForegroundColorAttr Attr = 37
+ ExtendedForegroundColorAttr Attr = 38
+ DefaultForegroundColorAttr Attr = 39
+ BlackBackgroundColorAttr Attr = 40
+ RedBackgroundColorAttr Attr = 41
+ GreenBackgroundColorAttr Attr = 42
+ YellowBackgroundColorAttr Attr = 43
+ BlueBackgroundColorAttr Attr = 44
+ MagentaBackgroundColorAttr Attr = 45
+ CyanBackgroundColorAttr Attr = 46
+ WhiteBackgroundColorAttr Attr = 47
+ ExtendedBackgroundColorAttr Attr = 48
+ DefaultBackgroundColorAttr Attr = 49
+ ExtendedUnderlineColorAttr Attr = 58
+ DefaultUnderlineColorAttr Attr = 59
+ BrightBlackForegroundColorAttr Attr = 90
+ BrightRedForegroundColorAttr Attr = 91
+ BrightGreenForegroundColorAttr Attr = 92
+ BrightYellowForegroundColorAttr Attr = 93
+ BrightBlueForegroundColorAttr Attr = 94
+ BrightMagentaForegroundColorAttr Attr = 95
+ BrightCyanForegroundColorAttr Attr = 96
+ BrightWhiteForegroundColorAttr Attr = 97
+ BrightBlackBackgroundColorAttr Attr = 100
+ BrightRedBackgroundColorAttr Attr = 101
+ BrightGreenBackgroundColorAttr Attr = 102
+ BrightYellowBackgroundColorAttr Attr = 103
+ BrightBlueBackgroundColorAttr Attr = 104
+ BrightMagentaBackgroundColorAttr Attr = 105
+ BrightCyanBackgroundColorAttr Attr = 106
+ BrightWhiteBackgroundColorAttr Attr = 107
+
+ RGBColorIntroducerAttr Attr = 2
+ ExtendedColorIntroducerAttr Attr = 5
+)
+
+const (
+ resetAttr = "0"
+ boldAttr = "1"
+ faintAttr = "2"
+ italicAttr = "3"
+ underlineAttr = "4"
+ slowBlinkAttr = "5"
+ rapidBlinkAttr = "6"
+ reverseAttr = "7"
+ concealAttr = "8"
+ strikethroughAttr = "9"
+ noBoldAttr = "21"
+ normalIntensityAttr = "22"
+ noItalicAttr = "23"
+ noUnderlineAttr = "24"
+ noBlinkAttr = "25"
+ noReverseAttr = "27"
+ noConcealAttr = "28"
+ noStrikethroughAttr = "29"
+ blackForegroundColorAttr = "30"
+ redForegroundColorAttr = "31"
+ greenForegroundColorAttr = "32"
+ yellowForegroundColorAttr = "33"
+ blueForegroundColorAttr = "34"
+ magentaForegroundColorAttr = "35"
+ cyanForegroundColorAttr = "36"
+ whiteForegroundColorAttr = "37"
+ extendedForegroundColorAttr = "38"
+ defaultForegroundColorAttr = "39"
+ blackBackgroundColorAttr = "40"
+ redBackgroundColorAttr = "41"
+ greenBackgroundColorAttr = "42"
+ yellowBackgroundColorAttr = "43"
+ blueBackgroundColorAttr = "44"
+ magentaBackgroundColorAttr = "45"
+ cyanBackgroundColorAttr = "46"
+ whiteBackgroundColorAttr = "47"
+ extendedBackgroundColorAttr = "48"
+ defaultBackgroundColorAttr = "49"
+ extendedUnderlineColorAttr = "58"
+ defaultUnderlineColorAttr = "59"
+ brightBlackForegroundColorAttr = "90"
+ brightRedForegroundColorAttr = "91"
+ brightGreenForegroundColorAttr = "92"
+ brightYellowForegroundColorAttr = "93"
+ brightBlueForegroundColorAttr = "94"
+ brightMagentaForegroundColorAttr = "95"
+ brightCyanForegroundColorAttr = "96"
+ brightWhiteForegroundColorAttr = "97"
+ brightBlackBackgroundColorAttr = "100"
+ brightRedBackgroundColorAttr = "101"
+ brightGreenBackgroundColorAttr = "102"
+ brightYellowBackgroundColorAttr = "103"
+ brightBlueBackgroundColorAttr = "104"
+ brightMagentaBackgroundColorAttr = "105"
+ brightCyanBackgroundColorAttr = "106"
+ brightWhiteBackgroundColorAttr = "107"
+)
+
+// foregroundColorString returns the style SGR attribute for the given
+// foreground color.
+// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
+func foregroundColorString(c Color) string {
+ switch c := c.(type) {
+ case BasicColor:
+ // 3-bit or 4-bit ANSI foreground
+ // "3" or "9" where n is the color number from 0 to 7
+ switch c {
+ case Black:
+ return blackForegroundColorAttr
+ case Red:
+ return redForegroundColorAttr
+ case Green:
+ return greenForegroundColorAttr
+ case Yellow:
+ return yellowForegroundColorAttr
+ case Blue:
+ return blueForegroundColorAttr
+ case Magenta:
+ return magentaForegroundColorAttr
+ case Cyan:
+ return cyanForegroundColorAttr
+ case White:
+ return whiteForegroundColorAttr
+ case BrightBlack:
+ return brightBlackForegroundColorAttr
+ case BrightRed:
+ return brightRedForegroundColorAttr
+ case BrightGreen:
+ return brightGreenForegroundColorAttr
+ case BrightYellow:
+ return brightYellowForegroundColorAttr
+ case BrightBlue:
+ return brightBlueForegroundColorAttr
+ case BrightMagenta:
+ return brightMagentaForegroundColorAttr
+ case BrightCyan:
+ return brightCyanForegroundColorAttr
+ case BrightWhite:
+ return brightWhiteForegroundColorAttr
+ }
+ case ExtendedColor:
+ // 256-color ANSI foreground
+ // "38;5;"
+ return "38;5;" + strconv.FormatUint(uint64(c), 10)
+ case TrueColor, color.Color:
+ // 24-bit "true color" foreground
+ // "38;2;;;"
+ r, g, b, _ := c.RGBA()
+ return "38;2;" +
+ strconv.FormatUint(uint64(shift(r)), 10) + ";" +
+ strconv.FormatUint(uint64(shift(g)), 10) + ";" +
+ strconv.FormatUint(uint64(shift(b)), 10)
+ }
+ return defaultForegroundColorAttr
+}
+
+// backgroundColorString returns the style SGR attribute for the given
+// background color.
+// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
+func backgroundColorString(c Color) string {
+ switch c := c.(type) {
+ case BasicColor:
+ // 3-bit or 4-bit ANSI foreground
+ // "4" or "10" where n is the color number from 0 to 7
+ switch c {
+ case Black:
+ return blackBackgroundColorAttr
+ case Red:
+ return redBackgroundColorAttr
+ case Green:
+ return greenBackgroundColorAttr
+ case Yellow:
+ return yellowBackgroundColorAttr
+ case Blue:
+ return blueBackgroundColorAttr
+ case Magenta:
+ return magentaBackgroundColorAttr
+ case Cyan:
+ return cyanBackgroundColorAttr
+ case White:
+ return whiteBackgroundColorAttr
+ case BrightBlack:
+ return brightBlackBackgroundColorAttr
+ case BrightRed:
+ return brightRedBackgroundColorAttr
+ case BrightGreen:
+ return brightGreenBackgroundColorAttr
+ case BrightYellow:
+ return brightYellowBackgroundColorAttr
+ case BrightBlue:
+ return brightBlueBackgroundColorAttr
+ case BrightMagenta:
+ return brightMagentaBackgroundColorAttr
+ case BrightCyan:
+ return brightCyanBackgroundColorAttr
+ case BrightWhite:
+ return brightWhiteBackgroundColorAttr
+ }
+ case ExtendedColor:
+ // 256-color ANSI foreground
+ // "48;5;"
+ return "48;5;" + strconv.FormatUint(uint64(c), 10)
+ case TrueColor, color.Color:
+ // 24-bit "true color" foreground
+ // "38;2;;;"
+ r, g, b, _ := c.RGBA()
+ return "48;2;" +
+ strconv.FormatUint(uint64(shift(r)), 10) + ";" +
+ strconv.FormatUint(uint64(shift(g)), 10) + ";" +
+ strconv.FormatUint(uint64(shift(b)), 10)
+ }
+ return defaultBackgroundColorAttr
+}
+
+// underlineColorString returns the style SGR attribute for the given underline
+// color.
+// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
+func underlineColorString(c Color) string {
+ switch c := c.(type) {
+ // NOTE: we can't use 3-bit and 4-bit ANSI color codes with underline
+ // color, use 256-color instead.
+ //
+ // 256-color ANSI underline color
+ // "58;5;"
+ case BasicColor:
+ return "58;5;" + strconv.FormatUint(uint64(c), 10)
+ case ExtendedColor:
+ return "58;5;" + strconv.FormatUint(uint64(c), 10)
+ case TrueColor, color.Color:
+ // 24-bit "true color" foreground
+ // "38;2;;;"
+ r, g, b, _ := c.RGBA()
+ return "58;2;" +
+ strconv.FormatUint(uint64(shift(r)), 10) + ";" +
+ strconv.FormatUint(uint64(shift(g)), 10) + ";" +
+ strconv.FormatUint(uint64(shift(b)), 10)
+ }
+ return defaultUnderlineColorAttr
+}
+
+// ReadStyleColor decodes a color from a slice of parameters. It returns the
+// number of parameters read and the color. This function is used to read SGR
+// color parameters following the ITU T.416 standard.
+//
+// It supports reading the following color types:
+// - 0: implementation defined
+// - 1: transparent
+// - 2: RGB direct color
+// - 3: CMY direct color
+// - 4: CMYK direct color
+// - 5: indexed color
+// - 6: RGBA direct color (WezTerm extension)
+//
+// The parameters can be separated by semicolons (;) or colons (:). Mixing
+// separators is not allowed.
+//
+// The specs supports defining a color space id, a color tolerance value, and a
+// tolerance color space id. However, these values have no effect on the
+// returned color and will be ignored.
+//
+// This implementation includes a few modifications to the specs:
+// 1. Support for legacy color values separated by semicolons (;) with respect to RGB, and indexed colors
+// 2. Support ignoring and omitting the color space id (second parameter) with respect to RGB colors
+// 3. Support ignoring and omitting the 6th parameter with respect to RGB and CMY colors
+// 4. Support reading RGBA colors
+func ReadStyleColor(params Params, co *color.Color) (n int) {
+ if len(params) < 2 { // Need at least SGR type and color type
+ return 0
+ }
+
+ // First parameter indicates one of 38, 48, or 58 (foreground, background, or underline)
+ s := params[0]
+ p := params[1]
+ colorType := p.Param(0)
+ n = 2
+
+ paramsfn := func() (p1, p2, p3, p4 int) {
+ // Where should we start reading the color?
+ switch {
+ case s.HasMore() && p.HasMore() && len(params) > 8 && params[2].HasMore() && params[3].HasMore() && params[4].HasMore() && params[5].HasMore() && params[6].HasMore() && params[7].HasMore():
+ // We have color space id, a 6th parameter, a tolerance value, and a tolerance color space
+ n += 7
+ return params[3].Param(0), params[4].Param(0), params[5].Param(0), params[6].Param(0)
+ case s.HasMore() && p.HasMore() && len(params) > 7 && params[2].HasMore() && params[3].HasMore() && params[4].HasMore() && params[5].HasMore() && params[6].HasMore():
+ // We have color space id, a 6th parameter, and a tolerance value
+ n += 6
+ return params[3].Param(0), params[4].Param(0), params[5].Param(0), params[6].Param(0)
+ case s.HasMore() && p.HasMore() && len(params) > 6 && params[2].HasMore() && params[3].HasMore() && params[4].HasMore() && params[5].HasMore():
+ // We have color space id and a 6th parameter
+ // 48 : 4 : : 1 : 2 : 3 :4
+ n += 5
+ return params[3].Param(0), params[4].Param(0), params[5].Param(0), params[6].Param(0)
+ case s.HasMore() && p.HasMore() && len(params) > 5 && params[2].HasMore() && params[3].HasMore() && params[4].HasMore() && !params[5].HasMore():
+ // We have color space
+ // 48 : 3 : : 1 : 2 : 3
+ n += 4
+ return params[3].Param(0), params[4].Param(0), params[5].Param(0), -1
+ case s.HasMore() && p.HasMore() && p.Param(0) == 2 && params[2].HasMore() && params[3].HasMore() && !params[4].HasMore():
+ // We have color values separated by colons (:)
+ // 48 : 2 : 1 : 2 : 3
+ fallthrough
+ case !s.HasMore() && !p.HasMore() && p.Param(0) == 2 && !params[2].HasMore() && !params[3].HasMore() && !params[4].HasMore():
+ // Support legacy color values separated by semicolons (;)
+ // 48 ; 2 ; 1 ; 2 ; 3
+ n += 3
+ return params[2].Param(0), params[3].Param(0), params[4].Param(0), -1
+ }
+ // Ambiguous SGR color
+ return -1, -1, -1, -1
+ }
+
+ switch colorType {
+ case 0: // implementation defined
+ return 2
+ case 1: // transparent
+ *co = color.Transparent
+ return 2
+ case 2: // RGB direct color
+ if len(params) < 5 {
+ return 0
+ }
+
+ r, g, b, _ := paramsfn()
+ if r == -1 || g == -1 || b == -1 {
+ return 0
+ }
+
+ *co = color.RGBA{
+ R: uint8(r), //nolint:gosec
+ G: uint8(g), //nolint:gosec
+ B: uint8(b), //nolint:gosec
+ A: 0xff,
+ }
+ return
+
+ case 3: // CMY direct color
+ if len(params) < 5 {
+ return 0
+ }
+
+ c, m, y, _ := paramsfn()
+ if c == -1 || m == -1 || y == -1 {
+ return 0
+ }
+
+ *co = color.CMYK{
+ C: uint8(c), //nolint:gosec
+ M: uint8(m), //nolint:gosec
+ Y: uint8(y), //nolint:gosec
+ K: 0,
+ }
+ return
+
+ case 4: // CMYK direct color
+ if len(params) < 6 {
+ return 0
+ }
+
+ c, m, y, k := paramsfn()
+ if c == -1 || m == -1 || y == -1 || k == -1 {
+ return 0
+ }
+
+ *co = color.CMYK{
+ C: uint8(c), //nolint:gosec
+ M: uint8(m), //nolint:gosec
+ Y: uint8(y), //nolint:gosec
+ K: uint8(k), //nolint:gosec
+ }
+ return
+
+ case 5: // indexed color
+ if len(params) < 3 {
+ return 0
+ }
+ switch {
+ case s.HasMore() && p.HasMore() && !params[2].HasMore():
+ // Colon separated indexed color
+ // 38 : 5 : 234
+ case !s.HasMore() && !p.HasMore() && !params[2].HasMore():
+ // Legacy semicolon indexed color
+ // 38 ; 5 ; 234
+ default:
+ return 0
+ }
+ *co = ExtendedColor(params[2].Param(0)) //nolint:gosec
+ return 3
+
+ case 6: // RGBA direct color
+ if len(params) < 6 {
+ return 0
+ }
+
+ r, g, b, a := paramsfn()
+ if r == -1 || g == -1 || b == -1 || a == -1 {
+ return 0
+ }
+
+ *co = color.RGBA{
+ R: uint8(r), //nolint:gosec
+ G: uint8(g), //nolint:gosec
+ B: uint8(b), //nolint:gosec
+ A: uint8(a), //nolint:gosec
+ }
+ return
+
+ default:
+ return 0
+ }
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/termcap.go b/vendor/github.com/charmbracelet/x/ansi/termcap.go
new file mode 100644
index 00000000..3c5c7da9
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/termcap.go
@@ -0,0 +1,41 @@
+package ansi
+
+import (
+ "encoding/hex"
+ "strings"
+)
+
+// RequestTermcap (XTGETTCAP) requests Termcap/Terminfo strings.
+//
+// DCS + q ST
+//
+// Where is a list of Termcap/Terminfo capabilities, encoded in 2-digit
+// hexadecimals, separated by semicolons.
+//
+// See: https://man7.org/linux/man-pages/man5/terminfo.5.html
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
+func XTGETTCAP(caps ...string) string {
+ if len(caps) == 0 {
+ return ""
+ }
+
+ s := "\x1bP+q"
+ for i, c := range caps {
+ if i > 0 {
+ s += ";"
+ }
+ s += strings.ToUpper(hex.EncodeToString([]byte(c)))
+ }
+
+ return s + "\x1b\\"
+}
+
+// RequestTermcap is an alias for [XTGETTCAP].
+func RequestTermcap(caps ...string) string {
+ return XTGETTCAP(caps...)
+}
+
+// RequestTerminfo is an alias for [XTGETTCAP].
+func RequestTerminfo(caps ...string) string {
+ return XTGETTCAP(caps...)
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/title.go b/vendor/github.com/charmbracelet/x/ansi/title.go
new file mode 100644
index 00000000..8fd8bf98
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/title.go
@@ -0,0 +1,32 @@
+package ansi
+
+// SetIconNameWindowTitle returns a sequence for setting the icon name and
+// window title.
+//
+// OSC 0 ; title ST
+// OSC 0 ; title BEL
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands
+func SetIconNameWindowTitle(s string) string {
+ return "\x1b]0;" + s + "\x07"
+}
+
+// SetIconName returns a sequence for setting the icon name.
+//
+// OSC 1 ; title ST
+// OSC 1 ; title BEL
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands
+func SetIconName(s string) string {
+ return "\x1b]1;" + s + "\x07"
+}
+
+// SetWindowTitle returns a sequence for setting the window title.
+//
+// OSC 2 ; title ST
+// OSC 2 ; title BEL
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands
+func SetWindowTitle(s string) string {
+ return "\x1b]2;" + s + "\x07"
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/truncate.go b/vendor/github.com/charmbracelet/x/ansi/truncate.go
new file mode 100644
index 00000000..1fa3efef
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/truncate.go
@@ -0,0 +1,282 @@
+package ansi
+
+import (
+ "bytes"
+
+ "github.com/charmbracelet/x/ansi/parser"
+ "github.com/mattn/go-runewidth"
+ "github.com/rivo/uniseg"
+)
+
+// Cut the string, without adding any prefix or tail strings. This function is
+// aware of ANSI escape codes and will not break them, and accounts for
+// wide-characters (such as East-Asian characters and emojis). Note that the
+// [left] parameter is inclusive, while [right] isn't.
+// This treats the text as a sequence of graphemes.
+func Cut(s string, left, right int) string {
+ return cut(GraphemeWidth, s, left, right)
+}
+
+// CutWc the string, without adding any prefix or tail strings. This function is
+// aware of ANSI escape codes and will not break them, and accounts for
+// wide-characters (such as East-Asian characters and emojis). Note that the
+// [left] parameter is inclusive, while [right] isn't.
+// This treats the text as a sequence of wide characters and runes.
+func CutWc(s string, left, right int) string {
+ return cut(WcWidth, s, left, right)
+}
+
+func cut(m Method, s string, left, right int) string {
+ if right <= left {
+ return ""
+ }
+
+ truncate := Truncate
+ truncateLeft := TruncateLeft
+ if m == WcWidth {
+ truncate = TruncateWc
+ truncateLeft = TruncateWc
+ }
+
+ if left == 0 {
+ return truncate(s, right, "")
+ }
+ return truncateLeft(Truncate(s, right, ""), left, "")
+}
+
+// Truncate truncates a string to a given length, adding a tail to the end if
+// the string is longer than the given length. This function is aware of ANSI
+// escape codes and will not break them, and accounts for wide-characters (such
+// as East-Asian characters and emojis).
+// This treats the text as a sequence of graphemes.
+func Truncate(s string, length int, tail string) string {
+ return truncate(GraphemeWidth, s, length, tail)
+}
+
+// TruncateWc truncates a string to a given length, adding a tail to the end if
+// the string is longer than the given length. This function is aware of ANSI
+// escape codes and will not break them, and accounts for wide-characters (such
+// as East-Asian characters and emojis).
+// This treats the text as a sequence of wide characters and runes.
+func TruncateWc(s string, length int, tail string) string {
+ return truncate(WcWidth, s, length, tail)
+}
+
+func truncate(m Method, s string, length int, tail string) string {
+ if sw := StringWidth(s); sw <= length {
+ return s
+ }
+
+ tw := StringWidth(tail)
+ length -= tw
+ if length < 0 {
+ return ""
+ }
+
+ var cluster []byte
+ var buf bytes.Buffer
+ curWidth := 0
+ ignoring := false
+ pstate := parser.GroundState // initial state
+ b := []byte(s)
+ i := 0
+
+ // Here we iterate over the bytes of the string and collect printable
+ // characters and runes. We also keep track of the width of the string
+ // in cells.
+ //
+ // Once we reach the given length, we start ignoring characters and only
+ // collect ANSI escape codes until we reach the end of string.
+ for i < len(b) {
+ state, action := parser.Table.Transition(pstate, b[i])
+ if state == parser.Utf8State {
+ // This action happens when we transition to the Utf8State.
+ var width int
+ cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
+ if m == WcWidth {
+ width = runewidth.StringWidth(string(cluster))
+ }
+
+ // increment the index by the length of the cluster
+ i += len(cluster)
+
+ // Are we ignoring? Skip to the next byte
+ if ignoring {
+ continue
+ }
+
+ // Is this gonna be too wide?
+ // If so write the tail and stop collecting.
+ if curWidth+width > length && !ignoring {
+ ignoring = true
+ buf.WriteString(tail)
+ }
+
+ if curWidth+width > length {
+ continue
+ }
+
+ curWidth += width
+ buf.Write(cluster)
+
+ // Done collecting, now we're back in the ground state.
+ pstate = parser.GroundState
+ continue
+ }
+
+ switch action {
+ case parser.PrintAction:
+ // Is this gonna be too wide?
+ // If so write the tail and stop collecting.
+ if curWidth >= length && !ignoring {
+ ignoring = true
+ buf.WriteString(tail)
+ }
+
+ // Skip to the next byte if we're ignoring
+ if ignoring {
+ i++
+ continue
+ }
+
+ // collects printable ASCII
+ curWidth++
+ fallthrough
+ default:
+ buf.WriteByte(b[i])
+ i++
+ }
+
+ // Transition to the next state.
+ pstate = state
+
+ // Once we reach the given length, we start ignoring runes and write
+ // the tail to the buffer.
+ if curWidth > length && !ignoring {
+ ignoring = true
+ buf.WriteString(tail)
+ }
+ }
+
+ return buf.String()
+}
+
+// TruncateLeft truncates a string from the left side by removing n characters,
+// adding a prefix to the beginning if the string is longer than n.
+// This function is aware of ANSI escape codes and will not break them, and
+// accounts for wide-characters (such as East-Asian characters and emojis).
+// This treats the text as a sequence of graphemes.
+func TruncateLeft(s string, n int, prefix string) string {
+ return truncateLeft(GraphemeWidth, s, n, prefix)
+}
+
+// TruncateLeftWc truncates a string from the left side by removing n characters,
+// adding a prefix to the beginning if the string is longer than n.
+// This function is aware of ANSI escape codes and will not break them, and
+// accounts for wide-characters (such as East-Asian characters and emojis).
+// This treats the text as a sequence of wide characters and runes.
+func TruncateLeftWc(s string, n int, prefix string) string {
+ return truncateLeft(WcWidth, s, n, prefix)
+}
+
+func truncateLeft(m Method, s string, n int, prefix string) string {
+ if n <= 0 {
+ return s
+ }
+
+ var cluster []byte
+ var buf bytes.Buffer
+ curWidth := 0
+ ignoring := true
+ pstate := parser.GroundState
+ b := []byte(s)
+ i := 0
+
+ for i < len(b) {
+ if !ignoring {
+ buf.Write(b[i:])
+ break
+ }
+
+ state, action := parser.Table.Transition(pstate, b[i])
+ if state == parser.Utf8State {
+ var width int
+ cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
+ if m == WcWidth {
+ width = runewidth.StringWidth(string(cluster))
+ }
+
+ i += len(cluster)
+ curWidth += width
+
+ if curWidth > n && ignoring {
+ ignoring = false
+ buf.WriteString(prefix)
+ }
+
+ if ignoring {
+ continue
+ }
+
+ if curWidth > n {
+ buf.Write(cluster)
+ }
+
+ pstate = parser.GroundState
+ continue
+ }
+
+ switch action {
+ case parser.PrintAction:
+ curWidth++
+
+ if curWidth > n && ignoring {
+ ignoring = false
+ buf.WriteString(prefix)
+ }
+
+ if ignoring {
+ i++
+ continue
+ }
+
+ fallthrough
+ default:
+ buf.WriteByte(b[i])
+ i++
+ }
+
+ pstate = state
+ if curWidth > n && ignoring {
+ ignoring = false
+ buf.WriteString(prefix)
+ }
+ }
+
+ return buf.String()
+}
+
+// ByteToGraphemeRange takes start and stop byte positions and converts them to
+// grapheme-aware char positions.
+// You can use this with [Truncate], [TruncateLeft], and [Cut].
+func ByteToGraphemeRange(str string, byteStart, byteStop int) (charStart, charStop int) {
+ bytePos, charPos := 0, 0
+ gr := uniseg.NewGraphemes(str)
+ for byteStart > bytePos {
+ if !gr.Next() {
+ break
+ }
+ bytePos += len(gr.Str())
+ charPos += max(1, gr.Width())
+ }
+ charStart = charPos
+ for byteStop > bytePos {
+ if !gr.Next() {
+ break
+ }
+ bytePos += len(gr.Str())
+ charPos += max(1, gr.Width())
+ }
+ charStop = charPos
+ return
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/util.go b/vendor/github.com/charmbracelet/x/ansi/util.go
new file mode 100644
index 00000000..301ef15f
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/util.go
@@ -0,0 +1,106 @@
+package ansi
+
+import (
+ "fmt"
+ "image/color"
+ "strconv"
+ "strings"
+
+ "github.com/lucasb-eyer/go-colorful"
+)
+
+// colorToHexString returns a hex string representation of a color.
+func colorToHexString(c color.Color) string {
+ if c == nil {
+ return ""
+ }
+ shift := func(v uint32) uint32 {
+ if v > 0xff {
+ return v >> 8
+ }
+ return v
+ }
+ r, g, b, _ := c.RGBA()
+ r, g, b = shift(r), shift(g), shift(b)
+ return fmt.Sprintf("#%02x%02x%02x", r, g, b)
+}
+
+// rgbToHex converts red, green, and blue values to a hexadecimal value.
+//
+// hex := rgbToHex(0, 0, 255) // 0x0000FF
+func rgbToHex(r, g, b uint32) uint32 {
+ return r<<16 + g<<8 + b
+}
+
+type shiftable interface {
+ ~uint | ~uint16 | ~uint32 | ~uint64
+}
+
+func shift[T shiftable](x T) T {
+ if x > 0xff {
+ x >>= 8
+ }
+ return x
+}
+
+// XParseColor is a helper function that parses a string into a color.Color. It
+// provides a similar interface to the XParseColor function in Xlib. It
+// supports the following formats:
+//
+// - #RGB
+// - #RRGGBB
+// - rgb:RRRR/GGGG/BBBB
+// - rgba:RRRR/GGGG/BBBB/AAAA
+//
+// If the string is not a valid color, nil is returned.
+//
+// See: https://linux.die.net/man/3/xparsecolor
+func XParseColor(s string) color.Color {
+ switch {
+ case strings.HasPrefix(s, "#"):
+ c, err := colorful.Hex(s)
+ if err != nil {
+ return nil
+ }
+
+ return c
+ case strings.HasPrefix(s, "rgb:"):
+ parts := strings.Split(s[4:], "/")
+ if len(parts) != 3 {
+ return nil
+ }
+
+ r, _ := strconv.ParseUint(parts[0], 16, 32)
+ g, _ := strconv.ParseUint(parts[1], 16, 32)
+ b, _ := strconv.ParseUint(parts[2], 16, 32)
+
+ return color.RGBA{uint8(shift(r)), uint8(shift(g)), uint8(shift(b)), 255} //nolint:gosec
+ case strings.HasPrefix(s, "rgba:"):
+ parts := strings.Split(s[5:], "/")
+ if len(parts) != 4 {
+ return nil
+ }
+
+ r, _ := strconv.ParseUint(parts[0], 16, 32)
+ g, _ := strconv.ParseUint(parts[1], 16, 32)
+ b, _ := strconv.ParseUint(parts[2], 16, 32)
+ a, _ := strconv.ParseUint(parts[3], 16, 32)
+
+ return color.RGBA{uint8(shift(r)), uint8(shift(g)), uint8(shift(b)), uint8(shift(a))} //nolint:gosec
+ }
+ return nil
+}
+
+type ordered interface {
+ ~int | ~int8 | ~int16 | ~int32 | ~int64 |
+ ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
+ ~float32 | ~float64 |
+ ~string
+}
+
+func max[T ordered](a, b T) T { //nolint:predeclared
+ if a > b {
+ return a
+ }
+ return b
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/width.go b/vendor/github.com/charmbracelet/x/ansi/width.go
new file mode 100644
index 00000000..d0487d35
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/width.go
@@ -0,0 +1,113 @@
+package ansi
+
+import (
+ "bytes"
+
+ "github.com/charmbracelet/x/ansi/parser"
+ "github.com/mattn/go-runewidth"
+ "github.com/rivo/uniseg"
+)
+
+// Strip removes ANSI escape codes from a string.
+func Strip(s string) string {
+ var (
+ buf bytes.Buffer // buffer for collecting printable characters
+ ri int // rune index
+ rw int // rune width
+ pstate = parser.GroundState // initial state
+ )
+
+ // This implements a subset of the Parser to only collect runes and
+ // printable characters.
+ for i := 0; i < len(s); i++ {
+ if pstate == parser.Utf8State {
+ // During this state, collect rw bytes to form a valid rune in the
+ // buffer. After getting all the rune bytes into the buffer,
+ // transition to GroundState and reset the counters.
+ buf.WriteByte(s[i])
+ ri++
+ if ri < rw {
+ continue
+ }
+ pstate = parser.GroundState
+ ri = 0
+ rw = 0
+ continue
+ }
+
+ state, action := parser.Table.Transition(pstate, s[i])
+ switch action {
+ case parser.CollectAction:
+ if state == parser.Utf8State {
+ // This action happens when we transition to the Utf8State.
+ rw = utf8ByteLen(s[i])
+ buf.WriteByte(s[i])
+ ri++
+ }
+ case parser.PrintAction, parser.ExecuteAction:
+ // collects printable ASCII and non-printable characters
+ buf.WriteByte(s[i])
+ }
+
+ // Transition to the next state.
+ // The Utf8State is managed separately above.
+ if pstate != parser.Utf8State {
+ pstate = state
+ }
+ }
+
+ return buf.String()
+}
+
+// StringWidth returns the width of a string in cells. This is the number of
+// cells that the string will occupy when printed in a terminal. ANSI escape
+// codes are ignored and wide characters (such as East Asians and emojis) are
+// accounted for.
+// This treats the text as a sequence of grapheme clusters.
+func StringWidth(s string) int {
+ return stringWidth(GraphemeWidth, s)
+}
+
+// StringWidthWc returns the width of a string in cells. This is the number of
+// cells that the string will occupy when printed in a terminal. ANSI escape
+// codes are ignored and wide characters (such as East Asians and emojis) are
+// accounted for.
+// This treats the text as a sequence of wide characters and runes.
+func StringWidthWc(s string) int {
+ return stringWidth(WcWidth, s)
+}
+
+func stringWidth(m Method, s string) int {
+ if s == "" {
+ return 0
+ }
+
+ var (
+ pstate = parser.GroundState // initial state
+ cluster string
+ width int
+ )
+
+ for i := 0; i < len(s); i++ {
+ state, action := parser.Table.Transition(pstate, s[i])
+ if state == parser.Utf8State {
+ var w int
+ cluster, _, w, _ = uniseg.FirstGraphemeClusterInString(s[i:], -1)
+ if m == WcWidth {
+ w = runewidth.StringWidth(cluster)
+ }
+ width += w
+ i += len(cluster) - 1
+ pstate = parser.GroundState
+ continue
+ }
+
+ if action == parser.PrintAction {
+ width++
+ }
+
+ pstate = state
+ }
+
+ return width
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/winop.go b/vendor/github.com/charmbracelet/x/ansi/winop.go
new file mode 100644
index 00000000..0238780d
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/winop.go
@@ -0,0 +1,53 @@
+package ansi
+
+import (
+ "strconv"
+ "strings"
+)
+
+const (
+ // ResizeWindowWinOp is a window operation that resizes the terminal
+ // window.
+ ResizeWindowWinOp = 4
+
+ // RequestWindowSizeWinOp is a window operation that requests a report of
+ // the size of the terminal window in pixels. The response is in the form:
+ // CSI 4 ; height ; width t
+ RequestWindowSizeWinOp = 14
+
+ // RequestCellSizeWinOp is a window operation that requests a report of
+ // the size of the terminal cell size in pixels. The response is in the form:
+ // CSI 6 ; height ; width t
+ RequestCellSizeWinOp = 16
+)
+
+// WindowOp (XTWINOPS) is a sequence that manipulates the terminal window.
+//
+// CSI Ps ; Ps ; Ps t
+//
+// Ps is a semicolon-separated list of parameters.
+// See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps;Ps;Ps-t.1EB0
+func WindowOp(p int, ps ...int) string {
+ if p <= 0 {
+ return ""
+ }
+
+ if len(ps) == 0 {
+ return "\x1b[" + strconv.Itoa(p) + "t"
+ }
+
+ params := make([]string, 0, len(ps)+1)
+ params = append(params, strconv.Itoa(p))
+ for _, p := range ps {
+ if p >= 0 {
+ params = append(params, strconv.Itoa(p))
+ }
+ }
+
+ return "\x1b[" + strings.Join(params, ";") + "t"
+}
+
+// XTWINOPS is an alias for [WindowOp].
+func XTWINOPS(p int, ps ...int) string {
+ return WindowOp(p, ps...)
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/wrap.go b/vendor/github.com/charmbracelet/x/ansi/wrap.go
new file mode 100644
index 00000000..6b995800
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/wrap.go
@@ -0,0 +1,467 @@
+package ansi
+
+import (
+ "bytes"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/charmbracelet/x/ansi/parser"
+ "github.com/mattn/go-runewidth"
+ "github.com/rivo/uniseg"
+)
+
+// nbsp is a non-breaking space
+const nbsp = 0xA0
+
+// Hardwrap wraps a string or a block of text to a given line length, breaking
+// word boundaries. This will preserve ANSI escape codes and will account for
+// wide-characters in the string.
+// When preserveSpace is true, spaces at the beginning of a line will be
+// preserved.
+// This treats the text as a sequence of graphemes.
+func Hardwrap(s string, limit int, preserveSpace bool) string {
+ return hardwrap(GraphemeWidth, s, limit, preserveSpace)
+}
+
+// HardwrapWc wraps a string or a block of text to a given line length, breaking
+// word boundaries. This will preserve ANSI escape codes and will account for
+// wide-characters in the string.
+// When preserveSpace is true, spaces at the beginning of a line will be
+// preserved.
+// This treats the text as a sequence of wide characters and runes.
+func HardwrapWc(s string, limit int, preserveSpace bool) string {
+ return hardwrap(WcWidth, s, limit, preserveSpace)
+}
+
+func hardwrap(m Method, s string, limit int, preserveSpace bool) string {
+ if limit < 1 {
+ return s
+ }
+
+ var (
+ cluster []byte
+ buf bytes.Buffer
+ curWidth int
+ forceNewline bool
+ pstate = parser.GroundState // initial state
+ b = []byte(s)
+ )
+
+ addNewline := func() {
+ buf.WriteByte('\n')
+ curWidth = 0
+ }
+
+ i := 0
+ for i < len(b) {
+ state, action := parser.Table.Transition(pstate, b[i])
+ if state == parser.Utf8State {
+ var width int
+ cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
+ if m == WcWidth {
+ width = runewidth.StringWidth(string(cluster))
+ }
+ i += len(cluster)
+
+ if curWidth+width > limit {
+ addNewline()
+ }
+ if !preserveSpace && curWidth == 0 && len(cluster) <= 4 {
+ // Skip spaces at the beginning of a line
+ if r, _ := utf8.DecodeRune(cluster); r != utf8.RuneError && unicode.IsSpace(r) {
+ pstate = parser.GroundState
+ continue
+ }
+ }
+
+ buf.Write(cluster)
+ curWidth += width
+ pstate = parser.GroundState
+ continue
+ }
+
+ switch action {
+ case parser.PrintAction, parser.ExecuteAction:
+ if b[i] == '\n' {
+ addNewline()
+ forceNewline = false
+ break
+ }
+
+ if curWidth+1 > limit {
+ addNewline()
+ forceNewline = true
+ }
+
+ // Skip spaces at the beginning of a line
+ if curWidth == 0 {
+ if !preserveSpace && forceNewline && unicode.IsSpace(rune(b[i])) {
+ break
+ }
+ forceNewline = false
+ }
+
+ buf.WriteByte(b[i])
+ if action == parser.PrintAction {
+ curWidth++
+ }
+ default:
+ buf.WriteByte(b[i])
+ }
+
+ // We manage the UTF8 state separately manually above.
+ if pstate != parser.Utf8State {
+ pstate = state
+ }
+ i++
+ }
+
+ return buf.String()
+}
+
+// Wordwrap wraps a string or a block of text to a given line length, not
+// breaking word boundaries. This will preserve ANSI escape codes and will
+// account for wide-characters in the string.
+// The breakpoints string is a list of characters that are considered
+// breakpoints for word wrapping. A hyphen (-) is always considered a
+// breakpoint.
+//
+// Note: breakpoints must be a string of 1-cell wide rune characters.
+//
+// This treats the text as a sequence of graphemes.
+func Wordwrap(s string, limit int, breakpoints string) string {
+ return wordwrap(GraphemeWidth, s, limit, breakpoints)
+}
+
+// WordwrapWc wraps a string or a block of text to a given line length, not
+// breaking word boundaries. This will preserve ANSI escape codes and will
+// account for wide-characters in the string.
+// The breakpoints string is a list of characters that are considered
+// breakpoints for word wrapping. A hyphen (-) is always considered a
+// breakpoint.
+//
+// Note: breakpoints must be a string of 1-cell wide rune characters.
+//
+// This treats the text as a sequence of wide characters and runes.
+func WordwrapWc(s string, limit int, breakpoints string) string {
+ return wordwrap(WcWidth, s, limit, breakpoints)
+}
+
+func wordwrap(m Method, s string, limit int, breakpoints string) string {
+ if limit < 1 {
+ return s
+ }
+
+ var (
+ cluster []byte
+ buf bytes.Buffer
+ word bytes.Buffer
+ space bytes.Buffer
+ curWidth int
+ wordLen int
+ pstate = parser.GroundState // initial state
+ b = []byte(s)
+ )
+
+ addSpace := func() {
+ curWidth += space.Len()
+ buf.Write(space.Bytes())
+ space.Reset()
+ }
+
+ addWord := func() {
+ if word.Len() == 0 {
+ return
+ }
+
+ addSpace()
+ curWidth += wordLen
+ buf.Write(word.Bytes())
+ word.Reset()
+ wordLen = 0
+ }
+
+ addNewline := func() {
+ buf.WriteByte('\n')
+ curWidth = 0
+ space.Reset()
+ }
+
+ i := 0
+ for i < len(b) {
+ state, action := parser.Table.Transition(pstate, b[i])
+ if state == parser.Utf8State {
+ var width int
+ cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
+ if m == WcWidth {
+ width = runewidth.StringWidth(string(cluster))
+ }
+ i += len(cluster)
+
+ r, _ := utf8.DecodeRune(cluster)
+ if r != utf8.RuneError && unicode.IsSpace(r) && r != nbsp {
+ addWord()
+ space.WriteRune(r)
+ } else if bytes.ContainsAny(cluster, breakpoints) {
+ addSpace()
+ addWord()
+ buf.Write(cluster)
+ curWidth++
+ } else {
+ word.Write(cluster)
+ wordLen += width
+ if curWidth+space.Len()+wordLen > limit &&
+ wordLen < limit {
+ addNewline()
+ }
+ }
+
+ pstate = parser.GroundState
+ continue
+ }
+
+ switch action {
+ case parser.PrintAction, parser.ExecuteAction:
+ r := rune(b[i])
+ switch {
+ case r == '\n':
+ if wordLen == 0 {
+ if curWidth+space.Len() > limit {
+ curWidth = 0
+ } else {
+ buf.Write(space.Bytes())
+ }
+ space.Reset()
+ }
+
+ addWord()
+ addNewline()
+ case unicode.IsSpace(r):
+ addWord()
+ space.WriteByte(b[i])
+ case r == '-':
+ fallthrough
+ case runeContainsAny(r, breakpoints):
+ addSpace()
+ addWord()
+ buf.WriteByte(b[i])
+ curWidth++
+ default:
+ word.WriteByte(b[i])
+ wordLen++
+ if curWidth+space.Len()+wordLen > limit &&
+ wordLen < limit {
+ addNewline()
+ }
+ }
+
+ default:
+ word.WriteByte(b[i])
+ }
+
+ // We manage the UTF8 state separately manually above.
+ if pstate != parser.Utf8State {
+ pstate = state
+ }
+ i++
+ }
+
+ addWord()
+
+ return buf.String()
+}
+
+// Wrap wraps a string or a block of text to a given line length, breaking word
+// boundaries if necessary. This will preserve ANSI escape codes and will
+// account for wide-characters in the string. The breakpoints string is a list
+// of characters that are considered breakpoints for word wrapping. A hyphen
+// (-) is always considered a breakpoint.
+//
+// Note: breakpoints must be a string of 1-cell wide rune characters.
+//
+// This treats the text as a sequence of graphemes.
+func Wrap(s string, limit int, breakpoints string) string {
+ return wrap(GraphemeWidth, s, limit, breakpoints)
+}
+
+// WrapWc wraps a string or a block of text to a given line length, breaking word
+// boundaries if necessary. This will preserve ANSI escape codes and will
+// account for wide-characters in the string. The breakpoints string is a list
+// of characters that are considered breakpoints for word wrapping. A hyphen
+// (-) is always considered a breakpoint.
+//
+// Note: breakpoints must be a string of 1-cell wide rune characters.
+//
+// This treats the text as a sequence of wide characters and runes.
+func WrapWc(s string, limit int, breakpoints string) string {
+ return wrap(WcWidth, s, limit, breakpoints)
+}
+
+func wrap(m Method, s string, limit int, breakpoints string) string {
+ if limit < 1 {
+ return s
+ }
+
+ var (
+ cluster []byte
+ buf bytes.Buffer
+ word bytes.Buffer
+ space bytes.Buffer
+ curWidth int // written width of the line
+ wordLen int // word buffer len without ANSI escape codes
+ pstate = parser.GroundState // initial state
+ b = []byte(s)
+ )
+
+ addSpace := func() {
+ curWidth += space.Len()
+ buf.Write(space.Bytes())
+ space.Reset()
+ }
+
+ addWord := func() {
+ if word.Len() == 0 {
+ return
+ }
+
+ addSpace()
+ curWidth += wordLen
+ buf.Write(word.Bytes())
+ word.Reset()
+ wordLen = 0
+ }
+
+ addNewline := func() {
+ buf.WriteByte('\n')
+ curWidth = 0
+ space.Reset()
+ }
+
+ i := 0
+ for i < len(b) {
+ state, action := parser.Table.Transition(pstate, b[i])
+ if state == parser.Utf8State {
+ var width int
+ cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
+ if m == WcWidth {
+ width = runewidth.StringWidth(string(cluster))
+ }
+ i += len(cluster)
+
+ r, _ := utf8.DecodeRune(cluster)
+ switch {
+ case r != utf8.RuneError && unicode.IsSpace(r) && r != nbsp: // nbsp is a non-breaking space
+ addWord()
+ space.WriteRune(r)
+ case bytes.ContainsAny(cluster, breakpoints):
+ addSpace()
+ if curWidth+wordLen+width > limit {
+ word.Write(cluster)
+ wordLen += width
+ } else {
+ addWord()
+ buf.Write(cluster)
+ curWidth += width
+ }
+ default:
+ if wordLen+width > limit {
+ // Hardwrap the word if it's too long
+ addWord()
+ }
+
+ word.Write(cluster)
+ wordLen += width
+
+ if curWidth+wordLen+space.Len() > limit {
+ addNewline()
+ }
+ }
+
+ pstate = parser.GroundState
+ continue
+ }
+
+ switch action {
+ case parser.PrintAction, parser.ExecuteAction:
+ switch r := rune(b[i]); {
+ case r == '\n':
+ if wordLen == 0 {
+ if curWidth+space.Len() > limit {
+ curWidth = 0
+ } else {
+ // preserve whitespaces
+ buf.Write(space.Bytes())
+ }
+ space.Reset()
+ }
+
+ addWord()
+ addNewline()
+ case unicode.IsSpace(r):
+ addWord()
+ space.WriteRune(r)
+ case r == '-':
+ fallthrough
+ case runeContainsAny(r, breakpoints):
+ addSpace()
+ if curWidth+wordLen >= limit {
+ // We can't fit the breakpoint in the current line, treat
+ // it as part of the word.
+ word.WriteRune(r)
+ wordLen++
+ } else {
+ addWord()
+ buf.WriteRune(r)
+ curWidth++
+ }
+ default:
+ if curWidth == limit {
+ addNewline()
+ }
+ word.WriteRune(r)
+ wordLen++
+
+ if wordLen == limit {
+ // Hardwrap the word if it's too long
+ addWord()
+ }
+
+ if curWidth+wordLen+space.Len() > limit {
+ addNewline()
+ }
+ }
+
+ default:
+ word.WriteByte(b[i])
+ }
+
+ // We manage the UTF8 state separately manually above.
+ if pstate != parser.Utf8State {
+ pstate = state
+ }
+ i++
+ }
+
+ if wordLen == 0 {
+ if curWidth+space.Len() > limit {
+ curWidth = 0
+ } else {
+ // preserve whitespaces
+ buf.Write(space.Bytes())
+ }
+ space.Reset()
+ }
+
+ addWord()
+
+ return buf.String()
+}
+
+func runeContainsAny(r rune, s string) bool {
+ for _, c := range s {
+ if c == r {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/charmbracelet/x/ansi/xterm.go b/vendor/github.com/charmbracelet/x/ansi/xterm.go
new file mode 100644
index 00000000..83fd4bdc
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/ansi/xterm.go
@@ -0,0 +1,138 @@
+package ansi
+
+import "strconv"
+
+// KeyModifierOptions (XTMODKEYS) sets/resets xterm key modifier options.
+//
+// Default is 0.
+//
+// CSI > Pp m
+// CSI > Pp ; Pv m
+//
+// If Pv is omitted, the resource is reset to its initial value.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+func KeyModifierOptions(p int, vs ...int) string {
+ var pp, pv string
+ if p > 0 {
+ pp = strconv.Itoa(p)
+ }
+
+ if len(vs) == 0 {
+ return "\x1b[>" + strconv.Itoa(p) + "m"
+ }
+
+ v := vs[0]
+ if v > 0 {
+ pv = strconv.Itoa(v)
+ return "\x1b[>" + pp + ";" + pv + "m"
+ }
+
+ return "\x1b[>" + pp + "m"
+}
+
+// XTMODKEYS is an alias for [KeyModifierOptions].
+func XTMODKEYS(p int, vs ...int) string {
+ return KeyModifierOptions(p, vs...)
+}
+
+// SetKeyModifierOptions sets xterm key modifier options.
+// This is an alias for [KeyModifierOptions].
+func SetKeyModifierOptions(pp int, pv int) string {
+ return KeyModifierOptions(pp, pv)
+}
+
+// ResetKeyModifierOptions resets xterm key modifier options.
+// This is an alias for [KeyModifierOptions].
+func ResetKeyModifierOptions(pp int) string {
+ return KeyModifierOptions(pp)
+}
+
+// QueryKeyModifierOptions (XTQMODKEYS) requests xterm key modifier options.
+//
+// Default is 0.
+//
+// CSI ? Pp m
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+func QueryKeyModifierOptions(pp int) string {
+ var p string
+ if pp > 0 {
+ p = strconv.Itoa(pp)
+ }
+ return "\x1b[?" + p + "m"
+}
+
+// XTQMODKEYS is an alias for [QueryKeyModifierOptions].
+func XTQMODKEYS(pp int) string {
+ return QueryKeyModifierOptions(pp)
+}
+
+// Modify Other Keys (modifyOtherKeys) is an xterm feature that allows the
+// terminal to modify the behavior of certain keys to send different escape
+// sequences when pressed.
+//
+// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys
+const (
+ SetModifyOtherKeys1 = "\x1b[>4;1m"
+ SetModifyOtherKeys2 = "\x1b[>4;2m"
+ ResetModifyOtherKeys = "\x1b[>4m"
+ QueryModifyOtherKeys = "\x1b[?4m"
+)
+
+// ModifyOtherKeys returns a sequence that sets XTerm modifyOtherKeys mode.
+// The mode argument specifies the mode to set.
+//
+// 0: Disable modifyOtherKeys mode.
+// 1: Enable modifyOtherKeys mode 1.
+// 2: Enable modifyOtherKeys mode 2.
+//
+// CSI > 4 ; mode m
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys
+//
+// Deprecated: use [SetModifyOtherKeys1] or [SetModifyOtherKeys2] instead.
+func ModifyOtherKeys(mode int) string {
+ return "\x1b[>4;" + strconv.Itoa(mode) + "m"
+}
+
+// DisableModifyOtherKeys disables the modifyOtherKeys mode.
+//
+// CSI > 4 ; 0 m
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys
+//
+// Deprecated: use [ResetModifyOtherKeys] instead.
+const DisableModifyOtherKeys = "\x1b[>4;0m"
+
+// EnableModifyOtherKeys1 enables the modifyOtherKeys mode 1.
+//
+// CSI > 4 ; 1 m
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys
+//
+// Deprecated: use [SetModifyOtherKeys1] instead.
+const EnableModifyOtherKeys1 = "\x1b[>4;1m"
+
+// EnableModifyOtherKeys2 enables the modifyOtherKeys mode 2.
+//
+// CSI > 4 ; 2 m
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys
+//
+// Deprecated: use [SetModifyOtherKeys2] instead.
+const EnableModifyOtherKeys2 = "\x1b[>4;2m"
+
+// RequestModifyOtherKeys requests the modifyOtherKeys mode.
+//
+// CSI ? 4 m
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys
+//
+// Deprecated: use [QueryModifyOtherKeys] instead.
+const RequestModifyOtherKeys = "\x1b[?4m"
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/LICENSE b/vendor/github.com/charmbracelet/x/cellbuf/LICENSE
new file mode 100644
index 00000000..65a5654e
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Charmbracelet, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/buffer.go b/vendor/github.com/charmbracelet/x/cellbuf/buffer.go
new file mode 100644
index 00000000..790d1f7c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/buffer.go
@@ -0,0 +1,473 @@
+package cellbuf
+
+import (
+ "strings"
+
+ "github.com/mattn/go-runewidth"
+ "github.com/rivo/uniseg"
+)
+
+// NewCell returns a new cell. This is a convenience function that initializes a
+// new cell with the given content. The cell's width is determined by the
+// content using [runewidth.RuneWidth].
+// This will only account for the first combined rune in the content. If the
+// content is empty, it will return an empty cell with a width of 0.
+func NewCell(r rune, comb ...rune) (c *Cell) {
+ c = new(Cell)
+ c.Rune = r
+ c.Width = runewidth.RuneWidth(r)
+ for _, r := range comb {
+ if runewidth.RuneWidth(r) > 0 {
+ break
+ }
+ c.Comb = append(c.Comb, r)
+ }
+ c.Comb = comb
+ c.Width = runewidth.StringWidth(string(append([]rune{r}, comb...)))
+ return
+}
+
+// NewCellString returns a new cell with the given string content. This is a
+// convenience function that initializes a new cell with the given content. The
+// cell's width is determined by the content using [runewidth.StringWidth].
+// This will only use the first combined rune in the string. If the string is
+// empty, it will return an empty cell with a width of 0.
+func NewCellString(s string) (c *Cell) {
+ c = new(Cell)
+ for i, r := range s {
+ if i == 0 {
+ c.Rune = r
+ // We only care about the first rune's width
+ c.Width = runewidth.RuneWidth(r)
+ } else {
+ if runewidth.RuneWidth(r) > 0 {
+ break
+ }
+ c.Comb = append(c.Comb, r)
+ }
+ }
+ return
+}
+
+// NewGraphemeCell returns a new cell. This is a convenience function that
+// initializes a new cell with the given content. The cell's width is determined
+// by the content using [uniseg.FirstGraphemeClusterInString].
+// This is used when the content is a grapheme cluster i.e. a sequence of runes
+// that form a single visual unit.
+// This will only return the first grapheme cluster in the string. If the
+// string is empty, it will return an empty cell with a width of 0.
+func NewGraphemeCell(s string) (c *Cell) {
+ g, _, w, _ := uniseg.FirstGraphemeClusterInString(s, -1)
+ return newGraphemeCell(g, w)
+}
+
+func newGraphemeCell(s string, w int) (c *Cell) {
+ c = new(Cell)
+ c.Width = w
+ for i, r := range s {
+ if i == 0 {
+ c.Rune = r
+ } else {
+ c.Comb = append(c.Comb, r)
+ }
+ }
+ return
+}
+
+// Line represents a line in the terminal.
+// A nil cell represents an blank cell, a cell with a space character and a
+// width of 1.
+// If a cell has no content and a width of 0, it is a placeholder for a wide
+// cell.
+type Line []*Cell
+
+// Width returns the width of the line.
+func (l Line) Width() int {
+ return len(l)
+}
+
+// Len returns the length of the line.
+func (l Line) Len() int {
+ return len(l)
+}
+
+// String returns the string representation of the line. Any trailing spaces
+// are removed.
+func (l Line) String() (s string) {
+ for _, c := range l {
+ if c == nil {
+ s += " "
+ } else if c.Empty() {
+ continue
+ } else {
+ s += c.String()
+ }
+ }
+ s = strings.TrimRight(s, " ")
+ return
+}
+
+// At returns the cell at the given x position.
+// If the cell does not exist, it returns nil.
+func (l Line) At(x int) *Cell {
+ if x < 0 || x >= len(l) {
+ return nil
+ }
+
+ c := l[x]
+ if c == nil {
+ newCell := BlankCell
+ return &newCell
+ }
+
+ return c
+}
+
+// Set sets the cell at the given x position. If a wide cell is given, it will
+// set the cell and the following cells to [EmptyCell]. It returns true if the
+// cell was set.
+func (l Line) Set(x int, c *Cell) bool {
+ return l.set(x, c, true)
+}
+
+func (l Line) set(x int, c *Cell, clone bool) bool {
+ width := l.Width()
+ if x < 0 || x >= width {
+ return false
+ }
+
+ // When a wide cell is partially overwritten, we need
+ // to fill the rest of the cell with space cells to
+ // avoid rendering issues.
+ prev := l.At(x)
+ if prev != nil && prev.Width > 1 {
+ // Writing to the first wide cell
+ for j := 0; j < prev.Width && x+j < l.Width(); j++ {
+ l[x+j] = prev.Clone().Blank()
+ }
+ } else if prev != nil && prev.Width == 0 {
+ // Writing to wide cell placeholders
+ for j := 1; j < maxCellWidth && x-j >= 0; j++ {
+ wide := l.At(x - j)
+ if wide != nil && wide.Width > 1 && j < wide.Width {
+ for k := 0; k < wide.Width; k++ {
+ l[x-j+k] = wide.Clone().Blank()
+ }
+ break
+ }
+ }
+ }
+
+ if clone && c != nil {
+ // Clone the cell if not nil.
+ c = c.Clone()
+ }
+
+ if c != nil && x+c.Width > width {
+ // If the cell is too wide, we write blanks with the same style.
+ for i := 0; i < c.Width && x+i < width; i++ {
+ l[x+i] = c.Clone().Blank()
+ }
+ } else {
+ l[x] = c
+
+ // Mark wide cells with an empty cell zero width
+ // We set the wide cell down below
+ if c != nil && c.Width > 1 {
+ for j := 1; j < c.Width && x+j < l.Width(); j++ {
+ var wide Cell
+ l[x+j] = &wide
+ }
+ }
+ }
+
+ return true
+}
+
+// Buffer is a 2D grid of cells representing a screen or terminal.
+type Buffer struct {
+ // Lines holds the lines of the buffer.
+ Lines []Line
+}
+
+// NewBuffer creates a new buffer with the given width and height.
+// This is a convenience function that initializes a new buffer and resizes it.
+func NewBuffer(width int, height int) *Buffer {
+ b := new(Buffer)
+ b.Resize(width, height)
+ return b
+}
+
+// String returns the string representation of the buffer.
+func (b *Buffer) String() (s string) {
+ for i, l := range b.Lines {
+ s += l.String()
+ if i < len(b.Lines)-1 {
+ s += "\r\n"
+ }
+ }
+ return
+}
+
+// Line returns a pointer to the line at the given y position.
+// If the line does not exist, it returns nil.
+func (b *Buffer) Line(y int) Line {
+ if y < 0 || y >= len(b.Lines) {
+ return nil
+ }
+ return b.Lines[y]
+}
+
+// Cell implements Screen.
+func (b *Buffer) Cell(x int, y int) *Cell {
+ if y < 0 || y >= len(b.Lines) {
+ return nil
+ }
+ return b.Lines[y].At(x)
+}
+
+// maxCellWidth is the maximum width a terminal cell can get.
+const maxCellWidth = 4
+
+// SetCell sets the cell at the given x, y position.
+func (b *Buffer) SetCell(x, y int, c *Cell) bool {
+ return b.setCell(x, y, c, true)
+}
+
+// setCell sets the cell at the given x, y position. This will always clone and
+// allocates a new cell if c is not nil.
+func (b *Buffer) setCell(x, y int, c *Cell, clone bool) bool {
+ if y < 0 || y >= len(b.Lines) {
+ return false
+ }
+ return b.Lines[y].set(x, c, clone)
+}
+
+// Height implements Screen.
+func (b *Buffer) Height() int {
+ return len(b.Lines)
+}
+
+// Width implements Screen.
+func (b *Buffer) Width() int {
+ if len(b.Lines) == 0 {
+ return 0
+ }
+ return b.Lines[0].Width()
+}
+
+// Bounds returns the bounds of the buffer.
+func (b *Buffer) Bounds() Rectangle {
+ return Rect(0, 0, b.Width(), b.Height())
+}
+
+// Resize resizes the buffer to the given width and height.
+func (b *Buffer) Resize(width int, height int) {
+ if width == 0 || height == 0 {
+ b.Lines = nil
+ return
+ }
+
+ if width > b.Width() {
+ line := make(Line, width-b.Width())
+ for i := range b.Lines {
+ b.Lines[i] = append(b.Lines[i], line...)
+ }
+ } else if width < b.Width() {
+ for i := range b.Lines {
+ b.Lines[i] = b.Lines[i][:width]
+ }
+ }
+
+ if height > len(b.Lines) {
+ for i := len(b.Lines); i < height; i++ {
+ b.Lines = append(b.Lines, make(Line, width))
+ }
+ } else if height < len(b.Lines) {
+ b.Lines = b.Lines[:height]
+ }
+}
+
+// FillRect fills the buffer with the given cell and rectangle.
+func (b *Buffer) FillRect(c *Cell, rect Rectangle) {
+ cellWidth := 1
+ if c != nil && c.Width > 1 {
+ cellWidth = c.Width
+ }
+ for y := rect.Min.Y; y < rect.Max.Y; y++ {
+ for x := rect.Min.X; x < rect.Max.X; x += cellWidth {
+ b.setCell(x, y, c, false) //nolint:errcheck
+ }
+ }
+}
+
+// Fill fills the buffer with the given cell and rectangle.
+func (b *Buffer) Fill(c *Cell) {
+ b.FillRect(c, b.Bounds())
+}
+
+// Clear clears the buffer with space cells and rectangle.
+func (b *Buffer) Clear() {
+ b.ClearRect(b.Bounds())
+}
+
+// ClearRect clears the buffer with space cells within the specified
+// rectangles. Only cells within the rectangle's bounds are affected.
+func (b *Buffer) ClearRect(rect Rectangle) {
+ b.FillRect(nil, rect)
+}
+
+// InsertLine inserts n lines at the given line position, with the given
+// optional cell, within the specified rectangles. If no rectangles are
+// specified, it inserts lines in the entire buffer. Only cells within the
+// rectangle's horizontal bounds are affected. Lines are pushed out of the
+// rectangle bounds and lost. This follows terminal [ansi.IL] behavior.
+// It returns the pushed out lines.
+func (b *Buffer) InsertLine(y, n int, c *Cell) {
+ b.InsertLineRect(y, n, c, b.Bounds())
+}
+
+// InsertLineRect inserts new lines at the given line position, with the
+// given optional cell, within the rectangle bounds. Only cells within the
+// rectangle's horizontal bounds are affected. Lines are pushed out of the
+// rectangle bounds and lost. This follows terminal [ansi.IL] behavior.
+func (b *Buffer) InsertLineRect(y, n int, c *Cell, rect Rectangle) {
+ if n <= 0 || y < rect.Min.Y || y >= rect.Max.Y || y >= b.Height() {
+ return
+ }
+
+ // Limit number of lines to insert to available space
+ if y+n > rect.Max.Y {
+ n = rect.Max.Y - y
+ }
+
+ // Move existing lines down within the bounds
+ for i := rect.Max.Y - 1; i >= y+n; i-- {
+ for x := rect.Min.X; x < rect.Max.X; x++ {
+ // We don't need to clone c here because we're just moving lines down.
+ b.setCell(x, i, b.Lines[i-n][x], false)
+ }
+ }
+
+ // Clear the newly inserted lines within bounds
+ for i := y; i < y+n; i++ {
+ for x := rect.Min.X; x < rect.Max.X; x++ {
+ b.setCell(x, i, c, true)
+ }
+ }
+}
+
+// DeleteLineRect deletes lines at the given line position, with the given
+// optional cell, within the rectangle bounds. Only cells within the
+// rectangle's bounds are affected. Lines are shifted up within the bounds and
+// new blank lines are created at the bottom. This follows terminal [ansi.DL]
+// behavior.
+func (b *Buffer) DeleteLineRect(y, n int, c *Cell, rect Rectangle) {
+ if n <= 0 || y < rect.Min.Y || y >= rect.Max.Y || y >= b.Height() {
+ return
+ }
+
+ // Limit deletion count to available space in scroll region
+ if n > rect.Max.Y-y {
+ n = rect.Max.Y - y
+ }
+
+ // Shift cells up within the bounds
+ for dst := y; dst < rect.Max.Y-n; dst++ {
+ src := dst + n
+ for x := rect.Min.X; x < rect.Max.X; x++ {
+ // We don't need to clone c here because we're just moving cells up.
+ // b.lines[dst][x] = b.lines[src][x]
+ b.setCell(x, dst, b.Lines[src][x], false)
+ }
+ }
+
+ // Fill the bottom n lines with blank cells
+ for i := rect.Max.Y - n; i < rect.Max.Y; i++ {
+ for x := rect.Min.X; x < rect.Max.X; x++ {
+ b.setCell(x, i, c, true)
+ }
+ }
+}
+
+// DeleteLine deletes n lines at the given line position, with the given
+// optional cell, within the specified rectangles. If no rectangles are
+// specified, it deletes lines in the entire buffer.
+func (b *Buffer) DeleteLine(y, n int, c *Cell) {
+ b.DeleteLineRect(y, n, c, b.Bounds())
+}
+
+// InsertCell inserts new cells at the given position, with the given optional
+// cell, within the specified rectangles. If no rectangles are specified, it
+// inserts cells in the entire buffer. This follows terminal [ansi.ICH]
+// behavior.
+func (b *Buffer) InsertCell(x, y, n int, c *Cell) {
+ b.InsertCellRect(x, y, n, c, b.Bounds())
+}
+
+// InsertCellRect inserts new cells at the given position, with the given
+// optional cell, within the rectangle bounds. Only cells within the
+// rectangle's bounds are affected, following terminal [ansi.ICH] behavior.
+func (b *Buffer) InsertCellRect(x, y, n int, c *Cell, rect Rectangle) {
+ if n <= 0 || y < rect.Min.Y || y >= rect.Max.Y || y >= b.Height() ||
+ x < rect.Min.X || x >= rect.Max.X || x >= b.Width() {
+ return
+ }
+
+ // Limit number of cells to insert to available space
+ if x+n > rect.Max.X {
+ n = rect.Max.X - x
+ }
+
+ // Move existing cells within rectangle bounds to the right
+ for i := rect.Max.X - 1; i >= x+n && i-n >= rect.Min.X; i-- {
+ // We don't need to clone c here because we're just moving cells to the
+ // right.
+ // b.lines[y][i] = b.lines[y][i-n]
+ b.setCell(i, y, b.Lines[y][i-n], false)
+ }
+
+ // Clear the newly inserted cells within rectangle bounds
+ for i := x; i < x+n && i < rect.Max.X; i++ {
+ b.setCell(i, y, c, true)
+ }
+}
+
+// DeleteCell deletes cells at the given position, with the given optional
+// cell, within the specified rectangles. If no rectangles are specified, it
+// deletes cells in the entire buffer. This follows terminal [ansi.DCH]
+// behavior.
+func (b *Buffer) DeleteCell(x, y, n int, c *Cell) {
+ b.DeleteCellRect(x, y, n, c, b.Bounds())
+}
+
+// DeleteCellRect deletes cells at the given position, with the given
+// optional cell, within the rectangle bounds. Only cells within the
+// rectangle's bounds are affected, following terminal [ansi.DCH] behavior.
+func (b *Buffer) DeleteCellRect(x, y, n int, c *Cell, rect Rectangle) {
+ if n <= 0 || y < rect.Min.Y || y >= rect.Max.Y || y >= b.Height() ||
+ x < rect.Min.X || x >= rect.Max.X || x >= b.Width() {
+ return
+ }
+
+ // Calculate how many positions we can actually delete
+ remainingCells := rect.Max.X - x
+ if n > remainingCells {
+ n = remainingCells
+ }
+
+ // Shift the remaining cells to the left
+ for i := x; i < rect.Max.X-n; i++ {
+ if i+n < rect.Max.X {
+ // We don't need to clone c here because we're just moving cells to
+ // the left.
+ // b.lines[y][i] = b.lines[y][i+n]
+ b.setCell(i, y, b.Lines[y][i+n], false)
+ }
+ }
+
+ // Fill the vacated positions with the given cell
+ for i := rect.Max.X - n; i < rect.Max.X; i++ {
+ b.setCell(i, y, c, true)
+ }
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/cell.go b/vendor/github.com/charmbracelet/x/cellbuf/cell.go
new file mode 100644
index 00000000..4d49d45e
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/cell.go
@@ -0,0 +1,508 @@
+package cellbuf
+
+import (
+ "github.com/charmbracelet/x/ansi"
+)
+
+var (
+ // BlankCell is a cell with a single space, width of 1, and no style or link.
+ BlankCell = Cell{Rune: ' ', Width: 1}
+
+ // EmptyCell is just an empty cell used for comparisons and as a placeholder
+ // for wide cells.
+ EmptyCell = Cell{}
+)
+
+// Cell represents a single cell in the terminal screen.
+type Cell struct {
+ // The style of the cell. Nil style means no style. Zero value prints a
+ // reset sequence.
+ Style Style
+
+ // Link is the hyperlink of the cell.
+ Link Link
+
+ // Comb is the combining runes of the cell. This is nil if the cell is a
+ // single rune or if it's a zero width cell that is part of a wider cell.
+ Comb []rune
+
+ // Width is the mono-space width of the grapheme cluster.
+ Width int
+
+ // Rune is the main rune of the cell. This is zero if the cell is part of a
+ // wider cell.
+ Rune rune
+}
+
+// Append appends runes to the cell without changing the width. This is useful
+// when we want to use the cell to store escape sequences or other runes that
+// don't affect the width of the cell.
+func (c *Cell) Append(r ...rune) {
+ for i, r := range r {
+ if i == 0 && c.Rune == 0 {
+ c.Rune = r
+ continue
+ }
+ c.Comb = append(c.Comb, r)
+ }
+}
+
+// String returns the string content of the cell excluding any styles, links,
+// and escape sequences.
+func (c Cell) String() string {
+ if c.Rune == 0 {
+ return ""
+ }
+ if len(c.Comb) == 0 {
+ return string(c.Rune)
+ }
+ return string(append([]rune{c.Rune}, c.Comb...))
+}
+
+// Equal returns whether the cell is equal to the other cell.
+func (c *Cell) Equal(o *Cell) bool {
+ return o != nil &&
+ c.Width == o.Width &&
+ c.Rune == o.Rune &&
+ runesEqual(c.Comb, o.Comb) &&
+ c.Style.Equal(&o.Style) &&
+ c.Link.Equal(&o.Link)
+}
+
+// Empty returns whether the cell is an empty cell. An empty cell is a cell
+// with a width of 0, a rune of 0, and no combining runes.
+func (c Cell) Empty() bool {
+ return c.Width == 0 &&
+ c.Rune == 0 &&
+ len(c.Comb) == 0
+}
+
+// Reset resets the cell to the default state zero value.
+func (c *Cell) Reset() {
+ c.Rune = 0
+ c.Comb = nil
+ c.Width = 0
+ c.Style.Reset()
+ c.Link.Reset()
+}
+
+// Clear returns whether the cell consists of only attributes that don't
+// affect appearance of a space character.
+func (c *Cell) Clear() bool {
+ return c.Rune == ' ' && len(c.Comb) == 0 && c.Width == 1 && c.Style.Clear() && c.Link.Empty()
+}
+
+// Clone returns a copy of the cell.
+func (c *Cell) Clone() (n *Cell) {
+ n = new(Cell)
+ *n = *c
+ return
+}
+
+// Blank makes the cell a blank cell by setting the rune to a space, comb to
+// nil, and the width to 1.
+func (c *Cell) Blank() *Cell {
+ c.Rune = ' '
+ c.Comb = nil
+ c.Width = 1
+ return c
+}
+
+// Link represents a hyperlink in the terminal screen.
+type Link struct {
+ URL string
+ Params string
+}
+
+// String returns a string representation of the hyperlink.
+func (h Link) String() string {
+ return h.URL
+}
+
+// Reset resets the hyperlink to the default state zero value.
+func (h *Link) Reset() {
+ h.URL = ""
+ h.Params = ""
+}
+
+// Equal returns whether the hyperlink is equal to the other hyperlink.
+func (h *Link) Equal(o *Link) bool {
+ return o != nil && h.URL == o.URL && h.Params == o.Params
+}
+
+// Empty returns whether the hyperlink is empty.
+func (h Link) Empty() bool {
+ return h.URL == "" && h.Params == ""
+}
+
+// AttrMask is a bitmask for text attributes that can change the look of text.
+// These attributes can be combined to create different styles.
+type AttrMask uint8
+
+// These are the available text attributes that can be combined to create
+// different styles.
+const (
+ BoldAttr AttrMask = 1 << iota
+ FaintAttr
+ ItalicAttr
+ SlowBlinkAttr
+ RapidBlinkAttr
+ ReverseAttr
+ ConcealAttr
+ StrikethroughAttr
+
+ ResetAttr AttrMask = 0
+)
+
+// Contains returns whether the attribute mask contains the attribute.
+func (a AttrMask) Contains(attr AttrMask) bool {
+ return a&attr == attr
+}
+
+// UnderlineStyle is the style of underline to use for text.
+type UnderlineStyle = ansi.UnderlineStyle
+
+// These are the available underline styles.
+const (
+ NoUnderline = ansi.NoUnderlineStyle
+ SingleUnderline = ansi.SingleUnderlineStyle
+ DoubleUnderline = ansi.DoubleUnderlineStyle
+ CurlyUnderline = ansi.CurlyUnderlineStyle
+ DottedUnderline = ansi.DottedUnderlineStyle
+ DashedUnderline = ansi.DashedUnderlineStyle
+)
+
+// Style represents the Style of a cell.
+type Style struct {
+ Fg ansi.Color
+ Bg ansi.Color
+ Ul ansi.Color
+ Attrs AttrMask
+ UlStyle UnderlineStyle
+}
+
+// Sequence returns the ANSI sequence that sets the style.
+func (s Style) Sequence() string {
+ if s.Empty() {
+ return ansi.ResetStyle
+ }
+
+ var b ansi.Style
+
+ if s.Attrs != 0 {
+ if s.Attrs&BoldAttr != 0 {
+ b = b.Bold()
+ }
+ if s.Attrs&FaintAttr != 0 {
+ b = b.Faint()
+ }
+ if s.Attrs&ItalicAttr != 0 {
+ b = b.Italic()
+ }
+ if s.Attrs&SlowBlinkAttr != 0 {
+ b = b.SlowBlink()
+ }
+ if s.Attrs&RapidBlinkAttr != 0 {
+ b = b.RapidBlink()
+ }
+ if s.Attrs&ReverseAttr != 0 {
+ b = b.Reverse()
+ }
+ if s.Attrs&ConcealAttr != 0 {
+ b = b.Conceal()
+ }
+ if s.Attrs&StrikethroughAttr != 0 {
+ b = b.Strikethrough()
+ }
+ }
+ if s.UlStyle != NoUnderline {
+ switch s.UlStyle {
+ case SingleUnderline:
+ b = b.Underline()
+ case DoubleUnderline:
+ b = b.DoubleUnderline()
+ case CurlyUnderline:
+ b = b.CurlyUnderline()
+ case DottedUnderline:
+ b = b.DottedUnderline()
+ case DashedUnderline:
+ b = b.DashedUnderline()
+ }
+ }
+ if s.Fg != nil {
+ b = b.ForegroundColor(s.Fg)
+ }
+ if s.Bg != nil {
+ b = b.BackgroundColor(s.Bg)
+ }
+ if s.Ul != nil {
+ b = b.UnderlineColor(s.Ul)
+ }
+
+ return b.String()
+}
+
+// DiffSequence returns the ANSI sequence that sets the style as a diff from
+// another style.
+func (s Style) DiffSequence(o Style) string {
+ if o.Empty() {
+ return s.Sequence()
+ }
+
+ var b ansi.Style
+
+ if !colorEqual(s.Fg, o.Fg) {
+ b = b.ForegroundColor(s.Fg)
+ }
+
+ if !colorEqual(s.Bg, o.Bg) {
+ b = b.BackgroundColor(s.Bg)
+ }
+
+ if !colorEqual(s.Ul, o.Ul) {
+ b = b.UnderlineColor(s.Ul)
+ }
+
+ var (
+ noBlink bool
+ isNormal bool
+ )
+
+ if s.Attrs != o.Attrs {
+ if s.Attrs&BoldAttr != o.Attrs&BoldAttr {
+ if s.Attrs&BoldAttr != 0 {
+ b = b.Bold()
+ } else if !isNormal {
+ isNormal = true
+ b = b.NormalIntensity()
+ }
+ }
+ if s.Attrs&FaintAttr != o.Attrs&FaintAttr {
+ if s.Attrs&FaintAttr != 0 {
+ b = b.Faint()
+ } else if !isNormal {
+ b = b.NormalIntensity()
+ }
+ }
+ if s.Attrs&ItalicAttr != o.Attrs&ItalicAttr {
+ if s.Attrs&ItalicAttr != 0 {
+ b = b.Italic()
+ } else {
+ b = b.NoItalic()
+ }
+ }
+ if s.Attrs&SlowBlinkAttr != o.Attrs&SlowBlinkAttr {
+ if s.Attrs&SlowBlinkAttr != 0 {
+ b = b.SlowBlink()
+ } else if !noBlink {
+ noBlink = true
+ b = b.NoBlink()
+ }
+ }
+ if s.Attrs&RapidBlinkAttr != o.Attrs&RapidBlinkAttr {
+ if s.Attrs&RapidBlinkAttr != 0 {
+ b = b.RapidBlink()
+ } else if !noBlink {
+ b = b.NoBlink()
+ }
+ }
+ if s.Attrs&ReverseAttr != o.Attrs&ReverseAttr {
+ if s.Attrs&ReverseAttr != 0 {
+ b = b.Reverse()
+ } else {
+ b = b.NoReverse()
+ }
+ }
+ if s.Attrs&ConcealAttr != o.Attrs&ConcealAttr {
+ if s.Attrs&ConcealAttr != 0 {
+ b = b.Conceal()
+ } else {
+ b = b.NoConceal()
+ }
+ }
+ if s.Attrs&StrikethroughAttr != o.Attrs&StrikethroughAttr {
+ if s.Attrs&StrikethroughAttr != 0 {
+ b = b.Strikethrough()
+ } else {
+ b = b.NoStrikethrough()
+ }
+ }
+ }
+
+ if s.UlStyle != o.UlStyle {
+ b = b.UnderlineStyle(s.UlStyle)
+ }
+
+ return b.String()
+}
+
+// Equal returns true if the style is equal to the other style.
+func (s *Style) Equal(o *Style) bool {
+ return s.Attrs == o.Attrs &&
+ s.UlStyle == o.UlStyle &&
+ colorEqual(s.Fg, o.Fg) &&
+ colorEqual(s.Bg, o.Bg) &&
+ colorEqual(s.Ul, o.Ul)
+}
+
+func colorEqual(c, o ansi.Color) bool {
+ if c == nil && o == nil {
+ return true
+ }
+ if c == nil || o == nil {
+ return false
+ }
+ cr, cg, cb, ca := c.RGBA()
+ or, og, ob, oa := o.RGBA()
+ return cr == or && cg == og && cb == ob && ca == oa
+}
+
+// Bold sets the bold attribute.
+func (s *Style) Bold(v bool) *Style {
+ if v {
+ s.Attrs |= BoldAttr
+ } else {
+ s.Attrs &^= BoldAttr
+ }
+ return s
+}
+
+// Faint sets the faint attribute.
+func (s *Style) Faint(v bool) *Style {
+ if v {
+ s.Attrs |= FaintAttr
+ } else {
+ s.Attrs &^= FaintAttr
+ }
+ return s
+}
+
+// Italic sets the italic attribute.
+func (s *Style) Italic(v bool) *Style {
+ if v {
+ s.Attrs |= ItalicAttr
+ } else {
+ s.Attrs &^= ItalicAttr
+ }
+ return s
+}
+
+// SlowBlink sets the slow blink attribute.
+func (s *Style) SlowBlink(v bool) *Style {
+ if v {
+ s.Attrs |= SlowBlinkAttr
+ } else {
+ s.Attrs &^= SlowBlinkAttr
+ }
+ return s
+}
+
+// RapidBlink sets the rapid blink attribute.
+func (s *Style) RapidBlink(v bool) *Style {
+ if v {
+ s.Attrs |= RapidBlinkAttr
+ } else {
+ s.Attrs &^= RapidBlinkAttr
+ }
+ return s
+}
+
+// Reverse sets the reverse attribute.
+func (s *Style) Reverse(v bool) *Style {
+ if v {
+ s.Attrs |= ReverseAttr
+ } else {
+ s.Attrs &^= ReverseAttr
+ }
+ return s
+}
+
+// Conceal sets the conceal attribute.
+func (s *Style) Conceal(v bool) *Style {
+ if v {
+ s.Attrs |= ConcealAttr
+ } else {
+ s.Attrs &^= ConcealAttr
+ }
+ return s
+}
+
+// Strikethrough sets the strikethrough attribute.
+func (s *Style) Strikethrough(v bool) *Style {
+ if v {
+ s.Attrs |= StrikethroughAttr
+ } else {
+ s.Attrs &^= StrikethroughAttr
+ }
+ return s
+}
+
+// UnderlineStyle sets the underline style.
+func (s *Style) UnderlineStyle(style UnderlineStyle) *Style {
+ s.UlStyle = style
+ return s
+}
+
+// Underline sets the underline attribute.
+// This is a syntactic sugar for [UnderlineStyle].
+func (s *Style) Underline(v bool) *Style {
+ if v {
+ return s.UnderlineStyle(SingleUnderline)
+ }
+ return s.UnderlineStyle(NoUnderline)
+}
+
+// Foreground sets the foreground color.
+func (s *Style) Foreground(c ansi.Color) *Style {
+ s.Fg = c
+ return s
+}
+
+// Background sets the background color.
+func (s *Style) Background(c ansi.Color) *Style {
+ s.Bg = c
+ return s
+}
+
+// UnderlineColor sets the underline color.
+func (s *Style) UnderlineColor(c ansi.Color) *Style {
+ s.Ul = c
+ return s
+}
+
+// Reset resets the style to default.
+func (s *Style) Reset() *Style {
+ s.Fg = nil
+ s.Bg = nil
+ s.Ul = nil
+ s.Attrs = ResetAttr
+ s.UlStyle = NoUnderline
+ return s
+}
+
+// Empty returns true if the style is empty.
+func (s *Style) Empty() bool {
+ return s.Fg == nil && s.Bg == nil && s.Ul == nil && s.Attrs == ResetAttr && s.UlStyle == NoUnderline
+}
+
+// Clear returns whether the style consists of only attributes that don't
+// affect appearance of a space character.
+func (s *Style) Clear() bool {
+ return s.UlStyle == NoUnderline &&
+ s.Attrs&^(BoldAttr|FaintAttr|ItalicAttr|SlowBlinkAttr|RapidBlinkAttr) == 0 &&
+ s.Fg == nil &&
+ s.Bg == nil &&
+ s.Ul == nil
+}
+
+func runesEqual(a, b []rune) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i, r := range a {
+ if r != b[i] {
+ return false
+ }
+ }
+ return true
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/errors.go b/vendor/github.com/charmbracelet/x/cellbuf/errors.go
new file mode 100644
index 00000000..64258fe3
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/errors.go
@@ -0,0 +1,6 @@
+package cellbuf
+
+import "errors"
+
+// ErrOutOfBounds is returned when the given x, y position is out of bounds.
+var ErrOutOfBounds = errors.New("out of bounds")
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/geom.go b/vendor/github.com/charmbracelet/x/cellbuf/geom.go
new file mode 100644
index 00000000..c12e6fb1
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/geom.go
@@ -0,0 +1,21 @@
+package cellbuf
+
+import (
+ "image"
+)
+
+// Position represents an x, y position.
+type Position = image.Point
+
+// Pos is a shorthand for Position{X: x, Y: y}.
+func Pos(x, y int) Position {
+ return image.Pt(x, y)
+}
+
+// Rectange represents a rectangle.
+type Rectangle = image.Rectangle
+
+// Rect is a shorthand for Rectangle.
+func Rect(x, y, w, h int) Rectangle {
+ return image.Rect(x, y, x+w, y+h)
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/hardscroll.go b/vendor/github.com/charmbracelet/x/cellbuf/hardscroll.go
new file mode 100644
index 00000000..402ac06a
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/hardscroll.go
@@ -0,0 +1,272 @@
+package cellbuf
+
+import (
+ "strings"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+// scrollOptimize optimizes the screen to transform the old buffer into the new
+// buffer.
+func (s *Screen) scrollOptimize() {
+ height := s.newbuf.Height()
+ if s.oldnum == nil || len(s.oldnum) < height {
+ s.oldnum = make([]int, height)
+ }
+
+ // Calculate the indices
+ s.updateHashmap()
+ if len(s.hashtab) < height {
+ return
+ }
+
+ // Pass 1 - from top to bottom scrolling up
+ for i := 0; i < height; {
+ for i < height && (s.oldnum[i] == newIndex || s.oldnum[i] <= i) {
+ i++
+ }
+ if i >= height {
+ break
+ }
+
+ shift := s.oldnum[i] - i // shift > 0
+ start := i
+
+ i++
+ for i < height && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift {
+ i++
+ }
+ end := i - 1 + shift
+
+ if !s.scrolln(shift, start, end, height-1) {
+ continue
+ }
+ }
+
+ // Pass 2 - from bottom to top scrolling down
+ for i := height - 1; i >= 0; {
+ for i >= 0 && (s.oldnum[i] == newIndex || s.oldnum[i] >= i) {
+ i--
+ }
+ if i < 0 {
+ break
+ }
+
+ shift := s.oldnum[i] - i // shift < 0
+ end := i
+
+ i--
+ for i >= 0 && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift {
+ i--
+ }
+
+ start := i + 1 - (-shift)
+ if !s.scrolln(shift, start, end, height-1) {
+ continue
+ }
+ }
+}
+
+// scrolln scrolls the screen up by n lines.
+func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
+ const (
+ nonDestScrollRegion = false
+ memoryBelow = false
+ )
+
+ blank := s.clearBlank()
+ if n > 0 {
+ // Scroll up (forward)
+ v = s.scrollUp(n, top, bot, 0, maxY, blank)
+ if !v {
+ s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1))
+
+ // XXX: How should we handle this in inline mode when not using alternate screen?
+ s.cur.X, s.cur.Y = -1, -1
+ v = s.scrollUp(n, top, bot, top, bot, blank)
+ s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1))
+ s.cur.X, s.cur.Y = -1, -1
+ }
+
+ if !v {
+ v = s.scrollIdl(n, top, bot-n+1, blank)
+ }
+
+ // Clear newly shifted-in lines.
+ if v &&
+ (nonDestScrollRegion || (memoryBelow && bot == maxY)) {
+ if bot == maxY {
+ s.move(0, bot-n+1)
+ s.clearToBottom(nil)
+ } else {
+ for i := 0; i < n; i++ {
+ s.move(0, bot-i)
+ s.clearToEnd(nil, false)
+ }
+ }
+ }
+ } else if n < 0 {
+ // Scroll down (backward)
+ v = s.scrollDown(-n, top, bot, 0, maxY, blank)
+ if !v {
+ s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1))
+
+ // XXX: How should we handle this in inline mode when not using alternate screen?
+ s.cur.X, s.cur.Y = -1, -1
+ v = s.scrollDown(-n, top, bot, top, bot, blank)
+ s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1))
+ s.cur.X, s.cur.Y = -1, -1
+
+ if !v {
+ v = s.scrollIdl(-n, bot+n+1, top, blank)
+ }
+
+ // Clear newly shifted-in lines.
+ if v &&
+ (nonDestScrollRegion || (memoryBelow && top == 0)) {
+ for i := 0; i < -n; i++ {
+ s.move(0, top+i)
+ s.clearToEnd(nil, false)
+ }
+ }
+ }
+ }
+
+ if !v {
+ return
+ }
+
+ s.scrollBuffer(s.curbuf, n, top, bot, blank)
+
+ // shift hash values too, they can be reused
+ s.scrollOldhash(n, top, bot)
+
+ return true
+}
+
+// scrollBuffer scrolls the buffer by n lines.
+func (s *Screen) scrollBuffer(b *Buffer, n, top, bot int, blank *Cell) {
+ if top < 0 || bot < top || bot >= b.Height() {
+ // Nothing to scroll
+ return
+ }
+
+ if n < 0 {
+ // shift n lines downwards
+ limit := top - n
+ for line := bot; line >= limit && line >= 0 && line >= top; line-- {
+ copy(b.Lines[line], b.Lines[line+n])
+ }
+ for line := top; line < limit && line <= b.Height()-1 && line <= bot; line++ {
+ b.FillRect(blank, Rect(0, line, b.Width(), 1))
+ }
+ }
+
+ if n > 0 {
+ // shift n lines upwards
+ limit := bot - n
+ for line := top; line <= limit && line <= b.Height()-1 && line <= bot; line++ {
+ copy(b.Lines[line], b.Lines[line+n])
+ }
+ for line := bot; line > limit && line >= 0 && line >= top; line-- {
+ b.FillRect(blank, Rect(0, line, b.Width(), 1))
+ }
+ }
+
+ s.touchLine(b.Width(), b.Height(), top, bot-top+1, true)
+}
+
+// touchLine marks the line as touched.
+func (s *Screen) touchLine(width, height, y, n int, changed bool) {
+ if n < 0 || y < 0 || y >= height {
+ return // Nothing to touch
+ }
+
+ for i := y; i < y+n && i < height; i++ {
+ if changed {
+ s.touch[i] = lineData{firstCell: 0, lastCell: width - 1}
+ } else {
+ delete(s.touch, i)
+ }
+ }
+}
+
+// scrollUp scrolls the screen up by n lines.
+func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
+ if n == 1 && top == minY && bot == maxY {
+ s.move(0, bot)
+ s.updatePen(blank)
+ s.buf.WriteByte('\n')
+ } else if n == 1 && bot == maxY {
+ s.move(0, top)
+ s.updatePen(blank)
+ s.buf.WriteString(ansi.DeleteLine(1))
+ } else if top == minY && bot == maxY {
+ if s.xtermLike {
+ s.move(0, bot)
+ } else {
+ s.move(0, top)
+ }
+ s.updatePen(blank)
+ if s.xtermLike {
+ s.buf.WriteString(ansi.ScrollUp(n))
+ } else {
+ s.buf.WriteString(strings.Repeat("\n", n))
+ }
+ } else if bot == maxY {
+ s.move(0, top)
+ s.updatePen(blank)
+ s.buf.WriteString(ansi.DeleteLine(n))
+ } else {
+ return false
+ }
+ return true
+}
+
+// scrollDown scrolls the screen down by n lines.
+func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
+ if n == 1 && top == minY && bot == maxY {
+ s.move(0, top)
+ s.updatePen(blank)
+ s.buf.WriteString(ansi.ReverseIndex)
+ } else if n == 1 && bot == maxY {
+ s.move(0, top)
+ s.updatePen(blank)
+ s.buf.WriteString(ansi.InsertLine(1))
+ } else if top == minY && bot == maxY {
+ s.move(0, top)
+ s.updatePen(blank)
+ if s.xtermLike {
+ s.buf.WriteString(ansi.ScrollDown(n))
+ } else {
+ s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n))
+ }
+ } else if bot == maxY {
+ s.move(0, top)
+ s.updatePen(blank)
+ s.buf.WriteString(ansi.InsertLine(n))
+ } else {
+ return false
+ }
+ return true
+}
+
+// scrollIdl scrolls the screen n lines by using [ansi.DL] at del and using
+// [ansi.IL] at ins.
+func (s *Screen) scrollIdl(n, del, ins int, blank *Cell) bool {
+ if n < 0 {
+ return false
+ }
+
+ // Delete lines
+ s.move(0, del)
+ s.updatePen(blank)
+ s.buf.WriteString(ansi.DeleteLine(n))
+
+ // Insert lines
+ s.move(0, ins)
+ s.updatePen(blank)
+ s.buf.WriteString(ansi.InsertLine(n))
+
+ return true
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/hashmap.go b/vendor/github.com/charmbracelet/x/cellbuf/hashmap.go
new file mode 100644
index 00000000..0d25b549
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/hashmap.go
@@ -0,0 +1,301 @@
+package cellbuf
+
+import (
+ "github.com/charmbracelet/x/ansi"
+)
+
+// hash returns the hash value of a [Line].
+func hash(l Line) (h uint64) {
+ for _, c := range l {
+ var r rune
+ if c == nil {
+ r = ansi.SP
+ } else {
+ r = c.Rune
+ }
+ h += (h << 5) + uint64(r)
+ }
+ return
+}
+
+// hashmap represents a single [Line] hash.
+type hashmap struct {
+ value uint64
+ oldcount, newcount int
+ oldindex, newindex int
+}
+
+// The value used to indicate lines created by insertions and scrolls.
+const newIndex = -1
+
+// updateHashmap updates the hashmap with the new hash value.
+func (s *Screen) updateHashmap() {
+ height := s.newbuf.Height()
+ if len(s.oldhash) >= height && len(s.newhash) >= height {
+ // rehash changed lines
+ for i := 0; i < height; i++ {
+ _, ok := s.touch[i]
+ if ok {
+ s.oldhash[i] = hash(s.curbuf.Line(i))
+ s.newhash[i] = hash(s.newbuf.Line(i))
+ }
+ }
+ } else {
+ // rehash all
+ if len(s.oldhash) != height {
+ s.oldhash = make([]uint64, height)
+ }
+ if len(s.newhash) != height {
+ s.newhash = make([]uint64, height)
+ }
+ for i := 0; i < height; i++ {
+ s.oldhash[i] = hash(s.curbuf.Line(i))
+ s.newhash[i] = hash(s.newbuf.Line(i))
+ }
+ }
+
+ s.hashtab = make([]hashmap, height*2)
+ for i := 0; i < height; i++ {
+ hashval := s.oldhash[i]
+
+ // Find matching hash or empty slot
+ idx := 0
+ for idx < len(s.hashtab) && s.hashtab[idx].value != 0 {
+ if s.hashtab[idx].value == hashval {
+ break
+ }
+ idx++
+ }
+
+ s.hashtab[idx].value = hashval // in case this is a new hash
+ s.hashtab[idx].oldcount++
+ s.hashtab[idx].oldindex = i
+ }
+ for i := 0; i < height; i++ {
+ hashval := s.newhash[i]
+
+ // Find matching hash or empty slot
+ idx := 0
+ for idx < len(s.hashtab) && s.hashtab[idx].value != 0 {
+ if s.hashtab[idx].value == hashval {
+ break
+ }
+ idx++
+ }
+
+ s.hashtab[idx].value = hashval // in case this is a new hash
+ s.hashtab[idx].newcount++
+ s.hashtab[idx].newindex = i
+
+ s.oldnum[i] = newIndex // init old indices slice
+ }
+
+ // Mark line pair corresponding to unique hash pairs.
+ for i := 0; i < len(s.hashtab) && s.hashtab[i].value != 0; i++ {
+ hsp := &s.hashtab[i]
+ if hsp.oldcount == 1 && hsp.newcount == 1 && hsp.oldindex != hsp.newindex {
+ s.oldnum[hsp.newindex] = hsp.oldindex
+ }
+ }
+
+ s.growHunks()
+
+ // Eliminate bad or impossible shifts. This includes removing those hunks
+ // which could not grow because of conflicts, as well those which are to be
+ // moved too far, they are likely to destroy more than carry.
+ for i := 0; i < height; {
+ var start, shift, size int
+ for i < height && s.oldnum[i] == newIndex {
+ i++
+ }
+ if i >= height {
+ break
+ }
+ start = i
+ shift = s.oldnum[i] - i
+ i++
+ for i < height && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift {
+ i++
+ }
+ size = i - start
+ if size < 3 || size+min(size/8, 2) < abs(shift) {
+ for start < i {
+ s.oldnum[start] = newIndex
+ start++
+ }
+ }
+ }
+
+ // After clearing invalid hunks, try grow the rest.
+ s.growHunks()
+}
+
+// scrollOldhash
+func (s *Screen) scrollOldhash(n, top, bot int) {
+ if len(s.oldhash) == 0 {
+ return
+ }
+
+ size := bot - top + 1 - abs(n)
+ if n > 0 {
+ // Move existing hashes up
+ copy(s.oldhash[top:], s.oldhash[top+n:top+n+size])
+ // Recalculate hashes for newly shifted-in lines
+ for i := bot; i > bot-n; i-- {
+ s.oldhash[i] = hash(s.curbuf.Line(i))
+ }
+ } else {
+ // Move existing hashes down
+ copy(s.oldhash[top-n:], s.oldhash[top:top+size])
+ // Recalculate hashes for newly shifted-in lines
+ for i := top; i < top-n; i++ {
+ s.oldhash[i] = hash(s.curbuf.Line(i))
+ }
+ }
+}
+
+func (s *Screen) growHunks() {
+ var (
+ backLimit int // limits for cells to fill
+ backRefLimit int // limit for references
+ i int
+ nextHunk int
+ )
+
+ height := s.newbuf.Height()
+ for i < height && s.oldnum[i] == newIndex {
+ i++
+ }
+ for ; i < height; i = nextHunk {
+ var (
+ forwardLimit int
+ forwardRefLimit int
+ end int
+ start = i
+ shift = s.oldnum[i] - i
+ )
+
+ // get forward limit
+ i = start + 1
+ for i < height &&
+ s.oldnum[i] != newIndex &&
+ s.oldnum[i]-i == shift {
+ i++
+ }
+
+ end = i
+ for i < height && s.oldnum[i] == newIndex {
+ i++
+ }
+
+ nextHunk = i
+ forwardLimit = i
+ if i >= height || s.oldnum[i] >= i {
+ forwardRefLimit = i
+ } else {
+ forwardRefLimit = s.oldnum[i]
+ }
+
+ i = start - 1
+
+ // grow back
+ if shift < 0 {
+ backLimit = backRefLimit + (-shift)
+ }
+ for i >= backLimit {
+ if s.newhash[i] == s.oldhash[i+shift] ||
+ s.costEffective(i+shift, i, shift < 0) {
+ s.oldnum[i] = i + shift
+ } else {
+ break
+ }
+ i--
+ }
+
+ i = end
+ // grow forward
+ if shift > 0 {
+ forwardLimit = forwardRefLimit - shift
+ }
+ for i < forwardLimit {
+ if s.newhash[i] == s.oldhash[i+shift] ||
+ s.costEffective(i+shift, i, shift > 0) {
+ s.oldnum[i] = i + shift
+ } else {
+ break
+ }
+ i++
+ }
+
+ backLimit = i
+ backRefLimit = backLimit
+ if shift > 0 {
+ backRefLimit += shift
+ }
+ }
+}
+
+// costEffective returns true if the cost of moving line 'from' to line 'to' seems to be
+// cost effective. 'blank' indicates whether the line 'to' would become blank.
+func (s *Screen) costEffective(from, to int, blank bool) bool {
+ if from == to {
+ return false
+ }
+
+ newFrom := s.oldnum[from]
+ if newFrom == newIndex {
+ newFrom = from
+ }
+
+ // On the left side of >= is the cost before moving. On the right side --
+ // cost after moving.
+
+ // Calculate costs before moving.
+ var costBeforeMove int
+ if blank {
+ // Cost of updating blank line at destination.
+ costBeforeMove = s.updateCostBlank(s.newbuf.Line(to))
+ } else {
+ // Cost of updating exiting line at destination.
+ costBeforeMove = s.updateCost(s.curbuf.Line(to), s.newbuf.Line(to))
+ }
+
+ // Add cost of updating source line
+ costBeforeMove += s.updateCost(s.curbuf.Line(newFrom), s.newbuf.Line(from))
+
+ // Calculate costs after moving.
+ var costAfterMove int
+ if newFrom == from {
+ // Source becomes blank after move
+ costAfterMove = s.updateCostBlank(s.newbuf.Line(from))
+ } else {
+ // Source gets updated from another line
+ costAfterMove = s.updateCost(s.curbuf.Line(newFrom), s.newbuf.Line(from))
+ }
+
+ // Add cost of moving source line to destination
+ costAfterMove += s.updateCost(s.curbuf.Line(from), s.newbuf.Line(to))
+
+ // Return true if moving is cost effective (costs less or equal)
+ return costBeforeMove >= costAfterMove
+}
+
+func (s *Screen) updateCost(from, to Line) (cost int) {
+ var fidx, tidx int
+ for i := s.newbuf.Width() - 1; i > 0; i, fidx, tidx = i-1, fidx+1, tidx+1 {
+ if !cellEqual(from.At(fidx), to.At(tidx)) {
+ cost++
+ }
+ }
+ return
+}
+
+func (s *Screen) updateCostBlank(to Line) (cost int) {
+ var tidx int
+ for i := s.newbuf.Width() - 1; i > 0; i, tidx = i-1, tidx+1 {
+ if !cellEqual(nil, to.At(tidx)) {
+ cost++
+ }
+ }
+ return
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/link.go b/vendor/github.com/charmbracelet/x/cellbuf/link.go
new file mode 100644
index 00000000..112f8e8a
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/link.go
@@ -0,0 +1,14 @@
+package cellbuf
+
+import (
+ "github.com/charmbracelet/colorprofile"
+)
+
+// Convert converts a hyperlink to respect the given color profile.
+func ConvertLink(h Link, p colorprofile.Profile) Link {
+ if p == colorprofile.NoTTY {
+ return Link{}
+ }
+
+ return h
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/screen.go b/vendor/github.com/charmbracelet/x/cellbuf/screen.go
new file mode 100644
index 00000000..963b9cac
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/screen.go
@@ -0,0 +1,1457 @@
+package cellbuf
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "os"
+ "strings"
+ "sync"
+
+ "github.com/charmbracelet/colorprofile"
+ "github.com/charmbracelet/x/ansi"
+ "github.com/charmbracelet/x/term"
+)
+
+// ErrInvalidDimensions is returned when the dimensions of a window are invalid
+// for the operation.
+var ErrInvalidDimensions = errors.New("invalid dimensions")
+
+// notLocal returns whether the coordinates are not considered local movement
+// using the defined thresholds.
+// This takes the number of columns, and the coordinates of the current and
+// target positions.
+func notLocal(cols, fx, fy, tx, ty int) bool {
+ // The typical distance for a [ansi.CUP] sequence. Anything less than this
+ // is considered local movement.
+ const longDist = 8 - 1
+ return (tx > longDist) &&
+ (tx < cols-1-longDist) &&
+ (abs(ty-fy)+abs(tx-fx) > longDist)
+}
+
+// relativeCursorMove returns the relative cursor movement sequence using one or two
+// of the following sequences [ansi.CUU], [ansi.CUD], [ansi.CUF], [ansi.CUB],
+// [ansi.VPA], [ansi.HPA].
+// When overwrite is true, this will try to optimize the sequence by using the
+// screen cells values to move the cursor instead of using escape sequences.
+func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBackspace bool) string {
+ var seq strings.Builder
+
+ width, height := s.newbuf.Width(), s.newbuf.Height()
+ if ty != fy {
+ var yseq string
+ if s.xtermLike && !s.opts.RelativeCursor {
+ yseq = ansi.VerticalPositionAbsolute(ty + 1)
+ }
+
+ // OPTIM: Use [ansi.LF] and [ansi.ReverseIndex] as optimizations.
+
+ if ty > fy {
+ n := ty - fy
+ if cud := ansi.CursorDown(n); yseq == "" || len(cud) < len(yseq) {
+ yseq = cud
+ }
+ shouldScroll := !s.opts.AltScreen && fy+n >= s.scrollHeight
+ if lf := strings.Repeat("\n", n); shouldScroll || (fy+n < height && len(lf) < len(yseq)) {
+ // TODO: Ensure we're not unintentionally scrolling the screen down.
+ yseq = lf
+ s.scrollHeight = max(s.scrollHeight, fy+n)
+ }
+ } else if ty < fy {
+ n := fy - ty
+ if cuu := ansi.CursorUp(n); yseq == "" || len(cuu) < len(yseq) {
+ yseq = cuu
+ }
+ if n == 1 && fy-1 > 0 {
+ // TODO: Ensure we're not unintentionally scrolling the screen up.
+ yseq = ansi.ReverseIndex
+ }
+ }
+
+ seq.WriteString(yseq)
+ }
+
+ if tx != fx {
+ var xseq string
+ if s.xtermLike && !s.opts.RelativeCursor {
+ xseq = ansi.HorizontalPositionAbsolute(tx + 1)
+ }
+
+ if tx > fx {
+ n := tx - fx
+ if useTabs {
+ var tabs int
+ var col int
+ for col = fx; s.tabs.Next(col) <= tx; col = s.tabs.Next(col) {
+ tabs++
+ if col == s.tabs.Next(col) || col >= width-1 {
+ break
+ }
+ }
+
+ if tabs > 0 {
+ cht := ansi.CursorHorizontalForwardTab(tabs)
+ tab := strings.Repeat("\t", tabs)
+ if false && s.xtermLike && len(cht) < len(tab) {
+ // TODO: The linux console and some terminals such as
+ // Alacritty don't support [ansi.CHT]. Enable this when
+ // we have a way to detect this, or after 5 years when
+ // we're sure everyone has updated their terminals :P
+ seq.WriteString(cht)
+ } else {
+ seq.WriteString(tab)
+ }
+
+ n = tx - col
+ fx = col
+ }
+ }
+
+ if cuf := ansi.CursorForward(n); xseq == "" || len(cuf) < len(xseq) {
+ xseq = cuf
+ }
+
+ // If we have no attribute and style changes, overwrite is cheaper.
+ var ovw string
+ if overwrite && ty >= 0 {
+ for i := 0; i < n; i++ {
+ cell := s.newbuf.Cell(fx+i, ty)
+ if cell != nil && cell.Width > 0 {
+ i += cell.Width - 1
+ if !cell.Style.Equal(&s.cur.Style) || !cell.Link.Equal(&s.cur.Link) {
+ overwrite = false
+ break
+ }
+ }
+ }
+ }
+
+ if overwrite && ty >= 0 {
+ for i := 0; i < n; i++ {
+ cell := s.newbuf.Cell(fx+i, ty)
+ if cell != nil && cell.Width > 0 {
+ ovw += cell.String()
+ i += cell.Width - 1
+ } else {
+ ovw += " "
+ }
+ }
+ }
+
+ if overwrite && len(ovw) < len(xseq) {
+ xseq = ovw
+ }
+ } else if tx < fx {
+ n := fx - tx
+ if useTabs && s.xtermLike {
+ // VT100 does not support backward tabs [ansi.CBT].
+
+ col := fx
+
+ var cbt int // cursor backward tabs count
+ for s.tabs.Prev(col) >= tx {
+ col = s.tabs.Prev(col)
+ cbt++
+ if col == s.tabs.Prev(col) || col <= 0 {
+ break
+ }
+ }
+
+ if cbt > 0 {
+ seq.WriteString(ansi.CursorBackwardTab(cbt))
+ n = col - tx
+ }
+ }
+
+ if cub := ansi.CursorBackward(n); xseq == "" || len(cub) < len(xseq) {
+ xseq = cub
+ }
+
+ if useBackspace && n < len(xseq) {
+ xseq = strings.Repeat("\b", n)
+ }
+ }
+
+ seq.WriteString(xseq)
+ }
+
+ return seq.String()
+}
+
+// moveCursor moves and returns the cursor movement sequence to move the cursor
+// to the specified position.
+// When overwrite is true, this will try to optimize the sequence by using the
+// screen cells values to move the cursor instead of using escape sequences.
+func moveCursor(s *Screen, x, y int, overwrite bool) (seq string) {
+ fx, fy := s.cur.X, s.cur.Y
+
+ if !s.opts.RelativeCursor {
+ // Method #0: Use [ansi.CUP] if the distance is long.
+ seq = ansi.CursorPosition(x+1, y+1)
+ if fx == -1 || fy == -1 || notLocal(s.newbuf.Width(), fx, fy, x, y) {
+ return
+ }
+ }
+
+ // Optimize based on options.
+ trials := 0
+ if s.opts.HardTabs {
+ trials |= 2 // 0b10 in binary
+ }
+ if s.opts.Backspace {
+ trials |= 1 // 0b01 in binary
+ }
+
+ // Try all possible combinations of hard tabs and backspace optimizations.
+ for i := 0; i <= trials; i++ {
+ // Skip combinations that are not enabled.
+ if i & ^trials != 0 {
+ continue
+ }
+
+ useHardTabs := i&2 != 0
+ useBackspace := i&1 != 0
+
+ // Method #1: Use local movement sequences.
+ nseq := relativeCursorMove(s, fx, fy, x, y, overwrite, useHardTabs, useBackspace)
+ if (i == 0 && len(seq) == 0) || len(nseq) < len(seq) {
+ seq = nseq
+ }
+
+ // Method #2: Use [ansi.CR] and local movement sequences.
+ nseq = "\r" + relativeCursorMove(s, 0, fy, x, y, overwrite, useHardTabs, useBackspace)
+ if len(nseq) < len(seq) {
+ seq = nseq
+ }
+
+ if !s.opts.RelativeCursor {
+ // Method #3: Use [ansi.CursorHomePosition] and local movement sequences.
+ nseq = ansi.CursorHomePosition + relativeCursorMove(s, 0, 0, x, y, overwrite, useHardTabs, useBackspace)
+ if len(nseq) < len(seq) {
+ seq = nseq
+ }
+ }
+ }
+
+ return
+}
+
+// moveCursor moves the cursor to the specified position.
+func (s *Screen) moveCursor(x, y int, overwrite bool) {
+ if !s.opts.AltScreen && s.cur.X == -1 && s.cur.Y == -1 {
+ // First cursor movement in inline mode, move the cursor to the first
+ // column before moving to the target position.
+ s.buf.WriteByte('\r') //nolint:errcheck
+ s.cur.X, s.cur.Y = 0, 0
+ }
+ s.buf.WriteString(moveCursor(s, x, y, overwrite)) //nolint:errcheck
+ s.cur.X, s.cur.Y = x, y
+}
+
+func (s *Screen) move(x, y int) {
+ // XXX: Make sure we use the max height and width of the buffer in case
+ // we're in the middle of a resize operation.
+ width := max(s.newbuf.Width(), s.curbuf.Width())
+ height := max(s.newbuf.Height(), s.curbuf.Height())
+
+ if width > 0 && x >= width {
+ // Handle autowrap
+ y += (x / width)
+ x %= width
+ }
+
+ // XXX: Disable styles if there's any
+ // Some move operations such as [ansi.LF] can apply styles to the new
+ // cursor position, thus, we need to reset the styles before moving the
+ // cursor.
+ blank := s.clearBlank()
+ resetPen := y != s.cur.Y && !blank.Equal(&BlankCell)
+ if resetPen {
+ s.updatePen(nil)
+ }
+
+ // Reset wrap around (phantom cursor) state
+ if s.atPhantom {
+ s.cur.X = 0
+ s.buf.WriteByte('\r') //nolint:errcheck
+ s.atPhantom = false // reset phantom cell state
+ }
+
+ // TODO: Investigate if we need to handle this case and/or if we need the
+ // following code.
+ //
+ // if width > 0 && s.cur.X >= width {
+ // l := (s.cur.X + 1) / width
+ //
+ // s.cur.Y += l
+ // if height > 0 && s.cur.Y >= height {
+ // l -= s.cur.Y - height - 1
+ // }
+ //
+ // if l > 0 {
+ // s.cur.X = 0
+ // s.buf.WriteString("\r" + strings.Repeat("\n", l)) //nolint:errcheck
+ // }
+ // }
+
+ if height > 0 {
+ if s.cur.Y > height-1 {
+ s.cur.Y = height - 1
+ }
+ if y > height-1 {
+ y = height - 1
+ }
+ }
+
+ if x == s.cur.X && y == s.cur.Y {
+ // We give up later because we need to run checks for the phantom cell
+ // and others before we can determine if we can give up.
+ return
+ }
+
+ // We set the new cursor in [Screen.moveCursor].
+ s.moveCursor(x, y, true) // Overwrite cells if possible
+}
+
+// Cursor represents a terminal Cursor.
+type Cursor struct {
+ Style
+ Link
+ Position
+}
+
+// ScreenOptions are options for the screen.
+type ScreenOptions struct {
+ // Term is the terminal type to use when writing to the screen. When empty,
+ // `$TERM` is used from [os.Getenv].
+ Term string
+ // Profile is the color profile to use when writing to the screen.
+ Profile colorprofile.Profile
+ // RelativeCursor is whether to use relative cursor movements. This is
+ // useful when alt-screen is not used or when using inline mode.
+ RelativeCursor bool
+ // AltScreen is whether to use the alternate screen buffer.
+ AltScreen bool
+ // ShowCursor is whether to show the cursor.
+ ShowCursor bool
+ // HardTabs is whether to use hard tabs to optimize cursor movements.
+ HardTabs bool
+ // Backspace is whether to use backspace characters to move the cursor.
+ Backspace bool
+}
+
+// lineData represents the metadata for a line.
+type lineData struct {
+ // first and last changed cell indices
+ firstCell, lastCell int
+ // old index used for scrolling
+ oldIndex int //nolint:unused
+}
+
+// Screen represents the terminal screen.
+type Screen struct {
+ w io.Writer
+ buf *bytes.Buffer // buffer for writing to the screen
+ curbuf *Buffer // the current buffer
+ newbuf *Buffer // the new buffer
+ tabs *TabStops
+ touch map[int]lineData
+ queueAbove []string // the queue of strings to write above the screen
+ oldhash, newhash []uint64 // the old and new hash values for each line
+ hashtab []hashmap // the hashmap table
+ oldnum []int // old indices from previous hash
+ cur, saved Cursor // the current and saved cursors
+ opts ScreenOptions
+ mu sync.Mutex
+ method ansi.Method
+ scrollHeight int // keeps track of how many lines we've scrolled down (inline mode)
+ altScreenMode bool // whether alternate screen mode is enabled
+ cursorHidden bool // whether text cursor mode is enabled
+ clear bool // whether to force clear the screen
+ xtermLike bool // whether to use xterm-like optimizations, otherwise, it uses vt100 only
+ queuedText bool // whether we have queued non-zero width text queued up
+ atPhantom bool // whether the cursor is out of bounds and at a phantom cell
+}
+
+// SetMethod sets the method used to calculate the width of cells.
+func (s *Screen) SetMethod(method ansi.Method) {
+ s.method = method
+}
+
+// UseBackspaces sets whether to use backspace characters to move the cursor.
+func (s *Screen) UseBackspaces(v bool) {
+ s.opts.Backspace = v
+}
+
+// UseHardTabs sets whether to use hard tabs to optimize cursor movements.
+func (s *Screen) UseHardTabs(v bool) {
+ s.opts.HardTabs = v
+}
+
+// SetColorProfile sets the color profile to use when writing to the screen.
+func (s *Screen) SetColorProfile(p colorprofile.Profile) {
+ s.opts.Profile = p
+}
+
+// SetRelativeCursor sets whether to use relative cursor movements.
+func (s *Screen) SetRelativeCursor(v bool) {
+ s.opts.RelativeCursor = v
+}
+
+// EnterAltScreen enters the alternate screen buffer.
+func (s *Screen) EnterAltScreen() {
+ s.opts.AltScreen = true
+ s.clear = true
+ s.saved = s.cur
+}
+
+// ExitAltScreen exits the alternate screen buffer.
+func (s *Screen) ExitAltScreen() {
+ s.opts.AltScreen = false
+ s.clear = true
+ s.cur = s.saved
+}
+
+// ShowCursor shows the cursor.
+func (s *Screen) ShowCursor() {
+ s.opts.ShowCursor = true
+}
+
+// HideCursor hides the cursor.
+func (s *Screen) HideCursor() {
+ s.opts.ShowCursor = false
+}
+
+// Bounds implements Window.
+func (s *Screen) Bounds() Rectangle {
+ // Always return the new buffer bounds.
+ return s.newbuf.Bounds()
+}
+
+// Cell implements Window.
+func (s *Screen) Cell(x int, y int) *Cell {
+ return s.newbuf.Cell(x, y)
+}
+
+// Redraw forces a full redraw of the screen.
+func (s *Screen) Redraw() {
+ s.mu.Lock()
+ s.clear = true
+ s.mu.Unlock()
+}
+
+// Clear clears the screen with blank cells. This is a convenience method for
+// [Screen.Fill] with a nil cell.
+func (s *Screen) Clear() bool {
+ return s.ClearRect(s.newbuf.Bounds())
+}
+
+// ClearRect clears the given rectangle with blank cells. This is a convenience
+// method for [Screen.FillRect] with a nil cell.
+func (s *Screen) ClearRect(r Rectangle) bool {
+ return s.FillRect(nil, r)
+}
+
+// SetCell implements Window.
+func (s *Screen) SetCell(x int, y int, cell *Cell) (v bool) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ cellWidth := 1
+ if cell != nil {
+ cellWidth = cell.Width
+ }
+ if prev := s.curbuf.Cell(x, y); !cellEqual(prev, cell) {
+ chg, ok := s.touch[y]
+ if !ok {
+ chg = lineData{firstCell: x, lastCell: x + cellWidth}
+ } else {
+ chg.firstCell = min(chg.firstCell, x)
+ chg.lastCell = max(chg.lastCell, x+cellWidth)
+ }
+ s.touch[y] = chg
+ }
+
+ return s.newbuf.SetCell(x, y, cell)
+}
+
+// Fill implements Window.
+func (s *Screen) Fill(cell *Cell) bool {
+ return s.FillRect(cell, s.newbuf.Bounds())
+}
+
+// FillRect implements Window.
+func (s *Screen) FillRect(cell *Cell, r Rectangle) bool {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.newbuf.FillRect(cell, r)
+ for i := r.Min.Y; i < r.Max.Y; i++ {
+ s.touch[i] = lineData{firstCell: r.Min.X, lastCell: r.Max.X}
+ }
+ return true
+}
+
+// isXtermLike returns whether the terminal is xterm-like. This means that the
+// terminal supports ECMA-48 and ANSI X3.64 escape sequences.
+// TODO: Should this be a lookup table into each $TERM terminfo database? Like
+// we could keep a map of ANSI escape sequence to terminfo capability name and
+// check if the database supports the escape sequence. Instead of keeping a
+// list of terminal names here.
+func isXtermLike(termtype string) (v bool) {
+ parts := strings.Split(termtype, "-")
+ if len(parts) == 0 {
+ return
+ }
+
+ switch parts[0] {
+ case
+ "alacritty",
+ "contour",
+ "foot",
+ "ghostty",
+ "kitty",
+ "linux",
+ "rio",
+ "screen",
+ "st",
+ "tmux",
+ "wezterm",
+ "xterm":
+ v = true
+ }
+
+ return
+}
+
+// NewScreen creates a new Screen.
+func NewScreen(w io.Writer, width, height int, opts *ScreenOptions) (s *Screen) {
+ s = new(Screen)
+ s.w = w
+ if opts != nil {
+ s.opts = *opts
+ }
+
+ if s.opts.Term == "" {
+ s.opts.Term = os.Getenv("TERM")
+ }
+
+ if width <= 0 || height <= 0 {
+ if f, ok := w.(term.File); ok {
+ width, height, _ = term.GetSize(f.Fd())
+ }
+ }
+ if width < 0 {
+ width = 0
+ }
+ if height < 0 {
+ height = 0
+ }
+
+ s.buf = new(bytes.Buffer)
+ s.xtermLike = isXtermLike(s.opts.Term)
+ s.curbuf = NewBuffer(width, height)
+ s.newbuf = NewBuffer(width, height)
+ s.cur = Cursor{Position: Pos(-1, -1)} // start at -1 to force a move
+ s.saved = s.cur
+ s.reset()
+
+ return
+}
+
+// Width returns the width of the screen.
+func (s *Screen) Width() int {
+ return s.newbuf.Width()
+}
+
+// Height returns the height of the screen.
+func (s *Screen) Height() int {
+ return s.newbuf.Height()
+}
+
+// cellEqual returns whether the two cells are equal. A nil cell is considered
+// a [BlankCell].
+func cellEqual(a, b *Cell) bool {
+ if a == b {
+ return true
+ }
+ if a == nil {
+ a = &BlankCell
+ }
+ if b == nil {
+ b = &BlankCell
+ }
+ return a.Equal(b)
+}
+
+// putCell draws a cell at the current cursor position.
+func (s *Screen) putCell(cell *Cell) {
+ width, height := s.newbuf.Width(), s.newbuf.Height()
+ if s.opts.AltScreen && s.cur.X == width-1 && s.cur.Y == height-1 {
+ s.putCellLR(cell)
+ } else {
+ s.putAttrCell(cell)
+ }
+}
+
+// wrapCursor wraps the cursor to the next line.
+//
+//nolint:unused
+func (s *Screen) wrapCursor() {
+ const autoRightMargin = true
+ if autoRightMargin {
+ // Assume we have auto wrap mode enabled.
+ s.cur.X = 0
+ s.cur.Y++
+ } else {
+ s.cur.X--
+ }
+}
+
+func (s *Screen) putAttrCell(cell *Cell) {
+ if cell != nil && cell.Empty() {
+ // XXX: Zero width cells are special and should not be written to the
+ // screen no matter what other attributes they have.
+ // Zero width cells are used for wide characters that are split into
+ // multiple cells.
+ return
+ }
+
+ if cell == nil {
+ cell = s.clearBlank()
+ }
+
+ // We're at pending wrap state (phantom cell), incoming cell should
+ // wrap.
+ if s.atPhantom {
+ s.wrapCursor()
+ s.atPhantom = false
+ }
+
+ s.updatePen(cell)
+ s.buf.WriteRune(cell.Rune) //nolint:errcheck
+ for _, c := range cell.Comb {
+ s.buf.WriteRune(c) //nolint:errcheck
+ }
+
+ s.cur.X += cell.Width
+
+ if cell.Width > 0 {
+ s.queuedText = true
+ }
+
+ if s.cur.X >= s.newbuf.Width() {
+ s.atPhantom = true
+ }
+}
+
+// putCellLR draws a cell at the lower right corner of the screen.
+func (s *Screen) putCellLR(cell *Cell) {
+ // Optimize for the lower right corner cell.
+ curX := s.cur.X
+ if cell == nil || !cell.Empty() {
+ s.buf.WriteString(ansi.ResetAutoWrapMode) //nolint:errcheck
+ s.putAttrCell(cell)
+ // Writing to lower-right corner cell should not wrap.
+ s.atPhantom = false
+ s.cur.X = curX
+ s.buf.WriteString(ansi.SetAutoWrapMode) //nolint:errcheck
+ }
+}
+
+// updatePen updates the cursor pen styles.
+func (s *Screen) updatePen(cell *Cell) {
+ if cell == nil {
+ cell = &BlankCell
+ }
+
+ if s.opts.Profile != 0 {
+ // Downsample colors to the given color profile.
+ cell.Style = ConvertStyle(cell.Style, s.opts.Profile)
+ cell.Link = ConvertLink(cell.Link, s.opts.Profile)
+ }
+
+ if !cell.Style.Equal(&s.cur.Style) {
+ seq := cell.Style.DiffSequence(s.cur.Style)
+ if cell.Style.Empty() && len(seq) > len(ansi.ResetStyle) {
+ seq = ansi.ResetStyle
+ }
+ s.buf.WriteString(seq) //nolint:errcheck
+ s.cur.Style = cell.Style
+ }
+ if !cell.Link.Equal(&s.cur.Link) {
+ s.buf.WriteString(ansi.SetHyperlink(cell.Link.URL, cell.Link.Params)) //nolint:errcheck
+ s.cur.Link = cell.Link
+ }
+}
+
+// emitRange emits a range of cells to the buffer. It it equivalent to calling
+// [Screen.putCell] for each cell in the range. This is optimized to use
+// [ansi.ECH] and [ansi.REP].
+// Returns whether the cursor is at the end of interval or somewhere in the
+// middle.
+func (s *Screen) emitRange(line Line, n int) (eoi bool) {
+ for n > 0 {
+ var count int
+ for n > 1 && !cellEqual(line.At(0), line.At(1)) {
+ s.putCell(line.At(0))
+ line = line[1:]
+ n--
+ }
+
+ cell0 := line[0]
+ if n == 1 {
+ s.putCell(cell0)
+ return false
+ }
+
+ count = 2
+ for count < n && cellEqual(line.At(count), cell0) {
+ count++
+ }
+
+ ech := ansi.EraseCharacter(count)
+ cup := ansi.CursorPosition(s.cur.X+count, s.cur.Y)
+ rep := ansi.RepeatPreviousCharacter(count)
+ if s.xtermLike && count > len(ech)+len(cup) && cell0 != nil && cell0.Clear() {
+ s.updatePen(cell0)
+ s.buf.WriteString(ech) //nolint:errcheck
+
+ // If this is the last cell, we don't need to move the cursor.
+ if count < n {
+ s.move(s.cur.X+count, s.cur.Y)
+ } else {
+ return true // cursor in the middle
+ }
+ } else if s.xtermLike && count > len(rep) &&
+ (cell0 == nil || (len(cell0.Comb) == 0 && cell0.Rune < 256)) {
+ // We only support ASCII characters. Most terminals will handle
+ // non-ASCII characters correctly, but some might not, ahem xterm.
+ //
+ // NOTE: [ansi.REP] only repeats the last rune and won't work
+ // if the last cell contains multiple runes.
+
+ wrapPossible := s.cur.X+count >= s.newbuf.Width()
+ repCount := count
+ if wrapPossible {
+ repCount--
+ }
+
+ s.updatePen(cell0)
+ s.putCell(cell0)
+ repCount-- // cell0 is a single width cell ASCII character
+
+ s.buf.WriteString(ansi.RepeatPreviousCharacter(repCount)) //nolint:errcheck
+ s.cur.X += repCount
+ if wrapPossible {
+ s.putCell(cell0)
+ }
+ } else {
+ for i := 0; i < count; i++ {
+ s.putCell(line.At(i))
+ }
+ }
+
+ line = line[clamp(count, 0, len(line)):]
+ n -= count
+ }
+
+ return
+}
+
+// putRange puts a range of cells from the old line to the new line.
+// Returns whether the cursor is at the end of interval or somewhere in the
+// middle.
+func (s *Screen) putRange(oldLine, newLine Line, y, start, end int) (eoi bool) {
+ inline := min(len(ansi.CursorPosition(start+1, y+1)),
+ min(len(ansi.HorizontalPositionAbsolute(start+1)),
+ len(ansi.CursorForward(start+1))))
+ if (end - start + 1) > inline {
+ var j, same int
+ for j, same = start, 0; j <= end; j++ {
+ oldCell, newCell := oldLine.At(j), newLine.At(j)
+ if same == 0 && oldCell != nil && oldCell.Empty() {
+ continue
+ }
+ if cellEqual(oldCell, newCell) {
+ same++
+ } else {
+ if same > end-start {
+ s.emitRange(newLine[start:], j-same-start)
+ s.move(j, y)
+ start = j
+ }
+ same = 0
+ }
+ }
+
+ i := s.emitRange(newLine[start:], j-same-start)
+
+ // Always return 1 for the next [Screen.move] after a [Screen.putRange] if
+ // we found identical characters at end of interval.
+ if same == 0 {
+ return i
+ }
+ return true
+ }
+
+ return s.emitRange(newLine[start:], end-start+1)
+}
+
+// clearToEnd clears the screen from the current cursor position to the end of
+// line.
+func (s *Screen) clearToEnd(blank *Cell, force bool) { //nolint:unparam
+ if s.cur.Y >= 0 {
+ curline := s.curbuf.Line(s.cur.Y)
+ for j := s.cur.X; j < s.curbuf.Width(); j++ {
+ if j >= 0 {
+ c := curline.At(j)
+ if !cellEqual(c, blank) {
+ curline.Set(j, blank)
+ force = true
+ }
+ }
+ }
+ }
+
+ if force {
+ s.updatePen(blank)
+ count := s.newbuf.Width() - s.cur.X
+ if s.el0Cost() <= count {
+ s.buf.WriteString(ansi.EraseLineRight) //nolint:errcheck
+ } else {
+ for i := 0; i < count; i++ {
+ s.putCell(blank)
+ }
+ }
+ }
+}
+
+// clearBlank returns a blank cell based on the current cursor background color.
+func (s *Screen) clearBlank() *Cell {
+ c := BlankCell
+ if !s.cur.Style.Empty() || !s.cur.Link.Empty() {
+ c.Style = s.cur.Style
+ c.Link = s.cur.Link
+ }
+ return &c
+}
+
+// insertCells inserts the count cells pointed by the given line at the current
+// cursor position.
+func (s *Screen) insertCells(line Line, count int) {
+ if s.xtermLike {
+ // Use [ansi.ICH] as an optimization.
+ s.buf.WriteString(ansi.InsertCharacter(count)) //nolint:errcheck
+ } else {
+ // Otherwise, use [ansi.IRM] mode.
+ s.buf.WriteString(ansi.SetInsertReplaceMode) //nolint:errcheck
+ }
+
+ for i := 0; count > 0; i++ {
+ s.putAttrCell(line[i])
+ count--
+ }
+
+ if !s.xtermLike {
+ s.buf.WriteString(ansi.ResetInsertReplaceMode) //nolint:errcheck
+ }
+}
+
+// el0Cost returns the cost of using [ansi.EL] 0 i.e. [ansi.EraseLineRight]. If
+// this terminal supports background color erase, it can be cheaper to use
+// [ansi.EL] 0 i.e. [ansi.EraseLineRight] to clear
+// trailing spaces.
+func (s *Screen) el0Cost() int {
+ if s.xtermLike {
+ return 0
+ }
+ return len(ansi.EraseLineRight)
+}
+
+// transformLine transforms the given line in the current window to the
+// corresponding line in the new window. It uses [ansi.ICH] and [ansi.DCH] to
+// insert or delete characters.
+func (s *Screen) transformLine(y int) {
+ var firstCell, oLastCell, nLastCell int // first, old last, new last index
+ oldLine := s.curbuf.Line(y)
+ newLine := s.newbuf.Line(y)
+
+ // Find the first changed cell in the line
+ var lineChanged bool
+ for i := 0; i < s.newbuf.Width(); i++ {
+ if !cellEqual(newLine.At(i), oldLine.At(i)) {
+ lineChanged = true
+ break
+ }
+ }
+
+ const ceolStandoutGlitch = false
+ if ceolStandoutGlitch && lineChanged {
+ s.move(0, y)
+ s.clearToEnd(nil, false)
+ s.putRange(oldLine, newLine, y, 0, s.newbuf.Width()-1)
+ } else {
+ blank := newLine.At(0)
+
+ // It might be cheaper to clear leading spaces with [ansi.EL] 1 i.e.
+ // [ansi.EraseLineLeft].
+ if blank == nil || blank.Clear() {
+ var oFirstCell, nFirstCell int
+ for oFirstCell = 0; oFirstCell < s.curbuf.Width(); oFirstCell++ {
+ if !cellEqual(oldLine.At(oFirstCell), blank) {
+ break
+ }
+ }
+ for nFirstCell = 0; nFirstCell < s.newbuf.Width(); nFirstCell++ {
+ if !cellEqual(newLine.At(nFirstCell), blank) {
+ break
+ }
+ }
+
+ if nFirstCell == oFirstCell {
+ firstCell = nFirstCell
+
+ // Find the first differing cell
+ for firstCell < s.newbuf.Width() &&
+ cellEqual(oldLine.At(firstCell), newLine.At(firstCell)) {
+ firstCell++
+ }
+ } else if oFirstCell > nFirstCell {
+ firstCell = nFirstCell
+ } else if oFirstCell < nFirstCell {
+ firstCell = oFirstCell
+ el1Cost := len(ansi.EraseLineLeft)
+ if el1Cost < nFirstCell-oFirstCell {
+ if nFirstCell >= s.newbuf.Width() {
+ s.move(0, y)
+ s.updatePen(blank)
+ s.buf.WriteString(ansi.EraseLineRight) //nolint:errcheck
+ } else {
+ s.move(nFirstCell-1, y)
+ s.updatePen(blank)
+ s.buf.WriteString(ansi.EraseLineLeft) //nolint:errcheck
+ }
+
+ for firstCell < nFirstCell {
+ oldLine.Set(firstCell, blank)
+ firstCell++
+ }
+ }
+ }
+ } else {
+ // Find the first differing cell
+ for firstCell < s.newbuf.Width() && cellEqual(newLine.At(firstCell), oldLine.At(firstCell)) {
+ firstCell++
+ }
+ }
+
+ // If we didn't find one, we're done
+ if firstCell >= s.newbuf.Width() {
+ return
+ }
+
+ blank = newLine.At(s.newbuf.Width() - 1)
+ if blank != nil && !blank.Clear() {
+ // Find the last differing cell
+ nLastCell = s.newbuf.Width() - 1
+ for nLastCell > firstCell && cellEqual(newLine.At(nLastCell), oldLine.At(nLastCell)) {
+ nLastCell--
+ }
+
+ if nLastCell >= firstCell {
+ s.move(firstCell, y)
+ s.putRange(oldLine, newLine, y, firstCell, nLastCell)
+ if firstCell < len(oldLine) && firstCell < len(newLine) {
+ copy(oldLine[firstCell:], newLine[firstCell:])
+ } else {
+ copy(oldLine, newLine)
+ }
+ }
+
+ return
+ }
+
+ // Find last non-blank cell in the old line.
+ oLastCell = s.curbuf.Width() - 1
+ for oLastCell > firstCell && cellEqual(oldLine.At(oLastCell), blank) {
+ oLastCell--
+ }
+
+ // Find last non-blank cell in the new line.
+ nLastCell = s.newbuf.Width() - 1
+ for nLastCell > firstCell && cellEqual(newLine.At(nLastCell), blank) {
+ nLastCell--
+ }
+
+ if nLastCell == firstCell && s.el0Cost() < oLastCell-nLastCell {
+ s.move(firstCell, y)
+ if !cellEqual(newLine.At(firstCell), blank) {
+ s.putCell(newLine.At(firstCell))
+ }
+ s.clearToEnd(blank, false)
+ } else if nLastCell != oLastCell &&
+ !cellEqual(newLine.At(nLastCell), oldLine.At(oLastCell)) {
+ s.move(firstCell, y)
+ if oLastCell-nLastCell > s.el0Cost() {
+ if s.putRange(oldLine, newLine, y, firstCell, nLastCell) {
+ s.move(nLastCell+1, y)
+ }
+ s.clearToEnd(blank, false)
+ } else {
+ n := max(nLastCell, oLastCell)
+ s.putRange(oldLine, newLine, y, firstCell, n)
+ }
+ } else {
+ nLastNonBlank := nLastCell
+ oLastNonBlank := oLastCell
+
+ // Find the last cells that really differ.
+ // Can be -1 if no cells differ.
+ for cellEqual(newLine.At(nLastCell), oldLine.At(oLastCell)) {
+ if !cellEqual(newLine.At(nLastCell-1), oldLine.At(oLastCell-1)) {
+ break
+ }
+ nLastCell--
+ oLastCell--
+ if nLastCell == -1 || oLastCell == -1 {
+ break
+ }
+ }
+
+ n := min(oLastCell, nLastCell)
+ if n >= firstCell {
+ s.move(firstCell, y)
+ s.putRange(oldLine, newLine, y, firstCell, n)
+ }
+
+ if oLastCell < nLastCell {
+ m := max(nLastNonBlank, oLastNonBlank)
+ if n != 0 {
+ for n > 0 {
+ wide := newLine.At(n + 1)
+ if wide == nil || !wide.Empty() {
+ break
+ }
+ n--
+ oLastCell--
+ }
+ } else if n >= firstCell && newLine.At(n) != nil && newLine.At(n).Width > 1 {
+ next := newLine.At(n + 1)
+ for next != nil && next.Empty() {
+ n++
+ oLastCell++
+ }
+ }
+
+ s.move(n+1, y)
+ ichCost := 3 + nLastCell - oLastCell
+ if s.xtermLike && (nLastCell < nLastNonBlank || ichCost > (m-n)) {
+ s.putRange(oldLine, newLine, y, n+1, m)
+ } else {
+ s.insertCells(newLine[n+1:], nLastCell-oLastCell)
+ }
+ } else if oLastCell > nLastCell {
+ s.move(n+1, y)
+ dchCost := 3 + oLastCell - nLastCell
+ if dchCost > len(ansi.EraseLineRight)+nLastNonBlank-(n+1) {
+ if s.putRange(oldLine, newLine, y, n+1, nLastNonBlank) {
+ s.move(nLastNonBlank+1, y)
+ }
+ s.clearToEnd(blank, false)
+ } else {
+ s.updatePen(blank)
+ s.deleteCells(oLastCell - nLastCell)
+ }
+ }
+ }
+ }
+
+ // Update the old line with the new line
+ if firstCell < len(oldLine) && firstCell < len(newLine) {
+ copy(oldLine[firstCell:], newLine[firstCell:])
+ } else {
+ copy(oldLine, newLine)
+ }
+}
+
+// deleteCells deletes the count cells at the current cursor position and moves
+// the rest of the line to the left. This is equivalent to [ansi.DCH].
+func (s *Screen) deleteCells(count int) {
+ // [ansi.DCH] will shift in cells from the right margin so we need to
+ // ensure that they are the right style.
+ s.buf.WriteString(ansi.DeleteCharacter(count)) //nolint:errcheck
+}
+
+// clearToBottom clears the screen from the current cursor position to the end
+// of the screen.
+func (s *Screen) clearToBottom(blank *Cell) {
+ row, col := s.cur.Y, s.cur.X
+ if row < 0 {
+ row = 0
+ }
+
+ s.updatePen(blank)
+ s.buf.WriteString(ansi.EraseScreenBelow) //nolint:errcheck
+ // Clear the rest of the current line
+ s.curbuf.ClearRect(Rect(col, row, s.curbuf.Width()-col, 1))
+ // Clear everything below the current line
+ s.curbuf.ClearRect(Rect(0, row+1, s.curbuf.Width(), s.curbuf.Height()-row-1))
+}
+
+// clearBottom tests if clearing the end of the screen would satisfy part of
+// the screen update. Scan backwards through lines in the screen checking if
+// each is blank and one or more are changed.
+// It returns the top line.
+func (s *Screen) clearBottom(total int) (top int) {
+ if total <= 0 {
+ return
+ }
+
+ top = total
+ last := s.newbuf.Width()
+ blank := s.clearBlank()
+ canClearWithBlank := blank == nil || blank.Clear()
+
+ if canClearWithBlank {
+ var row int
+ for row = total - 1; row >= 0; row-- {
+ oldLine := s.curbuf.Line(row)
+ newLine := s.newbuf.Line(row)
+
+ var col int
+ ok := true
+ for col = 0; ok && col < last; col++ {
+ ok = cellEqual(newLine.At(col), blank)
+ }
+ if !ok {
+ break
+ }
+
+ for col = 0; ok && col < last; col++ {
+ ok = len(oldLine) == last && cellEqual(oldLine.At(col), blank)
+ }
+ if !ok {
+ top = row
+ }
+ }
+
+ if top < total {
+ s.move(0, top-1) // top is 1-based
+ s.clearToBottom(blank)
+ if s.oldhash != nil && s.newhash != nil &&
+ row < len(s.oldhash) && row < len(s.newhash) {
+ for row := top; row < s.newbuf.Height(); row++ {
+ s.oldhash[row] = s.newhash[row]
+ }
+ }
+ }
+ }
+
+ return
+}
+
+// clearScreen clears the screen and put cursor at home.
+func (s *Screen) clearScreen(blank *Cell) {
+ s.updatePen(blank)
+ s.buf.WriteString(ansi.CursorHomePosition) //nolint:errcheck
+ s.buf.WriteString(ansi.EraseEntireScreen) //nolint:errcheck
+ s.cur.X, s.cur.Y = 0, 0
+ s.curbuf.Fill(blank)
+}
+
+// clearBelow clears everything below and including the row.
+func (s *Screen) clearBelow(blank *Cell, row int) {
+ s.move(0, row)
+ s.clearToBottom(blank)
+}
+
+// clearUpdate forces a screen redraw.
+func (s *Screen) clearUpdate() {
+ blank := s.clearBlank()
+ var nonEmpty int
+ if s.opts.AltScreen {
+ // XXX: We're using the maximum height of the two buffers to ensure
+ // we write newly added lines to the screen in [Screen.transformLine].
+ nonEmpty = max(s.curbuf.Height(), s.newbuf.Height())
+ s.clearScreen(blank)
+ } else {
+ nonEmpty = s.newbuf.Height()
+ s.clearBelow(blank, 0)
+ }
+ nonEmpty = s.clearBottom(nonEmpty)
+ for i := 0; i < nonEmpty; i++ {
+ s.transformLine(i)
+ }
+}
+
+// Flush flushes the buffer to the screen.
+func (s *Screen) Flush() (err error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ return s.flush()
+}
+
+func (s *Screen) flush() (err error) {
+ // Write the buffer
+ if s.buf.Len() > 0 {
+ _, err = s.w.Write(s.buf.Bytes()) //nolint:errcheck
+ if err == nil {
+ s.buf.Reset()
+ }
+ }
+
+ return
+}
+
+// Render renders changes of the screen to the internal buffer. Call
+// [Screen.Flush] to flush pending changes to the screen.
+func (s *Screen) Render() {
+ s.mu.Lock()
+ s.render()
+ s.mu.Unlock()
+}
+
+func (s *Screen) render() {
+ // Do we need to render anything?
+ if s.opts.AltScreen == s.altScreenMode &&
+ !s.opts.ShowCursor == s.cursorHidden &&
+ !s.clear &&
+ len(s.touch) == 0 &&
+ len(s.queueAbove) == 0 {
+ return
+ }
+
+ // TODO: Investigate whether this is necessary. Theoretically, terminals
+ // can add/remove tab stops and we should be able to handle that. We could
+ // use [ansi.DECTABSR] to read the tab stops, but that's not implemented in
+ // most terminals :/
+ // // Are we using hard tabs? If so, ensure tabs are using the
+ // // default interval using [ansi.DECST8C].
+ // if s.opts.HardTabs && !s.initTabs {
+ // s.buf.WriteString(ansi.SetTabEvery8Columns)
+ // s.initTabs = true
+ // }
+
+ // Do we need alt-screen mode?
+ if s.opts.AltScreen != s.altScreenMode {
+ if s.opts.AltScreen {
+ s.buf.WriteString(ansi.SetAltScreenSaveCursorMode)
+ } else {
+ s.buf.WriteString(ansi.ResetAltScreenSaveCursorMode)
+ }
+ s.altScreenMode = s.opts.AltScreen
+ }
+
+ // Do we need text cursor mode?
+ if !s.opts.ShowCursor != s.cursorHidden {
+ s.cursorHidden = !s.opts.ShowCursor
+ if s.cursorHidden {
+ s.buf.WriteString(ansi.HideCursor)
+ }
+ }
+
+ // Do we have queued strings to write above the screen?
+ if len(s.queueAbove) > 0 {
+ // TODO: Use scrolling region if available.
+ // TODO: Use [Screen.Write] [io.Writer] interface.
+
+ // We need to scroll the screen up by the number of lines in the queue.
+ // We can't use [ansi.SU] because we want the cursor to move down until
+ // it reaches the bottom of the screen.
+ s.move(0, s.newbuf.Height()-1)
+ s.buf.WriteString(strings.Repeat("\n", len(s.queueAbove)))
+ s.cur.Y += len(s.queueAbove)
+ // XXX: Now go to the top of the screen, insert new lines, and write
+ // the queued strings. It is important to use [Screen.moveCursor]
+ // instead of [Screen.move] because we don't want to perform any checks
+ // on the cursor position.
+ s.moveCursor(0, 0, false)
+ s.buf.WriteString(ansi.InsertLine(len(s.queueAbove)))
+ for _, line := range s.queueAbove {
+ s.buf.WriteString(line + "\r\n")
+ }
+
+ // Clear the queue
+ s.queueAbove = s.queueAbove[:0]
+ }
+
+ var nonEmpty int
+
+ // XXX: In inline mode, after a screen resize, we need to clear the extra
+ // lines at the bottom of the screen. This is because in inline mode, we
+ // don't use the full screen height and the current buffer size might be
+ // larger than the new buffer size.
+ partialClear := !s.opts.AltScreen && s.cur.X != -1 && s.cur.Y != -1 &&
+ s.curbuf.Width() == s.newbuf.Width() &&
+ s.curbuf.Height() > 0 &&
+ s.curbuf.Height() > s.newbuf.Height()
+
+ if !s.clear && partialClear {
+ s.clearBelow(nil, s.newbuf.Height()-1)
+ }
+
+ if s.clear {
+ s.clearUpdate()
+ s.clear = false
+ } else if len(s.touch) > 0 {
+ if s.opts.AltScreen {
+ // Optimize scrolling for the alternate screen buffer.
+ // TODO: Should we optimize for inline mode as well? If so, we need
+ // to know the actual cursor position to use [ansi.DECSTBM].
+ s.scrollOptimize()
+ }
+
+ var changedLines int
+ var i int
+
+ if s.opts.AltScreen {
+ nonEmpty = min(s.curbuf.Height(), s.newbuf.Height())
+ } else {
+ nonEmpty = s.newbuf.Height()
+ }
+
+ nonEmpty = s.clearBottom(nonEmpty)
+ for i = 0; i < nonEmpty; i++ {
+ _, ok := s.touch[i]
+ if ok {
+ s.transformLine(i)
+ changedLines++
+ }
+ }
+ }
+
+ // Sync windows and screen
+ s.touch = make(map[int]lineData, s.newbuf.Height())
+
+ if s.curbuf.Width() != s.newbuf.Width() || s.curbuf.Height() != s.newbuf.Height() {
+ // Resize the old buffer to match the new buffer.
+ _, oldh := s.curbuf.Width(), s.curbuf.Height()
+ s.curbuf.Resize(s.newbuf.Width(), s.newbuf.Height())
+ // Sync new lines to old lines
+ for i := oldh - 1; i < s.newbuf.Height(); i++ {
+ copy(s.curbuf.Line(i), s.newbuf.Line(i))
+ }
+ }
+
+ s.updatePen(nil) // nil indicates a blank cell with no styles
+
+ // Do we have enough changes to justify toggling the cursor?
+ if s.buf.Len() > 1 && s.opts.ShowCursor && !s.cursorHidden && s.queuedText {
+ nb := new(bytes.Buffer)
+ nb.Grow(s.buf.Len() + len(ansi.HideCursor) + len(ansi.ShowCursor))
+ nb.WriteString(ansi.HideCursor)
+ nb.Write(s.buf.Bytes())
+ nb.WriteString(ansi.ShowCursor)
+ *s.buf = *nb
+ }
+
+ s.queuedText = false
+}
+
+// Close writes the final screen update and resets the screen.
+func (s *Screen) Close() (err error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.render()
+ s.updatePen(nil)
+ // Go to the bottom of the screen
+ s.move(0, s.newbuf.Height()-1)
+
+ if s.altScreenMode {
+ s.buf.WriteString(ansi.ResetAltScreenSaveCursorMode)
+ s.altScreenMode = false
+ }
+
+ if s.cursorHidden {
+ s.buf.WriteString(ansi.ShowCursor)
+ s.cursorHidden = false
+ }
+
+ // Write the buffer
+ err = s.flush()
+ if err != nil {
+ return
+ }
+
+ s.reset()
+ return
+}
+
+// reset resets the screen to its initial state.
+func (s *Screen) reset() {
+ s.scrollHeight = 0
+ s.cursorHidden = false
+ s.altScreenMode = false
+ s.touch = make(map[int]lineData, s.newbuf.Height())
+ if s.curbuf != nil {
+ s.curbuf.Clear()
+ }
+ if s.newbuf != nil {
+ s.newbuf.Clear()
+ }
+ s.buf.Reset()
+ s.tabs = DefaultTabStops(s.newbuf.Width())
+ s.oldhash, s.newhash = nil, nil
+
+ // We always disable HardTabs when termtype is "linux".
+ if strings.HasPrefix(s.opts.Term, "linux") {
+ s.opts.HardTabs = false
+ }
+}
+
+// Resize resizes the screen.
+func (s *Screen) Resize(width, height int) bool {
+ oldw := s.newbuf.Width()
+ oldh := s.newbuf.Height()
+
+ if s.opts.AltScreen || width != oldw {
+ // We only clear the whole screen if the width changes. Adding/removing
+ // rows is handled by the [Screen.render] and [Screen.transformLine]
+ // methods.
+ s.clear = true
+ }
+
+ // Clear new columns and lines
+ if width > oldh {
+ s.ClearRect(Rect(max(oldw-1, 0), 0, width-oldw, height))
+ } else if width < oldw {
+ s.ClearRect(Rect(max(width-1, 0), 0, oldw-width, height))
+ }
+
+ if height > oldh {
+ s.ClearRect(Rect(0, max(oldh-1, 0), width, height-oldh))
+ } else if height < oldh {
+ s.ClearRect(Rect(0, max(height-1, 0), width, oldh-height))
+ }
+
+ s.mu.Lock()
+ s.newbuf.Resize(width, height)
+ s.tabs.Resize(width)
+ s.oldhash, s.newhash = nil, nil
+ s.scrollHeight = 0 // reset scroll lines
+ s.mu.Unlock()
+
+ return true
+}
+
+// MoveTo moves the cursor to the given position.
+func (s *Screen) MoveTo(x, y int) {
+ s.mu.Lock()
+ s.move(x, y)
+ s.mu.Unlock()
+}
+
+// InsertAbove inserts string above the screen. The inserted string is not
+// managed by the screen. This does nothing when alternate screen mode is
+// enabled.
+func (s *Screen) InsertAbove(str string) {
+ if s.opts.AltScreen {
+ return
+ }
+ s.mu.Lock()
+ for _, line := range strings.Split(str, "\n") {
+ s.queueAbove = append(s.queueAbove, s.method.Truncate(line, s.Width(), ""))
+ }
+ s.mu.Unlock()
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/sequence.go b/vendor/github.com/charmbracelet/x/cellbuf/sequence.go
new file mode 100644
index 00000000..613eefef
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/sequence.go
@@ -0,0 +1,131 @@
+package cellbuf
+
+import (
+ "bytes"
+ "image/color"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+// ReadStyle reads a Select Graphic Rendition (SGR) escape sequences from a
+// list of parameters.
+func ReadStyle(params ansi.Params, pen *Style) {
+ if len(params) == 0 {
+ pen.Reset()
+ return
+ }
+
+ for i := 0; i < len(params); i++ {
+ param, hasMore, _ := params.Param(i, 0)
+ switch param {
+ case 0: // Reset
+ pen.Reset()
+ case 1: // Bold
+ pen.Bold(true)
+ case 2: // Dim/Faint
+ pen.Faint(true)
+ case 3: // Italic
+ pen.Italic(true)
+ case 4: // Underline
+ nextParam, _, ok := params.Param(i+1, 0)
+ if hasMore && ok { // Only accept subparameters i.e. separated by ":"
+ switch nextParam {
+ case 0, 1, 2, 3, 4, 5:
+ i++
+ switch nextParam {
+ case 0: // No Underline
+ pen.UnderlineStyle(NoUnderline)
+ case 1: // Single Underline
+ pen.UnderlineStyle(SingleUnderline)
+ case 2: // Double Underline
+ pen.UnderlineStyle(DoubleUnderline)
+ case 3: // Curly Underline
+ pen.UnderlineStyle(CurlyUnderline)
+ case 4: // Dotted Underline
+ pen.UnderlineStyle(DottedUnderline)
+ case 5: // Dashed Underline
+ pen.UnderlineStyle(DashedUnderline)
+ }
+ }
+ } else {
+ // Single Underline
+ pen.Underline(true)
+ }
+ case 5: // Slow Blink
+ pen.SlowBlink(true)
+ case 6: // Rapid Blink
+ pen.RapidBlink(true)
+ case 7: // Reverse
+ pen.Reverse(true)
+ case 8: // Conceal
+ pen.Conceal(true)
+ case 9: // Crossed-out/Strikethrough
+ pen.Strikethrough(true)
+ case 22: // Normal Intensity (not bold or faint)
+ pen.Bold(false).Faint(false)
+ case 23: // Not italic, not Fraktur
+ pen.Italic(false)
+ case 24: // Not underlined
+ pen.Underline(false)
+ case 25: // Blink off
+ pen.SlowBlink(false).RapidBlink(false)
+ case 27: // Positive (not reverse)
+ pen.Reverse(false)
+ case 28: // Reveal
+ pen.Conceal(false)
+ case 29: // Not crossed out
+ pen.Strikethrough(false)
+ case 30, 31, 32, 33, 34, 35, 36, 37: // Set foreground
+ pen.Foreground(ansi.Black + ansi.BasicColor(param-30)) //nolint:gosec
+ case 38: // Set foreground 256 or truecolor
+ var c color.Color
+ n := ReadStyleColor(params[i:], &c)
+ if n > 0 {
+ pen.Foreground(c)
+ i += n - 1
+ }
+ case 39: // Default foreground
+ pen.Foreground(nil)
+ case 40, 41, 42, 43, 44, 45, 46, 47: // Set background
+ pen.Background(ansi.Black + ansi.BasicColor(param-40)) //nolint:gosec
+ case 48: // Set background 256 or truecolor
+ var c color.Color
+ n := ReadStyleColor(params[i:], &c)
+ if n > 0 {
+ pen.Background(c)
+ i += n - 1
+ }
+ case 49: // Default Background
+ pen.Background(nil)
+ case 58: // Set underline color
+ var c color.Color
+ n := ReadStyleColor(params[i:], &c)
+ if n > 0 {
+ pen.UnderlineColor(c)
+ i += n - 1
+ }
+ case 59: // Default underline color
+ pen.UnderlineColor(nil)
+ case 90, 91, 92, 93, 94, 95, 96, 97: // Set bright foreground
+ pen.Foreground(ansi.BrightBlack + ansi.BasicColor(param-90)) //nolint:gosec
+ case 100, 101, 102, 103, 104, 105, 106, 107: // Set bright background
+ pen.Background(ansi.BrightBlack + ansi.BasicColor(param-100)) //nolint:gosec
+ }
+ }
+}
+
+// ReadLink reads a hyperlink escape sequence from a data buffer.
+func ReadLink(p []byte, link *Link) {
+ params := bytes.Split(p, []byte{';'})
+ if len(params) != 3 {
+ return
+ }
+ link.Params = string(params[1])
+ link.URL = string(params[2])
+}
+
+// ReadStyleColor reads a color from a list of parameters.
+// See [ansi.ReadStyleColor] for more information.
+func ReadStyleColor(params ansi.Params, c *color.Color) int {
+ return ansi.ReadStyleColor(params, c)
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/style.go b/vendor/github.com/charmbracelet/x/cellbuf/style.go
new file mode 100644
index 00000000..82c4afb7
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/style.go
@@ -0,0 +1,31 @@
+package cellbuf
+
+import (
+ "github.com/charmbracelet/colorprofile"
+)
+
+// Convert converts a style to respect the given color profile.
+func ConvertStyle(s Style, p colorprofile.Profile) Style {
+ switch p {
+ case colorprofile.TrueColor:
+ return s
+ case colorprofile.Ascii:
+ s.Fg = nil
+ s.Bg = nil
+ s.Ul = nil
+ case colorprofile.NoTTY:
+ return Style{}
+ }
+
+ if s.Fg != nil {
+ s.Fg = p.Convert(s.Fg)
+ }
+ if s.Bg != nil {
+ s.Bg = p.Convert(s.Bg)
+ }
+ if s.Ul != nil {
+ s.Ul = p.Convert(s.Ul)
+ }
+
+ return s
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/tabstop.go b/vendor/github.com/charmbracelet/x/cellbuf/tabstop.go
new file mode 100644
index 00000000..24eec449
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/tabstop.go
@@ -0,0 +1,137 @@
+package cellbuf
+
+// DefaultTabInterval is the default tab interval.
+const DefaultTabInterval = 8
+
+// TabStops represents horizontal line tab stops.
+type TabStops struct {
+ stops []int
+ interval int
+ width int
+}
+
+// NewTabStops creates a new set of tab stops from a number of columns and an
+// interval.
+func NewTabStops(width, interval int) *TabStops {
+ ts := new(TabStops)
+ ts.interval = interval
+ ts.width = width
+ ts.stops = make([]int, (width+(interval-1))/interval)
+ ts.init(0, width)
+ return ts
+}
+
+// DefaultTabStops creates a new set of tab stops with the default interval.
+func DefaultTabStops(cols int) *TabStops {
+ return NewTabStops(cols, DefaultTabInterval)
+}
+
+// Resize resizes the tab stops to the given width.
+func (ts *TabStops) Resize(width int) {
+ if width == ts.width {
+ return
+ }
+
+ if width < ts.width {
+ size := (width + (ts.interval - 1)) / ts.interval
+ ts.stops = ts.stops[:size]
+ } else {
+ size := (width - ts.width + (ts.interval - 1)) / ts.interval
+ ts.stops = append(ts.stops, make([]int, size)...)
+ }
+
+ ts.init(ts.width, width)
+ ts.width = width
+}
+
+// IsStop returns true if the given column is a tab stop.
+func (ts TabStops) IsStop(col int) bool {
+ mask := ts.mask(col)
+ i := col >> 3
+ if i < 0 || i >= len(ts.stops) {
+ return false
+ }
+ return ts.stops[i]&mask != 0
+}
+
+// Next returns the next tab stop after the given column.
+func (ts TabStops) Next(col int) int {
+ return ts.Find(col, 1)
+}
+
+// Prev returns the previous tab stop before the given column.
+func (ts TabStops) Prev(col int) int {
+ return ts.Find(col, -1)
+}
+
+// Find returns the prev/next tab stop before/after the given column and delta.
+// If delta is positive, it returns the next tab stop after the given column.
+// If delta is negative, it returns the previous tab stop before the given column.
+// If delta is zero, it returns the given column.
+func (ts TabStops) Find(col, delta int) int {
+ if delta == 0 {
+ return col
+ }
+
+ var prev bool
+ count := delta
+ if count < 0 {
+ count = -count
+ prev = true
+ }
+
+ for count > 0 {
+ if !prev {
+ if col >= ts.width-1 {
+ return col
+ }
+
+ col++
+ } else {
+ if col < 1 {
+ return col
+ }
+
+ col--
+ }
+
+ if ts.IsStop(col) {
+ count--
+ }
+ }
+
+ return col
+}
+
+// Set adds a tab stop at the given column.
+func (ts *TabStops) Set(col int) {
+ mask := ts.mask(col)
+ ts.stops[col>>3] |= mask
+}
+
+// Reset removes the tab stop at the given column.
+func (ts *TabStops) Reset(col int) {
+ mask := ts.mask(col)
+ ts.stops[col>>3] &= ^mask
+}
+
+// Clear removes all tab stops.
+func (ts *TabStops) Clear() {
+ ts.stops = make([]int, len(ts.stops))
+}
+
+// mask returns the mask for the given column.
+func (ts *TabStops) mask(col int) int {
+ return 1 << (col & (ts.interval - 1))
+}
+
+// init initializes the tab stops starting from col until width.
+func (ts *TabStops) init(col, width int) {
+ for x := col; x < width; x++ {
+ if x%ts.interval == 0 {
+ ts.Set(x)
+ } else {
+ ts.Reset(x)
+ }
+ }
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/utils.go b/vendor/github.com/charmbracelet/x/cellbuf/utils.go
new file mode 100644
index 00000000..b0452fa9
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/utils.go
@@ -0,0 +1,38 @@
+package cellbuf
+
+import (
+ "strings"
+)
+
+// Height returns the height of a string.
+func Height(s string) int {
+ return strings.Count(s, "\n") + 1
+}
+
+func min(a, b int) int { //nolint:predeclared
+ if a > b {
+ return b
+ }
+ return a
+}
+
+func max(a, b int) int { //nolint:predeclared
+ if a > b {
+ return a
+ }
+ return b
+}
+
+func clamp(v, low, high int) int {
+ if high < low {
+ low, high = high, low
+ }
+ return min(high, max(low, v))
+}
+
+func abs(a int) int {
+ if a < 0 {
+ return -a
+ }
+ return a
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/wrap.go b/vendor/github.com/charmbracelet/x/cellbuf/wrap.go
new file mode 100644
index 00000000..f89f52f6
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/wrap.go
@@ -0,0 +1,185 @@
+package cellbuf
+
+import (
+ "bytes"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+const nbsp = '\u00a0'
+
+// Wrap returns a string that is wrapped to the specified limit applying any
+// ANSI escape sequences in the string. It tries to wrap the string at word
+// boundaries, but will break words if necessary.
+//
+// The breakpoints string is a list of characters that are considered
+// breakpoints for word wrapping. A hyphen (-) is always considered a
+// breakpoint.
+//
+// Note: breakpoints must be a string of 1-cell wide rune characters.
+func Wrap(s string, limit int, breakpoints string) string {
+ if len(s) == 0 {
+ return ""
+ }
+
+ if limit < 1 {
+ return s
+ }
+
+ p := ansi.GetParser()
+ defer ansi.PutParser(p)
+
+ var (
+ buf bytes.Buffer
+ word bytes.Buffer
+ space bytes.Buffer
+ style, curStyle Style
+ link, curLink Link
+ curWidth int
+ wordLen int
+ )
+
+ hasBlankStyle := func() bool {
+ // Only follow reverse attribute, bg color and underline style
+ return !style.Attrs.Contains(ReverseAttr) && style.Bg == nil && style.UlStyle == NoUnderline
+ }
+
+ addSpace := func() {
+ curWidth += space.Len()
+ buf.Write(space.Bytes())
+ space.Reset()
+ }
+
+ addWord := func() {
+ if word.Len() == 0 {
+ return
+ }
+
+ curLink = link
+ curStyle = style
+
+ addSpace()
+ curWidth += wordLen
+ buf.Write(word.Bytes())
+ word.Reset()
+ wordLen = 0
+ }
+
+ addNewline := func() {
+ if !curStyle.Empty() {
+ buf.WriteString(ansi.ResetStyle)
+ }
+ if !curLink.Empty() {
+ buf.WriteString(ansi.ResetHyperlink())
+ }
+ buf.WriteByte('\n')
+ if !curLink.Empty() {
+ buf.WriteString(ansi.SetHyperlink(curLink.URL, curLink.Params))
+ }
+ if !curStyle.Empty() {
+ buf.WriteString(curStyle.Sequence())
+ }
+ curWidth = 0
+ space.Reset()
+ }
+
+ var state byte
+ for len(s) > 0 {
+ seq, width, n, newState := ansi.DecodeSequence(s, state, p)
+ switch width {
+ case 0:
+ if ansi.Equal(seq, "\t") {
+ addWord()
+ space.WriteString(seq)
+ break
+ } else if ansi.Equal(seq, "\n") {
+ if wordLen == 0 {
+ if curWidth+space.Len() > limit {
+ curWidth = 0
+ } else {
+ // preserve whitespaces
+ buf.Write(space.Bytes())
+ }
+ space.Reset()
+ }
+
+ addWord()
+ addNewline()
+ break
+ } else if ansi.HasCsiPrefix(seq) && p.Command() == 'm' {
+ // SGR style sequence [ansi.SGR]
+ ReadStyle(p.Params(), &style)
+ } else if ansi.HasOscPrefix(seq) && p.Command() == 8 {
+ // Hyperlink sequence [ansi.SetHyperlink]
+ ReadLink(p.Data(), &link)
+ }
+
+ word.WriteString(seq)
+ default:
+ if len(seq) == 1 {
+ // ASCII
+ r, _ := utf8.DecodeRuneInString(seq)
+ if r != nbsp && unicode.IsSpace(r) && hasBlankStyle() {
+ addWord()
+ space.WriteRune(r)
+ break
+ } else if r == '-' || runeContainsAny(r, breakpoints) {
+ addSpace()
+ if curWidth+wordLen+width <= limit {
+ addWord()
+ buf.WriteString(seq)
+ curWidth += width
+ break
+ }
+ }
+ }
+
+ if wordLen+width > limit {
+ // Hardwrap the word if it's too long
+ addWord()
+ }
+
+ word.WriteString(seq)
+ wordLen += width
+
+ if curWidth+wordLen+space.Len() > limit {
+ addNewline()
+ }
+ }
+
+ s = s[n:]
+ state = newState
+ }
+
+ if wordLen == 0 {
+ if curWidth+space.Len() > limit {
+ curWidth = 0
+ } else {
+ // preserve whitespaces
+ buf.Write(space.Bytes())
+ }
+ space.Reset()
+ }
+
+ addWord()
+
+ if !curLink.Empty() {
+ buf.WriteString(ansi.ResetHyperlink())
+ }
+ if !curStyle.Empty() {
+ buf.WriteString(ansi.ResetStyle)
+ }
+
+ return buf.String()
+}
+
+func runeContainsAny[T string | []rune](r rune, s T) bool {
+ for _, c := range []rune(s) {
+ if c == r {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/writer.go b/vendor/github.com/charmbracelet/x/cellbuf/writer.go
new file mode 100644
index 00000000..ae8b2a81
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/writer.go
@@ -0,0 +1,339 @@
+package cellbuf
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+// CellBuffer is a cell buffer that represents a set of cells in a screen or a
+// grid.
+type CellBuffer interface {
+ // Cell returns the cell at the given position.
+ Cell(x, y int) *Cell
+ // SetCell sets the cell at the given position to the given cell. It
+ // returns whether the cell was set successfully.
+ SetCell(x, y int, c *Cell) bool
+ // Bounds returns the bounds of the cell buffer.
+ Bounds() Rectangle
+}
+
+// FillRect fills the rectangle within the cell buffer with the given cell.
+// This will not fill cells outside the bounds of the cell buffer.
+func FillRect(s CellBuffer, c *Cell, rect Rectangle) {
+ for y := rect.Min.Y; y < rect.Max.Y; y++ {
+ for x := rect.Min.X; x < rect.Max.X; x++ {
+ s.SetCell(x, y, c) //nolint:errcheck
+ }
+ }
+}
+
+// Fill fills the cell buffer with the given cell.
+func Fill(s CellBuffer, c *Cell) {
+ FillRect(s, c, s.Bounds())
+}
+
+// ClearRect clears the rectangle within the cell buffer with blank cells.
+func ClearRect(s CellBuffer, rect Rectangle) {
+ FillRect(s, nil, rect)
+}
+
+// Clear clears the cell buffer with blank cells.
+func Clear(s CellBuffer) {
+ Fill(s, nil)
+}
+
+// SetContentRect clears the rectangle within the cell buffer with blank cells,
+// and sets the given string as its content. If the height or width of the
+// string exceeds the height or width of the cell buffer, it will be truncated.
+func SetContentRect(s CellBuffer, str string, rect Rectangle) {
+ // Replace all "\n" with "\r\n" to ensure the cursor is reset to the start
+ // of the line. Make sure we don't replace "\r\n" with "\r\r\n".
+ str = strings.ReplaceAll(str, "\r\n", "\n")
+ str = strings.ReplaceAll(str, "\n", "\r\n")
+ ClearRect(s, rect)
+ printString(s, ansi.GraphemeWidth, rect.Min.X, rect.Min.Y, rect, str, true, "")
+}
+
+// SetContent clears the cell buffer with blank cells, and sets the given string
+// as its content. If the height or width of the string exceeds the height or
+// width of the cell buffer, it will be truncated.
+func SetContent(s CellBuffer, str string) {
+ SetContentRect(s, str, s.Bounds())
+}
+
+// Render returns a string representation of the grid with ANSI escape sequences.
+func Render(d CellBuffer) string {
+ var buf bytes.Buffer
+ height := d.Bounds().Dy()
+ for y := 0; y < height; y++ {
+ _, line := RenderLine(d, y)
+ buf.WriteString(line)
+ if y < height-1 {
+ buf.WriteString("\r\n")
+ }
+ }
+ return buf.String()
+}
+
+// RenderLine returns a string representation of the yth line of the grid along
+// with the width of the line.
+func RenderLine(d CellBuffer, n int) (w int, line string) {
+ var pen Style
+ var link Link
+ var buf bytes.Buffer
+ var pendingLine string
+ var pendingWidth int // this ignores space cells until we hit a non-space cell
+
+ writePending := func() {
+ // If there's no pending line, we don't need to do anything.
+ if len(pendingLine) == 0 {
+ return
+ }
+ buf.WriteString(pendingLine)
+ w += pendingWidth
+ pendingWidth = 0
+ pendingLine = ""
+ }
+
+ for x := 0; x < d.Bounds().Dx(); x++ {
+ if cell := d.Cell(x, n); cell != nil && cell.Width > 0 {
+ // Convert the cell's style and link to the given color profile.
+ cellStyle := cell.Style
+ cellLink := cell.Link
+ if cellStyle.Empty() && !pen.Empty() {
+ writePending()
+ buf.WriteString(ansi.ResetStyle) //nolint:errcheck
+ pen.Reset()
+ }
+ if !cellStyle.Equal(&pen) {
+ writePending()
+ seq := cellStyle.DiffSequence(pen)
+ buf.WriteString(seq) // nolint:errcheck
+ pen = cellStyle
+ }
+
+ // Write the URL escape sequence
+ if cellLink != link && link.URL != "" {
+ writePending()
+ buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck
+ link.Reset()
+ }
+ if cellLink != link {
+ writePending()
+ buf.WriteString(ansi.SetHyperlink(cellLink.URL, cellLink.Params)) //nolint:errcheck
+ link = cellLink
+ }
+
+ // We only write the cell content if it's not empty. If it is, we
+ // append it to the pending line and width to be evaluated later.
+ if cell.Equal(&BlankCell) {
+ pendingLine += cell.String()
+ pendingWidth += cell.Width
+ } else {
+ writePending()
+ buf.WriteString(cell.String())
+ w += cell.Width
+ }
+ }
+ }
+ if link.URL != "" {
+ buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck
+ }
+ if !pen.Empty() {
+ buf.WriteString(ansi.ResetStyle) //nolint:errcheck
+ }
+ return w, strings.TrimRight(buf.String(), " ") // Trim trailing spaces
+}
+
+// ScreenWriter represents a writer that writes to a [Screen] parsing ANSI
+// escape sequences and Unicode characters and converting them into cells that
+// can be written to a cell [Buffer].
+type ScreenWriter struct {
+ *Screen
+}
+
+// NewScreenWriter creates a new ScreenWriter that writes to the given Screen.
+// This is a convenience function for creating a ScreenWriter.
+func NewScreenWriter(s *Screen) *ScreenWriter {
+ return &ScreenWriter{s}
+}
+
+// Write writes the given bytes to the screen.
+// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
+// sequences.
+func (s *ScreenWriter) Write(p []byte) (n int, err error) {
+ printString(s.Screen, s.method,
+ s.cur.X, s.cur.Y, s.Bounds(),
+ p, false, "")
+ return len(p), nil
+}
+
+// SetContent clears the screen with blank cells, and sets the given string as
+// its content. If the height or width of the string exceeds the height or
+// width of the screen, it will be truncated.
+//
+// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape sequences.
+func (s *ScreenWriter) SetContent(str string) {
+ s.SetContentRect(str, s.Bounds())
+}
+
+// SetContentRect clears the rectangle within the screen with blank cells, and
+// sets the given string as its content. If the height or width of the string
+// exceeds the height or width of the screen, it will be truncated.
+//
+// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
+// sequences.
+func (s *ScreenWriter) SetContentRect(str string, rect Rectangle) {
+ // Replace all "\n" with "\r\n" to ensure the cursor is reset to the start
+ // of the line. Make sure we don't replace "\r\n" with "\r\r\n".
+ str = strings.ReplaceAll(str, "\r\n", "\n")
+ str = strings.ReplaceAll(str, "\n", "\r\n")
+ s.ClearRect(rect)
+ printString(s.Screen, s.method,
+ rect.Min.X, rect.Min.Y, rect,
+ str, true, "")
+}
+
+// Print prints the string at the current cursor position. It will wrap the
+// string to the width of the screen if it exceeds the width of the screen.
+// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
+// sequences.
+func (s *ScreenWriter) Print(str string, v ...interface{}) {
+ if len(v) > 0 {
+ str = fmt.Sprintf(str, v...)
+ }
+ printString(s.Screen, s.method,
+ s.cur.X, s.cur.Y, s.Bounds(),
+ str, false, "")
+}
+
+// PrintAt prints the string at the given position. It will wrap the string to
+// the width of the screen if it exceeds the width of the screen.
+// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
+// sequences.
+func (s *ScreenWriter) PrintAt(x, y int, str string, v ...interface{}) {
+ if len(v) > 0 {
+ str = fmt.Sprintf(str, v...)
+ }
+ printString(s.Screen, s.method,
+ x, y, s.Bounds(),
+ str, false, "")
+}
+
+// PrintCrop prints the string at the current cursor position and truncates the
+// text if it exceeds the width of the screen. Use tail to specify a string to
+// append if the string is truncated.
+// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
+// sequences.
+func (s *ScreenWriter) PrintCrop(str string, tail string) {
+ printString(s.Screen, s.method,
+ s.cur.X, s.cur.Y, s.Bounds(),
+ str, true, tail)
+}
+
+// PrintCropAt prints the string at the given position and truncates the text
+// if it exceeds the width of the screen. Use tail to specify a string to append
+// if the string is truncated.
+// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
+// sequences.
+func (s *ScreenWriter) PrintCropAt(x, y int, str string, tail string) {
+ printString(s.Screen, s.method,
+ x, y, s.Bounds(),
+ str, true, tail)
+}
+
+// printString draws a string starting at the given position.
+func printString[T []byte | string](
+ s CellBuffer,
+ m ansi.Method,
+ x, y int,
+ bounds Rectangle, str T,
+ truncate bool, tail string,
+) {
+ p := ansi.GetParser()
+ defer ansi.PutParser(p)
+
+ var tailc Cell
+ if truncate && len(tail) > 0 {
+ if m == ansi.WcWidth {
+ tailc = *NewCellString(tail)
+ } else {
+ tailc = *NewGraphemeCell(tail)
+ }
+ }
+
+ decoder := ansi.DecodeSequenceWc[T]
+ if m == ansi.GraphemeWidth {
+ decoder = ansi.DecodeSequence[T]
+ }
+
+ var cell Cell
+ var style Style
+ var link Link
+ var state byte
+ for len(str) > 0 {
+ seq, width, n, newState := decoder(str, state, p)
+
+ switch width {
+ case 1, 2, 3, 4: // wide cells can go up to 4 cells wide
+ cell.Width += width
+ cell.Append([]rune(string(seq))...)
+
+ if !truncate && x+cell.Width > bounds.Max.X && y+1 < bounds.Max.Y {
+ // Wrap the string to the width of the window
+ x = bounds.Min.X
+ y++
+ }
+ if Pos(x, y).In(bounds) {
+ if truncate && tailc.Width > 0 && x+cell.Width > bounds.Max.X-tailc.Width {
+ // Truncate the string and append the tail if any.
+ cell := tailc
+ cell.Style = style
+ cell.Link = link
+ s.SetCell(x, y, &cell)
+ x += tailc.Width
+ } else {
+ // Print the cell to the screen
+ cell.Style = style
+ cell.Link = link
+ s.SetCell(x, y, &cell) //nolint:errcheck
+ x += width
+ }
+ }
+
+ // String is too long for the line, truncate it.
+ // Make sure we reset the cell for the next iteration.
+ cell.Reset()
+ default:
+ // Valid sequences always have a non-zero Cmd.
+ // TODO: Handle cursor movement and other sequences
+ switch {
+ case ansi.HasCsiPrefix(seq) && p.Command() == 'm':
+ // SGR - Select Graphic Rendition
+ ReadStyle(p.Params(), &style)
+ case ansi.HasOscPrefix(seq) && p.Command() == 8:
+ // Hyperlinks
+ ReadLink(p.Data(), &link)
+ case ansi.Equal(seq, T("\n")):
+ y++
+ case ansi.Equal(seq, T("\r")):
+ x = bounds.Min.X
+ default:
+ cell.Append([]rune(string(seq))...)
+ }
+ }
+
+ // Advance the state and data
+ state = newState
+ str = str[n:]
+ }
+
+ // Make sure to set the last cell if it's not empty.
+ if !cell.Empty() {
+ s.SetCell(x, y, &cell) //nolint:errcheck
+ cell.Reset()
+ }
+}
diff --git a/vendor/github.com/charmbracelet/x/exp/slice/LICENSE b/vendor/github.com/charmbracelet/x/exp/slice/LICENSE
new file mode 100644
index 00000000..65a5654e
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/exp/slice/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Charmbracelet, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/charmbracelet/x/exp/slice/slice.go b/vendor/github.com/charmbracelet/x/exp/slice/slice.go
new file mode 100644
index 00000000..b909f090
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/exp/slice/slice.go
@@ -0,0 +1,37 @@
+package slice
+
+// GroupBy groups a slice of items by a key function.
+func GroupBy[T any, K comparable](list []T, key func(T) K) map[K][]T {
+ groups := make(map[K][]T)
+
+ for _, item := range list {
+ k := key(item)
+ groups[k] = append(groups[k], item)
+ }
+
+ return groups
+}
+
+// Take returns the first n elements of the given slice. If there are not
+// enough elements in the slice, the whole slice is returned.
+func Take[A any](slice []A, n int) []A {
+ if n > len(slice) {
+ return slice
+ }
+ return slice[:n]
+}
+
+// Uniq returns a new slice with all duplicates removed.
+func Uniq[T comparable](list []T) []T {
+ seen := make(map[T]struct{}, len(list))
+ uniqList := make([]T, 0, len(list))
+
+ for _, item := range list {
+ if _, ok := seen[item]; !ok {
+ seen[item] = struct{}{}
+ uniqList = append(uniqList, item)
+ }
+ }
+
+ return uniqList
+}
diff --git a/vendor/github.com/charmbracelet/x/term/LICENSE b/vendor/github.com/charmbracelet/x/term/LICENSE
new file mode 100644
index 00000000..65a5654e
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/term/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Charmbracelet, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/charmbracelet/x/term/term.go b/vendor/github.com/charmbracelet/x/term/term.go
new file mode 100644
index 00000000..58d6522c
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/term/term.go
@@ -0,0 +1,49 @@
+package term
+
+// State contains platform-specific state of a terminal.
+type State struct {
+ state
+}
+
+// IsTerminal returns whether the given file descriptor is a terminal.
+func IsTerminal(fd uintptr) bool {
+ return isTerminal(fd)
+}
+
+// MakeRaw puts the terminal connected to the given file descriptor into raw
+// mode and returns the previous state of the terminal so that it can be
+// restored.
+func MakeRaw(fd uintptr) (*State, error) {
+ return makeRaw(fd)
+}
+
+// GetState returns the current state of a terminal which may be useful to
+// restore the terminal after a signal.
+func GetState(fd uintptr) (*State, error) {
+ return getState(fd)
+}
+
+// SetState sets the given state of the terminal.
+func SetState(fd uintptr, state *State) error {
+ return setState(fd, state)
+}
+
+// Restore restores the terminal connected to the given file descriptor to a
+// previous state.
+func Restore(fd uintptr, oldState *State) error {
+ return restore(fd, oldState)
+}
+
+// GetSize returns the visible dimensions of the given terminal.
+//
+// These dimensions don't include any scrollback buffer height.
+func GetSize(fd uintptr) (width, height int, err error) {
+ return getSize(fd)
+}
+
+// ReadPassword reads a line of input from a terminal without local echo. This
+// is commonly used for inputting passwords and other sensitive data. The slice
+// returned does not include the \n.
+func ReadPassword(fd uintptr) ([]byte, error) {
+ return readPassword(fd)
+}
diff --git a/vendor/github.com/charmbracelet/x/term/term_other.go b/vendor/github.com/charmbracelet/x/term/term_other.go
new file mode 100644
index 00000000..092c7e9d
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/term/term_other.go
@@ -0,0 +1,39 @@
+//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !zos && !windows && !solaris && !plan9
+// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!zos,!windows,!solaris,!plan9
+
+package term
+
+import (
+ "fmt"
+ "runtime"
+)
+
+type state struct{}
+
+func isTerminal(fd uintptr) bool {
+ return false
+}
+
+func makeRaw(fd uintptr) (*State, error) {
+ return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+}
+
+func getState(fd uintptr) (*State, error) {
+ return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+}
+
+func restore(fd uintptr, state *State) error {
+ return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+}
+
+func getSize(fd uintptr) (width, height int, err error) {
+ return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+}
+
+func setState(fd uintptr, state *State) error {
+ return fmt.Errorf("terminal: SetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+}
+
+func readPassword(fd uintptr) ([]byte, error) {
+ return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+}
diff --git a/vendor/github.com/charmbracelet/x/term/term_unix.go b/vendor/github.com/charmbracelet/x/term/term_unix.go
new file mode 100644
index 00000000..1459cb1b
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/term/term_unix.go
@@ -0,0 +1,96 @@
+//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
+// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
+
+package term
+
+import (
+ "golang.org/x/sys/unix"
+)
+
+type state struct {
+ unix.Termios
+}
+
+func isTerminal(fd uintptr) bool {
+ _, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
+ return err == nil
+}
+
+func makeRaw(fd uintptr) (*State, error) {
+ termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
+ if err != nil {
+ return nil, err
+ }
+
+ oldState := State{state{Termios: *termios}}
+
+ // This attempts to replicate the behaviour documented for cfmakeraw in
+ // the termios(3) manpage.
+ termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
+ termios.Oflag &^= unix.OPOST
+ termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
+ termios.Cflag &^= unix.CSIZE | unix.PARENB
+ termios.Cflag |= unix.CS8
+ termios.Cc[unix.VMIN] = 1
+ termios.Cc[unix.VTIME] = 0
+ if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios); err != nil {
+ return nil, err
+ }
+
+ return &oldState, nil
+}
+
+func setState(fd uintptr, state *State) error {
+ var termios *unix.Termios
+ if state != nil {
+ termios = &state.Termios
+ }
+ return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios)
+}
+
+func getState(fd uintptr) (*State, error) {
+ termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
+ if err != nil {
+ return nil, err
+ }
+
+ return &State{state{Termios: *termios}}, nil
+}
+
+func restore(fd uintptr, state *State) error {
+ return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &state.Termios)
+}
+
+func getSize(fd uintptr) (width, height int, err error) {
+ ws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
+ if err != nil {
+ return 0, 0, err
+ }
+ return int(ws.Col), int(ws.Row), nil
+}
+
+// passwordReader is an io.Reader that reads from a specific file descriptor.
+type passwordReader int
+
+func (r passwordReader) Read(buf []byte) (int, error) {
+ return unix.Read(int(r), buf)
+}
+
+func readPassword(fd uintptr) ([]byte, error) {
+ termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
+ if err != nil {
+ return nil, err
+ }
+
+ newState := *termios
+ newState.Lflag &^= unix.ECHO
+ newState.Lflag |= unix.ICANON | unix.ISIG
+ newState.Iflag |= unix.ICRNL
+ if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &newState); err != nil {
+ return nil, err
+ }
+
+ defer unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios)
+
+ return readPasswordLine(passwordReader(fd))
+}
diff --git a/vendor/github.com/charmbracelet/x/term/term_unix_bsd.go b/vendor/github.com/charmbracelet/x/term/term_unix_bsd.go
new file mode 100644
index 00000000..b435031a
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/term/term_unix_bsd.go
@@ -0,0 +1,11 @@
+//go:build darwin || dragonfly || freebsd || netbsd || openbsd
+// +build darwin dragonfly freebsd netbsd openbsd
+
+package term
+
+import "golang.org/x/sys/unix"
+
+const (
+ ioctlReadTermios = unix.TIOCGETA
+ ioctlWriteTermios = unix.TIOCSETA
+)
diff --git a/vendor/github.com/charmbracelet/x/term/term_unix_other.go b/vendor/github.com/charmbracelet/x/term/term_unix_other.go
new file mode 100644
index 00000000..ee2a29eb
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/term/term_unix_other.go
@@ -0,0 +1,11 @@
+//go:build aix || linux || solaris || zos
+// +build aix linux solaris zos
+
+package term
+
+import "golang.org/x/sys/unix"
+
+const (
+ ioctlReadTermios = unix.TCGETS
+ ioctlWriteTermios = unix.TCSETS
+)
diff --git a/vendor/github.com/charmbracelet/x/term/term_windows.go b/vendor/github.com/charmbracelet/x/term/term_windows.go
new file mode 100644
index 00000000..fe7afdec
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/term/term_windows.go
@@ -0,0 +1,87 @@
+//go:build windows
+// +build windows
+
+package term
+
+import (
+ "os"
+
+ "golang.org/x/sys/windows"
+)
+
+type state struct {
+ Mode uint32
+}
+
+func isTerminal(fd uintptr) bool {
+ var st uint32
+ err := windows.GetConsoleMode(windows.Handle(fd), &st)
+ return err == nil
+}
+
+func makeRaw(fd uintptr) (*State, error) {
+ var st uint32
+ if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
+ return nil, err
+ }
+ raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
+ raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
+ if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
+ return nil, err
+ }
+ return &State{state{st}}, nil
+}
+
+func setState(fd uintptr, state *State) error {
+ var mode uint32
+ if state != nil {
+ mode = state.Mode
+ }
+ return windows.SetConsoleMode(windows.Handle(fd), mode)
+}
+
+func getState(fd uintptr) (*State, error) {
+ var st uint32
+ if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
+ return nil, err
+ }
+ return &State{state{st}}, nil
+}
+
+func restore(fd uintptr, state *State) error {
+ return windows.SetConsoleMode(windows.Handle(fd), state.Mode)
+}
+
+func getSize(fd uintptr) (width, height int, err error) {
+ var info windows.ConsoleScreenBufferInfo
+ if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil {
+ return 0, 0, err
+ }
+ return int(info.Window.Right - info.Window.Left + 1), int(info.Window.Bottom - info.Window.Top + 1), nil
+}
+
+func readPassword(fd uintptr) ([]byte, error) {
+ var st uint32
+ if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
+ return nil, err
+ }
+ old := st
+
+ st &^= (windows.ENABLE_ECHO_INPUT | windows.ENABLE_LINE_INPUT)
+ st |= (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_PROCESSED_INPUT)
+ if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil {
+ return nil, err
+ }
+
+ defer windows.SetConsoleMode(windows.Handle(fd), old)
+
+ var h windows.Handle
+ p, _ := windows.GetCurrentProcess()
+ if err := windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil {
+ return nil, err
+ }
+
+ f := os.NewFile(uintptr(h), "stdin")
+ defer f.Close()
+ return readPasswordLine(f)
+}
diff --git a/vendor/github.com/charmbracelet/x/term/terminal.go b/vendor/github.com/charmbracelet/x/term/terminal.go
new file mode 100644
index 00000000..8963163f
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/term/terminal.go
@@ -0,0 +1,12 @@
+package term
+
+import (
+ "io"
+)
+
+// File represents a file that has a file descriptor and can be read from,
+// written to, and closed.
+type File interface {
+ io.ReadWriteCloser
+ Fd() uintptr
+}
diff --git a/vendor/github.com/charmbracelet/x/term/util.go b/vendor/github.com/charmbracelet/x/term/util.go
new file mode 100644
index 00000000..b7313418
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/term/util.go
@@ -0,0 +1,47 @@
+package term
+
+import (
+ "io"
+ "runtime"
+)
+
+// readPasswordLine reads from reader until it finds \n or io.EOF.
+// The slice returned does not include the \n.
+// readPasswordLine also ignores any \r it finds.
+// Windows uses \r as end of line. So, on Windows, readPasswordLine
+// reads until it finds \r and ignores any \n it finds during processing.
+func readPasswordLine(reader io.Reader) ([]byte, error) {
+ var buf [1]byte
+ var ret []byte
+
+ for {
+ n, err := reader.Read(buf[:])
+ if n > 0 {
+ switch buf[0] {
+ case '\b':
+ if len(ret) > 0 {
+ ret = ret[:len(ret)-1]
+ }
+ case '\n':
+ if runtime.GOOS != "windows" {
+ return ret, nil
+ }
+ // otherwise ignore \n
+ case '\r':
+ if runtime.GOOS == "windows" {
+ return ret, nil
+ }
+ // otherwise ignore \r
+ default:
+ ret = append(ret, buf[0])
+ }
+ continue
+ }
+ if err != nil {
+ if err == io.EOF && len(ret) > 0 {
+ return ret, nil
+ }
+ return ret, err
+ }
+ }
+}
diff --git a/vendor/github.com/dlclark/regexp2/.gitignore b/vendor/github.com/dlclark/regexp2/.gitignore
new file mode 100644
index 00000000..fb844c33
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/.gitignore
@@ -0,0 +1,27 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+*.out
+
+.DS_Store
diff --git a/vendor/github.com/dlclark/regexp2/.travis.yml b/vendor/github.com/dlclark/regexp2/.travis.yml
new file mode 100644
index 00000000..a2da6be4
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/.travis.yml
@@ -0,0 +1,7 @@
+language: go
+arch:
+ - AMD64
+ - ppc64le
+go:
+ - 1.9
+ - tip
diff --git a/vendor/github.com/dlclark/regexp2/ATTRIB b/vendor/github.com/dlclark/regexp2/ATTRIB
new file mode 100644
index 00000000..cdf4560b
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/ATTRIB
@@ -0,0 +1,133 @@
+============
+These pieces of code were ported from dotnet/corefx:
+
+syntax/charclass.go (from RegexCharClass.cs): ported to use the built-in Go unicode classes. Canonicalize is
+ a direct port, but most of the other code required large changes because the C# implementation
+ used a string to represent the CharSet data structure and I cleaned that up in my implementation.
+
+syntax/code.go (from RegexCode.cs): ported literally with various cleanups and layout to make it more Go-ish.
+
+syntax/escape.go (from RegexParser.cs): ported Escape method and added some optimizations. Unescape is inspired by
+ the C# implementation but couldn't be directly ported because of the lack of do-while syntax in Go.
+
+syntax/parser.go (from RegexpParser.cs and RegexOptions.cs): ported parser struct and associated methods as
+ literally as possible. Several language differences required changes. E.g. lack pre/post-fix increments as
+ expressions, lack of do-while loops, lack of overloads, etc.
+
+syntax/prefix.go (from RegexFCD.cs and RegexBoyerMoore.cs): ported as literally as possible and added support
+ for unicode chars that are longer than the 16-bit char in C# for the 32-bit rune in Go.
+
+syntax/replacerdata.go (from RegexReplacement.cs): conceptually ported and re-organized to handle differences
+ in charclass implementation, and fix odd code layout between RegexParser.cs, Regex.cs, and RegexReplacement.cs.
+
+syntax/tree.go (from RegexTree.cs and RegexNode.cs): ported literally as possible.
+
+syntax/writer.go (from RegexWriter.cs): ported literally with minor changes to make it more Go-ish.
+
+match.go (from RegexMatch.cs): ported, simplified, and changed to handle Go's lack of inheritence.
+
+regexp.go (from Regex.cs and RegexOptions.cs): conceptually serves the same "starting point", but is simplified
+ and changed to handle differences in C# strings and Go strings/runes.
+
+replace.go (from RegexReplacement.cs): ported closely and then cleaned up to combine the MatchEvaluator and
+ simple string replace implementations.
+
+runner.go (from RegexRunner.cs): ported literally as possible.
+
+regexp_test.go (from CaptureTests.cs and GroupNamesAndNumbers.cs): conceptually ported, but the code was
+ manually structured like Go tests.
+
+replace_test.go (from RegexReplaceStringTest0.cs): conceptually ported
+
+rtl_test.go (from RightToLeft.cs): conceptually ported
+---
+dotnet/corefx was released under this license:
+
+The MIT License (MIT)
+
+Copyright (c) Microsoft Corporation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+============
+These pieces of code are copied from the Go framework:
+
+- The overall directory structure of regexp2 was inspired by the Go runtime regexp package.
+- The optimization in the escape method of syntax/escape.go is from the Go runtime QuoteMeta() func in regexp/regexp.go
+- The method signatures in regexp.go are designed to match the Go framework regexp methods closely
+- func regexp2.MustCompile and func quote are almost identifical to the regexp package versions
+- BenchmarkMatch* and TestProgramTooLong* funcs in regexp_performance_test.go were copied from the framework
+ regexp/exec_test.go
+---
+The Go framework was released under this license:
+
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+============
+Some test data were gathered from the Mono project.
+
+regexp_mono_test.go: ported from https://github.com/mono/mono/blob/master/mcs/class/System/Test/System.Text.RegularExpressions/PerlTrials.cs
+---
+Mono tests released under this license:
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/vendor/github.com/dlclark/regexp2/LICENSE b/vendor/github.com/dlclark/regexp2/LICENSE
new file mode 100644
index 00000000..fe83dfdc
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) Doug Clark
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/dlclark/regexp2/README.md b/vendor/github.com/dlclark/regexp2/README.md
new file mode 100644
index 00000000..f3d1bd92
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/README.md
@@ -0,0 +1,167 @@
+# regexp2 - full featured regular expressions for Go
+Regexp2 is a feature-rich RegExp engine for Go. It doesn't have constant time guarantees like the built-in `regexp` package, but it allows backtracking and is compatible with Perl5 and .NET. You'll likely be better off with the RE2 engine from the `regexp` package and should only use this if you need to write very complex patterns or require compatibility with .NET.
+
+## Basis of the engine
+The engine is ported from the .NET framework's System.Text.RegularExpressions.Regex engine. That engine was open sourced in 2015 under the MIT license. There are some fundamental differences between .NET strings and Go strings that required a bit of borrowing from the Go framework regex engine as well. I cleaned up a couple of the dirtier bits during the port (regexcharclass.cs was terrible), but the parse tree, code emmitted, and therefore patterns matched should be identical.
+
+## Installing
+This is a go-gettable library, so install is easy:
+
+ go get github.com/dlclark/regexp2/...
+
+## Usage
+Usage is similar to the Go `regexp` package. Just like in `regexp`, you start by converting a regex into a state machine via the `Compile` or `MustCompile` methods. They ultimately do the same thing, but `MustCompile` will panic if the regex is invalid. You can then use the provided `Regexp` struct to find matches repeatedly. A `Regexp` struct is safe to use across goroutines.
+
+```go
+re := regexp2.MustCompile(`Your pattern`, 0)
+if isMatch, _ := re.MatchString(`Something to match`); isMatch {
+ //do something
+}
+```
+
+The only error that the `*Match*` methods *should* return is a Timeout if you set the `re.MatchTimeout` field. Any other error is a bug in the `regexp2` package. If you need more details about capture groups in a match then use the `FindStringMatch` method, like so:
+
+```go
+if m, _ := re.FindStringMatch(`Something to match`); m != nil {
+ // the whole match is always group 0
+ fmt.Printf("Group 0: %v\n", m.String())
+
+ // you can get all the groups too
+ gps := m.Groups()
+
+ // a group can be captured multiple times, so each cap is separately addressable
+ fmt.Printf("Group 1, first capture", gps[1].Captures[0].String())
+ fmt.Printf("Group 1, second capture", gps[1].Captures[1].String())
+}
+```
+
+Group 0 is embedded in the Match. Group 0 is an automatically-assigned group that encompasses the whole pattern. This means that `m.String()` is the same as `m.Group.String()` and `m.Groups()[0].String()`
+
+The __last__ capture is embedded in each group, so `g.String()` will return the same thing as `g.Capture.String()` and `g.Captures[len(g.Captures)-1].String()`.
+
+If you want to find multiple matches from a single input string you should use the `FindNextMatch` method. For example, to implement a function similar to `regexp.FindAllString`:
+
+```go
+func regexp2FindAllString(re *regexp2.Regexp, s string) []string {
+ var matches []string
+ m, _ := re.FindStringMatch(s)
+ for m != nil {
+ matches = append(matches, m.String())
+ m, _ = re.FindNextMatch(m)
+ }
+ return matches
+}
+```
+
+`FindNextMatch` is optmized so that it re-uses the underlying string/rune slice.
+
+The internals of `regexp2` always operate on `[]rune` so `Index` and `Length` data in a `Match` always reference a position in `rune`s rather than `byte`s (even if the input was given as a string). This is a dramatic difference between `regexp` and `regexp2`. It's advisable to use the provided `String()` methods to avoid having to work with indices.
+
+## Compare `regexp` and `regexp2`
+| Category | regexp | regexp2 |
+| --- | --- | --- |
+| Catastrophic backtracking possible | no, constant execution time guarantees | yes, if your pattern is at risk you can use the `re.MatchTimeout` field |
+| Python-style capture groups `(?Pre)` | yes | no (yes in RE2 compat mode) |
+| .NET-style capture groups `(?re)` or `(?'name're)` | no | yes |
+| comments `(?#comment)` | no | yes |
+| branch numbering reset `(?\|a\|b)` | no | no |
+| possessive match `(?>re)` | no | yes |
+| positive lookahead `(?=re)` | no | yes |
+| negative lookahead `(?!re)` | no | yes |
+| positive lookbehind `(?<=re)` | no | yes |
+| negative lookbehind `(?re)`)
+* change singleline behavior for `$` to only match end of string (like RE2) (see [#24](https://github.com/dlclark/regexp2/issues/24))
+* change the character classes `\d` `\s` and `\w` to match the same characters as RE2. NOTE: if you also use the `ECMAScript` option then this will change the `\s` character class to match ECMAScript instead of RE2. ECMAScript allows more whitespace characters in `\s` than RE2 (but still fewer than the the default behavior).
+* allow character escape sequences to have defaults. For example, by default `\_` isn't a known character escape and will fail to compile, but in RE2 mode it will match the literal character `_`
+
+```go
+re := regexp2.MustCompile(`Your RE2-compatible pattern`, regexp2.RE2)
+if isMatch, _ := re.MatchString(`Something to match`); isMatch {
+ //do something
+}
+```
+
+This feature is a work in progress and I'm open to ideas for more things to put here (maybe more relaxed character escaping rules?).
+
+## Catastrophic Backtracking and Timeouts
+
+`regexp2` supports features that can lead to catastrophic backtracking.
+`Regexp.MatchTimeout` can be set to to limit the impact of such behavior; the
+match will fail with an error after approximately MatchTimeout. No timeout
+checks are done by default.
+
+Timeout checking is not free. The current timeout checking implementation starts
+a background worker that updates a clock value approximately once every 100
+milliseconds. The matching code compares this value against the precomputed
+deadline for the match. The performance impact is as follows.
+
+1. A match with a timeout runs almost as fast as a match without a timeout.
+2. If any live matches have a timeout, there will be a background CPU load
+ (`~0.15%` currently on a modern machine). This load will remain constant
+ regardless of the number of matches done including matches done in parallel.
+3. If no live matches are using a timeout, the background load will remain
+ until the longest deadline (match timeout + the time when the match started)
+ is reached. E.g., if you set a timeout of one minute the load will persist
+ for approximately a minute even if the match finishes quickly.
+
+See [PR #58](https://github.com/dlclark/regexp2/pull/58) for more details and
+alternatives considered.
+
+## Goroutine leak error
+If you're using a library during unit tests (e.g. https://github.com/uber-go/goleak) that validates all goroutines are exited then you'll likely get an error if you or any of your dependencies use regex's with a MatchTimeout.
+To remedy the problem you'll need to tell the unit test to wait until the backgroup timeout goroutine is exited.
+
+```go
+func TestSomething(t *testing.T) {
+ defer goleak.VerifyNone(t)
+ defer regexp2.StopTimeoutClock()
+
+ // ... test
+}
+
+//or
+
+func TestMain(m *testing.M) {
+ // setup
+ // ...
+
+ // run
+ m.Run()
+
+ //tear down
+ regexp2.StopTimeoutClock()
+ goleak.VerifyNone(t)
+}
+```
+
+This will add ~100ms runtime to each test (or TestMain). If that's too much time you can set the clock cycle rate of the timeout goroutine in an init function in a test file. `regexp2.SetTimeoutCheckPeriod` isn't threadsafe so it must be setup before starting any regex's with Timeouts.
+
+```go
+func init() {
+ //speed up testing by making the timeout clock 1ms
+ regexp2.SetTimeoutCheckPeriod(time.Millisecond)
+}
+```
+
+## ECMAScript compatibility mode
+In this mode the engine provides compatibility with the [regex engine](https://tc39.es/ecma262/multipage/text-processing.html#sec-regexp-regular-expression-objects) described in the ECMAScript specification.
+
+Additionally a Unicode mode is provided which allows parsing of `\u{CodePoint}` syntax that is only when both are provided.
+
+## Library features that I'm still working on
+- Regex split
+
+## Potential bugs
+I've run a battery of tests against regexp2 from various sources and found the debug output matches the .NET engine, but .NET and Go handle strings very differently. I've attempted to handle these differences, but most of my testing deals with basic ASCII with a little bit of multi-byte Unicode. There's a chance that there are bugs in the string handling related to character sets with supplementary Unicode chars. Right-to-Left support is coded, but not well tested either.
+
+## Find a bug?
+I'm open to new issues and pull requests with tests if you find something odd!
diff --git a/vendor/github.com/dlclark/regexp2/fastclock.go b/vendor/github.com/dlclark/regexp2/fastclock.go
new file mode 100644
index 00000000..caf2c9d8
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/fastclock.go
@@ -0,0 +1,129 @@
+package regexp2
+
+import (
+ "sync"
+ "sync/atomic"
+ "time"
+)
+
+// fasttime holds a time value (ticks since clock initialization)
+type fasttime int64
+
+// fastclock provides a fast clock implementation.
+//
+// A background goroutine periodically stores the current time
+// into an atomic variable.
+//
+// A deadline can be quickly checked for expiration by comparing
+// its value to the clock stored in the atomic variable.
+//
+// The goroutine automatically stops once clockEnd is reached.
+// (clockEnd covers the largest deadline seen so far + some
+// extra time). This ensures that if regexp2 with timeouts
+// stops being used we will stop background work.
+type fastclock struct {
+ // instances of atomicTime must be at the start of the struct (or at least 64-bit aligned)
+ // otherwise 32-bit architectures will panic
+
+ current atomicTime // Current time (approximate)
+ clockEnd atomicTime // When clock updater is supposed to stop (>= any existing deadline)
+
+ // current and clockEnd can be read via atomic loads.
+ // Reads and writes of other fields require mu to be held.
+ mu sync.Mutex
+ start time.Time // Time corresponding to fasttime(0)
+ running bool // Is a clock updater running?
+}
+
+var fast fastclock
+
+// reached returns true if current time is at or past t.
+func (t fasttime) reached() bool {
+ return fast.current.read() >= t
+}
+
+// makeDeadline returns a time that is approximately time.Now().Add(d)
+func makeDeadline(d time.Duration) fasttime {
+ // Increase the deadline since the clock we are reading may be
+ // just about to tick forwards.
+ end := fast.current.read() + durationToTicks(d+clockPeriod)
+
+ // Start or extend clock if necessary.
+ if end > fast.clockEnd.read() {
+ extendClock(end)
+ }
+ return end
+}
+
+// extendClock ensures that clock is live and will run until at least end.
+func extendClock(end fasttime) {
+ fast.mu.Lock()
+ defer fast.mu.Unlock()
+
+ if fast.start.IsZero() {
+ fast.start = time.Now()
+ }
+
+ // Extend the running time to cover end as well as a bit of slop.
+ if shutdown := end + durationToTicks(time.Second); shutdown > fast.clockEnd.read() {
+ fast.clockEnd.write(shutdown)
+ }
+
+ // Start clock if necessary
+ if !fast.running {
+ fast.running = true
+ go runClock()
+ }
+}
+
+// stop the timeout clock in the background
+// should only used for unit tests to abandon the background goroutine
+func stopClock() {
+ fast.mu.Lock()
+ if fast.running {
+ fast.clockEnd.write(fasttime(0))
+ }
+ fast.mu.Unlock()
+
+ // pause until not running
+ // get and release the lock
+ isRunning := true
+ for isRunning {
+ time.Sleep(clockPeriod / 2)
+ fast.mu.Lock()
+ isRunning = fast.running
+ fast.mu.Unlock()
+ }
+}
+
+func durationToTicks(d time.Duration) fasttime {
+ // Downscale nanoseconds to approximately a millisecond so that we can avoid
+ // overflow even if the caller passes in math.MaxInt64.
+ return fasttime(d) >> 20
+}
+
+const DefaultClockPeriod = 100 * time.Millisecond
+
+// clockPeriod is the approximate interval between updates of approximateClock.
+var clockPeriod = DefaultClockPeriod
+
+func runClock() {
+ fast.mu.Lock()
+ defer fast.mu.Unlock()
+
+ for fast.current.read() <= fast.clockEnd.read() {
+ // Unlock while sleeping.
+ fast.mu.Unlock()
+ time.Sleep(clockPeriod)
+ fast.mu.Lock()
+
+ newTime := durationToTicks(time.Since(fast.start))
+ fast.current.write(newTime)
+ }
+ fast.running = false
+}
+
+type atomicTime struct{ v int64 } // Should change to atomic.Int64 when we can use go 1.19
+
+func (t *atomicTime) read() fasttime { return fasttime(atomic.LoadInt64(&t.v)) }
+func (t *atomicTime) write(v fasttime) { atomic.StoreInt64(&t.v, int64(v)) }
diff --git a/vendor/github.com/dlclark/regexp2/match.go b/vendor/github.com/dlclark/regexp2/match.go
new file mode 100644
index 00000000..1871cffe
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/match.go
@@ -0,0 +1,347 @@
+package regexp2
+
+import (
+ "bytes"
+ "fmt"
+)
+
+// Match is a single regex result match that contains groups and repeated captures
+// -Groups
+// -Capture
+type Match struct {
+ Group //embeded group 0
+
+ regex *Regexp
+ otherGroups []Group
+
+ // input to the match
+ textpos int
+ textstart int
+
+ capcount int
+ caps []int
+ sparseCaps map[int]int
+
+ // output from the match
+ matches [][]int
+ matchcount []int
+
+ // whether we've done any balancing with this match. If we
+ // have done balancing, we'll need to do extra work in Tidy().
+ balancing bool
+}
+
+// Group is an explicit or implit (group 0) matched group within the pattern
+type Group struct {
+ Capture // the last capture of this group is embeded for ease of use
+
+ Name string // group name
+ Captures []Capture // captures of this group
+}
+
+// Capture is a single capture of text within the larger original string
+type Capture struct {
+ // the original string
+ text []rune
+ // the position in the original string where the first character of
+ // captured substring was found.
+ Index int
+ // the length of the captured substring.
+ Length int
+}
+
+// String returns the captured text as a String
+func (c *Capture) String() string {
+ return string(c.text[c.Index : c.Index+c.Length])
+}
+
+// Runes returns the captured text as a rune slice
+func (c *Capture) Runes() []rune {
+ return c.text[c.Index : c.Index+c.Length]
+}
+
+func newMatch(regex *Regexp, capcount int, text []rune, startpos int) *Match {
+ m := Match{
+ regex: regex,
+ matchcount: make([]int, capcount),
+ matches: make([][]int, capcount),
+ textstart: startpos,
+ balancing: false,
+ }
+ m.Name = "0"
+ m.text = text
+ m.matches[0] = make([]int, 2)
+ return &m
+}
+
+func newMatchSparse(regex *Regexp, caps map[int]int, capcount int, text []rune, startpos int) *Match {
+ m := newMatch(regex, capcount, text, startpos)
+ m.sparseCaps = caps
+ return m
+}
+
+func (m *Match) reset(text []rune, textstart int) {
+ m.text = text
+ m.textstart = textstart
+ for i := 0; i < len(m.matchcount); i++ {
+ m.matchcount[i] = 0
+ }
+ m.balancing = false
+}
+
+func (m *Match) tidy(textpos int) {
+
+ interval := m.matches[0]
+ m.Index = interval[0]
+ m.Length = interval[1]
+ m.textpos = textpos
+ m.capcount = m.matchcount[0]
+ //copy our root capture to the list
+ m.Group.Captures = []Capture{m.Group.Capture}
+
+ if m.balancing {
+ // The idea here is that we want to compact all of our unbalanced captures. To do that we
+ // use j basically as a count of how many unbalanced captures we have at any given time
+ // (really j is an index, but j/2 is the count). First we skip past all of the real captures
+ // until we find a balance captures. Then we check each subsequent entry. If it's a balance
+ // capture (it's negative), we decrement j. If it's a real capture, we increment j and copy
+ // it down to the last free position.
+ for cap := 0; cap < len(m.matchcount); cap++ {
+ limit := m.matchcount[cap] * 2
+ matcharray := m.matches[cap]
+
+ var i, j int
+
+ for i = 0; i < limit; i++ {
+ if matcharray[i] < 0 {
+ break
+ }
+ }
+
+ for j = i; i < limit; i++ {
+ if matcharray[i] < 0 {
+ // skip negative values
+ j--
+ } else {
+ // but if we find something positive (an actual capture), copy it back to the last
+ // unbalanced position.
+ if i != j {
+ matcharray[j] = matcharray[i]
+ }
+ j++
+ }
+ }
+
+ m.matchcount[cap] = j / 2
+ }
+
+ m.balancing = false
+ }
+}
+
+// isMatched tells if a group was matched by capnum
+func (m *Match) isMatched(cap int) bool {
+ return cap < len(m.matchcount) && m.matchcount[cap] > 0 && m.matches[cap][m.matchcount[cap]*2-1] != (-3+1)
+}
+
+// matchIndex returns the index of the last specified matched group by capnum
+func (m *Match) matchIndex(cap int) int {
+ i := m.matches[cap][m.matchcount[cap]*2-2]
+ if i >= 0 {
+ return i
+ }
+
+ return m.matches[cap][-3-i]
+}
+
+// matchLength returns the length of the last specified matched group by capnum
+func (m *Match) matchLength(cap int) int {
+ i := m.matches[cap][m.matchcount[cap]*2-1]
+ if i >= 0 {
+ return i
+ }
+
+ return m.matches[cap][-3-i]
+}
+
+// Nonpublic builder: add a capture to the group specified by "c"
+func (m *Match) addMatch(c, start, l int) {
+
+ if m.matches[c] == nil {
+ m.matches[c] = make([]int, 2)
+ }
+
+ capcount := m.matchcount[c]
+
+ if capcount*2+2 > len(m.matches[c]) {
+ oldmatches := m.matches[c]
+ newmatches := make([]int, capcount*8)
+ copy(newmatches, oldmatches[:capcount*2])
+ m.matches[c] = newmatches
+ }
+
+ m.matches[c][capcount*2] = start
+ m.matches[c][capcount*2+1] = l
+ m.matchcount[c] = capcount + 1
+ //log.Printf("addMatch: c=%v, i=%v, l=%v ... matches: %v", c, start, l, m.matches)
+}
+
+// Nonpublic builder: Add a capture to balance the specified group. This is used by the
+// balanced match construct. (?...)
+//
+// If there were no such thing as backtracking, this would be as simple as calling RemoveMatch(c).
+// However, since we have backtracking, we need to keep track of everything.
+func (m *Match) balanceMatch(c int) {
+ m.balancing = true
+
+ // we'll look at the last capture first
+ capcount := m.matchcount[c]
+ target := capcount*2 - 2
+
+ // first see if it is negative, and therefore is a reference to the next available
+ // capture group for balancing. If it is, we'll reset target to point to that capture.
+ if m.matches[c][target] < 0 {
+ target = -3 - m.matches[c][target]
+ }
+
+ // move back to the previous capture
+ target -= 2
+
+ // if the previous capture is a reference, just copy that reference to the end. Otherwise, point to it.
+ if target >= 0 && m.matches[c][target] < 0 {
+ m.addMatch(c, m.matches[c][target], m.matches[c][target+1])
+ } else {
+ m.addMatch(c, -3-target, -4-target /* == -3 - (target + 1) */)
+ }
+}
+
+// Nonpublic builder: removes a group match by capnum
+func (m *Match) removeMatch(c int) {
+ m.matchcount[c]--
+}
+
+// GroupCount returns the number of groups this match has matched
+func (m *Match) GroupCount() int {
+ return len(m.matchcount)
+}
+
+// GroupByName returns a group based on the name of the group, or nil if the group name does not exist
+func (m *Match) GroupByName(name string) *Group {
+ num := m.regex.GroupNumberFromName(name)
+ if num < 0 {
+ return nil
+ }
+ return m.GroupByNumber(num)
+}
+
+// GroupByNumber returns a group based on the number of the group, or nil if the group number does not exist
+func (m *Match) GroupByNumber(num int) *Group {
+ // check our sparse map
+ if m.sparseCaps != nil {
+ if newNum, ok := m.sparseCaps[num]; ok {
+ num = newNum
+ }
+ }
+ if num >= len(m.matchcount) || num < 0 {
+ return nil
+ }
+
+ if num == 0 {
+ return &m.Group
+ }
+
+ m.populateOtherGroups()
+
+ return &m.otherGroups[num-1]
+}
+
+// Groups returns all the capture groups, starting with group 0 (the full match)
+func (m *Match) Groups() []Group {
+ m.populateOtherGroups()
+ g := make([]Group, len(m.otherGroups)+1)
+ g[0] = m.Group
+ copy(g[1:], m.otherGroups)
+ return g
+}
+
+func (m *Match) populateOtherGroups() {
+ // Construct all the Group objects first time called
+ if m.otherGroups == nil {
+ m.otherGroups = make([]Group, len(m.matchcount)-1)
+ for i := 0; i < len(m.otherGroups); i++ {
+ m.otherGroups[i] = newGroup(m.regex.GroupNameFromNumber(i+1), m.text, m.matches[i+1], m.matchcount[i+1])
+ }
+ }
+}
+
+func (m *Match) groupValueAppendToBuf(groupnum int, buf *bytes.Buffer) {
+ c := m.matchcount[groupnum]
+ if c == 0 {
+ return
+ }
+
+ matches := m.matches[groupnum]
+
+ index := matches[(c-1)*2]
+ last := index + matches[(c*2)-1]
+
+ for ; index < last; index++ {
+ buf.WriteRune(m.text[index])
+ }
+}
+
+func newGroup(name string, text []rune, caps []int, capcount int) Group {
+ g := Group{}
+ g.text = text
+ if capcount > 0 {
+ g.Index = caps[(capcount-1)*2]
+ g.Length = caps[(capcount*2)-1]
+ }
+ g.Name = name
+ g.Captures = make([]Capture, capcount)
+ for i := 0; i < capcount; i++ {
+ g.Captures[i] = Capture{
+ text: text,
+ Index: caps[i*2],
+ Length: caps[i*2+1],
+ }
+ }
+ //log.Printf("newGroup! capcount %v, %+v", capcount, g)
+
+ return g
+}
+
+func (m *Match) dump() string {
+ buf := &bytes.Buffer{}
+ buf.WriteRune('\n')
+ if len(m.sparseCaps) > 0 {
+ for k, v := range m.sparseCaps {
+ fmt.Fprintf(buf, "Slot %v -> %v\n", k, v)
+ }
+ }
+
+ for i, g := range m.Groups() {
+ fmt.Fprintf(buf, "Group %v (%v), %v caps:\n", i, g.Name, len(g.Captures))
+
+ for _, c := range g.Captures {
+ fmt.Fprintf(buf, " (%v, %v) %v\n", c.Index, c.Length, c.String())
+ }
+ }
+ /*
+ for i := 0; i < len(m.matchcount); i++ {
+ fmt.Fprintf(buf, "\nGroup %v (%v):\n", i, m.regex.GroupNameFromNumber(i))
+
+ for j := 0; j < m.matchcount[i]; j++ {
+ text := ""
+
+ if m.matches[i][j*2] >= 0 {
+ start := m.matches[i][j*2]
+ text = m.text[start : start+m.matches[i][j*2+1]]
+ }
+
+ fmt.Fprintf(buf, " (%v, %v) %v\n", m.matches[i][j*2], m.matches[i][j*2+1], text)
+ }
+ }
+ */
+ return buf.String()
+}
diff --git a/vendor/github.com/dlclark/regexp2/regexp.go b/vendor/github.com/dlclark/regexp2/regexp.go
new file mode 100644
index 00000000..a7ddbaf3
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/regexp.go
@@ -0,0 +1,395 @@
+/*
+Package regexp2 is a regexp package that has an interface similar to Go's framework regexp engine but uses a
+more feature full regex engine behind the scenes.
+
+It doesn't have constant time guarantees, but it allows backtracking and is compatible with Perl5 and .NET.
+You'll likely be better off with the RE2 engine from the regexp package and should only use this if you
+need to write very complex patterns or require compatibility with .NET.
+*/
+package regexp2
+
+import (
+ "errors"
+ "math"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/dlclark/regexp2/syntax"
+)
+
+var (
+ // DefaultMatchTimeout used when running regexp matches -- "forever"
+ DefaultMatchTimeout = time.Duration(math.MaxInt64)
+ // DefaultUnmarshalOptions used when unmarshaling a regex from text
+ DefaultUnmarshalOptions = None
+)
+
+// Regexp is the representation of a compiled regular expression.
+// A Regexp is safe for concurrent use by multiple goroutines.
+type Regexp struct {
+ // A match will time out if it takes (approximately) more than
+ // MatchTimeout. This is a safety check in case the match
+ // encounters catastrophic backtracking. The default value
+ // (DefaultMatchTimeout) causes all time out checking to be
+ // suppressed.
+ MatchTimeout time.Duration
+
+ // read-only after Compile
+ pattern string // as passed to Compile
+ options RegexOptions // options
+
+ caps map[int]int // capnum->index
+ capnames map[string]int //capture group name -> index
+ capslist []string //sorted list of capture group names
+ capsize int // size of the capture array
+
+ code *syntax.Code // compiled program
+
+ // cache of machines for running regexp
+ muRun *sync.Mutex
+ runner []*runner
+}
+
+// Compile parses a regular expression and returns, if successful,
+// a Regexp object that can be used to match against text.
+func Compile(expr string, opt RegexOptions) (*Regexp, error) {
+ // parse it
+ tree, err := syntax.Parse(expr, syntax.RegexOptions(opt))
+ if err != nil {
+ return nil, err
+ }
+
+ // translate it to code
+ code, err := syntax.Write(tree)
+ if err != nil {
+ return nil, err
+ }
+
+ // return it
+ return &Regexp{
+ pattern: expr,
+ options: opt,
+ caps: code.Caps,
+ capnames: tree.Capnames,
+ capslist: tree.Caplist,
+ capsize: code.Capsize,
+ code: code,
+ MatchTimeout: DefaultMatchTimeout,
+ muRun: &sync.Mutex{},
+ }, nil
+}
+
+// MustCompile is like Compile but panics if the expression cannot be parsed.
+// It simplifies safe initialization of global variables holding compiled regular
+// expressions.
+func MustCompile(str string, opt RegexOptions) *Regexp {
+ regexp, error := Compile(str, opt)
+ if error != nil {
+ panic(`regexp2: Compile(` + quote(str) + `): ` + error.Error())
+ }
+ return regexp
+}
+
+// Escape adds backslashes to any special characters in the input string
+func Escape(input string) string {
+ return syntax.Escape(input)
+}
+
+// Unescape removes any backslashes from previously-escaped special characters in the input string
+func Unescape(input string) (string, error) {
+ return syntax.Unescape(input)
+}
+
+// SetTimeoutPeriod is a debug function that sets the frequency of the timeout goroutine's sleep cycle.
+// Defaults to 100ms. The only benefit of setting this lower is that the 1 background goroutine that manages
+// timeouts may exit slightly sooner after all the timeouts have expired. See Github issue #63
+func SetTimeoutCheckPeriod(d time.Duration) {
+ clockPeriod = d
+}
+
+// StopTimeoutClock should only be used in unit tests to prevent the timeout clock goroutine
+// from appearing like a leaking goroutine
+func StopTimeoutClock() {
+ stopClock()
+}
+
+// String returns the source text used to compile the regular expression.
+func (re *Regexp) String() string {
+ return re.pattern
+}
+
+func quote(s string) string {
+ if strconv.CanBackquote(s) {
+ return "`" + s + "`"
+ }
+ return strconv.Quote(s)
+}
+
+// RegexOptions impact the runtime and parsing behavior
+// for each specific regex. They are setable in code as well
+// as in the regex pattern itself.
+type RegexOptions int32
+
+const (
+ None RegexOptions = 0x0
+ IgnoreCase = 0x0001 // "i"
+ Multiline = 0x0002 // "m"
+ ExplicitCapture = 0x0004 // "n"
+ Compiled = 0x0008 // "c"
+ Singleline = 0x0010 // "s"
+ IgnorePatternWhitespace = 0x0020 // "x"
+ RightToLeft = 0x0040 // "r"
+ Debug = 0x0080 // "d"
+ ECMAScript = 0x0100 // "e"
+ RE2 = 0x0200 // RE2 (regexp package) compatibility mode
+ Unicode = 0x0400 // "u"
+)
+
+func (re *Regexp) RightToLeft() bool {
+ return re.options&RightToLeft != 0
+}
+
+func (re *Regexp) Debug() bool {
+ return re.options&Debug != 0
+}
+
+// Replace searches the input string and replaces each match found with the replacement text.
+// Count will limit the number of matches attempted and startAt will allow
+// us to skip past possible matches at the start of the input (left or right depending on RightToLeft option).
+// Set startAt and count to -1 to go through the whole string
+func (re *Regexp) Replace(input, replacement string, startAt, count int) (string, error) {
+ data, err := syntax.NewReplacerData(replacement, re.caps, re.capsize, re.capnames, syntax.RegexOptions(re.options))
+ if err != nil {
+ return "", err
+ }
+ //TODO: cache ReplacerData
+
+ return replace(re, data, nil, input, startAt, count)
+}
+
+// ReplaceFunc searches the input string and replaces each match found using the string from the evaluator
+// Count will limit the number of matches attempted and startAt will allow
+// us to skip past possible matches at the start of the input (left or right depending on RightToLeft option).
+// Set startAt and count to -1 to go through the whole string.
+func (re *Regexp) ReplaceFunc(input string, evaluator MatchEvaluator, startAt, count int) (string, error) {
+ return replace(re, nil, evaluator, input, startAt, count)
+}
+
+// FindStringMatch searches the input string for a Regexp match
+func (re *Regexp) FindStringMatch(s string) (*Match, error) {
+ // convert string to runes
+ return re.run(false, -1, getRunes(s))
+}
+
+// FindRunesMatch searches the input rune slice for a Regexp match
+func (re *Regexp) FindRunesMatch(r []rune) (*Match, error) {
+ return re.run(false, -1, r)
+}
+
+// FindStringMatchStartingAt searches the input string for a Regexp match starting at the startAt index
+func (re *Regexp) FindStringMatchStartingAt(s string, startAt int) (*Match, error) {
+ if startAt > len(s) {
+ return nil, errors.New("startAt must be less than the length of the input string")
+ }
+ r, startAt := re.getRunesAndStart(s, startAt)
+ if startAt == -1 {
+ // we didn't find our start index in the string -- that's a problem
+ return nil, errors.New("startAt must align to the start of a valid rune in the input string")
+ }
+
+ return re.run(false, startAt, r)
+}
+
+// FindRunesMatchStartingAt searches the input rune slice for a Regexp match starting at the startAt index
+func (re *Regexp) FindRunesMatchStartingAt(r []rune, startAt int) (*Match, error) {
+ return re.run(false, startAt, r)
+}
+
+// FindNextMatch returns the next match in the same input string as the match parameter.
+// Will return nil if there is no next match or if given a nil match.
+func (re *Regexp) FindNextMatch(m *Match) (*Match, error) {
+ if m == nil {
+ return nil, nil
+ }
+
+ // If previous match was empty, advance by one before matching to prevent
+ // infinite loop
+ startAt := m.textpos
+ if m.Length == 0 {
+ if m.textpos == len(m.text) {
+ return nil, nil
+ }
+
+ if re.RightToLeft() {
+ startAt--
+ } else {
+ startAt++
+ }
+ }
+ return re.run(false, startAt, m.text)
+}
+
+// MatchString return true if the string matches the regex
+// error will be set if a timeout occurs
+func (re *Regexp) MatchString(s string) (bool, error) {
+ m, err := re.run(true, -1, getRunes(s))
+ if err != nil {
+ return false, err
+ }
+ return m != nil, nil
+}
+
+func (re *Regexp) getRunesAndStart(s string, startAt int) ([]rune, int) {
+ if startAt < 0 {
+ if re.RightToLeft() {
+ r := getRunes(s)
+ return r, len(r)
+ }
+ return getRunes(s), 0
+ }
+ ret := make([]rune, len(s))
+ i := 0
+ runeIdx := -1
+ for strIdx, r := range s {
+ if strIdx == startAt {
+ runeIdx = i
+ }
+ ret[i] = r
+ i++
+ }
+ if startAt == len(s) {
+ runeIdx = i
+ }
+ return ret[:i], runeIdx
+}
+
+func getRunes(s string) []rune {
+ return []rune(s)
+}
+
+// MatchRunes return true if the runes matches the regex
+// error will be set if a timeout occurs
+func (re *Regexp) MatchRunes(r []rune) (bool, error) {
+ m, err := re.run(true, -1, r)
+ if err != nil {
+ return false, err
+ }
+ return m != nil, nil
+}
+
+// GetGroupNames Returns the set of strings used to name capturing groups in the expression.
+func (re *Regexp) GetGroupNames() []string {
+ var result []string
+
+ if re.capslist == nil {
+ result = make([]string, re.capsize)
+
+ for i := 0; i < len(result); i++ {
+ result[i] = strconv.Itoa(i)
+ }
+ } else {
+ result = make([]string, len(re.capslist))
+ copy(result, re.capslist)
+ }
+
+ return result
+}
+
+// GetGroupNumbers returns the integer group numbers corresponding to a group name.
+func (re *Regexp) GetGroupNumbers() []int {
+ var result []int
+
+ if re.caps == nil {
+ result = make([]int, re.capsize)
+
+ for i := 0; i < len(result); i++ {
+ result[i] = i
+ }
+ } else {
+ result = make([]int, len(re.caps))
+
+ for k, v := range re.caps {
+ result[v] = k
+ }
+ }
+
+ return result
+}
+
+// GroupNameFromNumber retrieves a group name that corresponds to a group number.
+// It will return "" for and unknown group number. Unnamed groups automatically
+// receive a name that is the decimal string equivalent of its number.
+func (re *Regexp) GroupNameFromNumber(i int) string {
+ if re.capslist == nil {
+ if i >= 0 && i < re.capsize {
+ return strconv.Itoa(i)
+ }
+
+ return ""
+ }
+
+ if re.caps != nil {
+ var ok bool
+ if i, ok = re.caps[i]; !ok {
+ return ""
+ }
+ }
+
+ if i >= 0 && i < len(re.capslist) {
+ return re.capslist[i]
+ }
+
+ return ""
+}
+
+// GroupNumberFromName returns a group number that corresponds to a group name.
+// Returns -1 if the name is not a recognized group name. Numbered groups
+// automatically get a group name that is the decimal string equivalent of its number.
+func (re *Regexp) GroupNumberFromName(name string) int {
+ // look up name if we have a hashtable of names
+ if re.capnames != nil {
+ if k, ok := re.capnames[name]; ok {
+ return k
+ }
+
+ return -1
+ }
+
+ // convert to an int if it looks like a number
+ result := 0
+ for i := 0; i < len(name); i++ {
+ ch := name[i]
+
+ if ch > '9' || ch < '0' {
+ return -1
+ }
+
+ result *= 10
+ result += int(ch - '0')
+ }
+
+ // return int if it's in range
+ if result >= 0 && result < re.capsize {
+ return result
+ }
+
+ return -1
+}
+
+// MarshalText implements [encoding.TextMarshaler]. The output
+// matches that of calling the [Regexp.String] method.
+func (re *Regexp) MarshalText() ([]byte, error) {
+ return []byte(re.String()), nil
+}
+
+// UnmarshalText implements [encoding.TextUnmarshaler] by calling
+// [Compile] on the encoded value.
+func (re *Regexp) UnmarshalText(text []byte) error {
+ newRE, err := Compile(string(text), DefaultUnmarshalOptions)
+ if err != nil {
+ return err
+ }
+ *re = *newRE
+ return nil
+}
diff --git a/vendor/github.com/dlclark/regexp2/replace.go b/vendor/github.com/dlclark/regexp2/replace.go
new file mode 100644
index 00000000..0376bd9d
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/replace.go
@@ -0,0 +1,177 @@
+package regexp2
+
+import (
+ "bytes"
+ "errors"
+
+ "github.com/dlclark/regexp2/syntax"
+)
+
+const (
+ replaceSpecials = 4
+ replaceLeftPortion = -1
+ replaceRightPortion = -2
+ replaceLastGroup = -3
+ replaceWholeString = -4
+)
+
+// MatchEvaluator is a function that takes a match and returns a replacement string to be used
+type MatchEvaluator func(Match) string
+
+// Three very similar algorithms appear below: replace (pattern),
+// replace (evaluator), and split.
+
+// Replace Replaces all occurrences of the regex in the string with the
+// replacement pattern.
+//
+// Note that the special case of no matches is handled on its own:
+// with no matches, the input string is returned unchanged.
+// The right-to-left case is split out because StringBuilder
+// doesn't handle right-to-left string building directly very well.
+func replace(regex *Regexp, data *syntax.ReplacerData, evaluator MatchEvaluator, input string, startAt, count int) (string, error) {
+ if count < -1 {
+ return "", errors.New("Count too small")
+ }
+ if count == 0 {
+ return "", nil
+ }
+
+ m, err := regex.FindStringMatchStartingAt(input, startAt)
+
+ if err != nil {
+ return "", err
+ }
+ if m == nil {
+ return input, nil
+ }
+
+ buf := &bytes.Buffer{}
+ text := m.text
+
+ if !regex.RightToLeft() {
+ prevat := 0
+ for m != nil {
+ if m.Index != prevat {
+ buf.WriteString(string(text[prevat:m.Index]))
+ }
+ prevat = m.Index + m.Length
+ if evaluator == nil {
+ replacementImpl(data, buf, m)
+ } else {
+ buf.WriteString(evaluator(*m))
+ }
+
+ count--
+ if count == 0 {
+ break
+ }
+ m, err = regex.FindNextMatch(m)
+ if err != nil {
+ return "", nil
+ }
+ }
+
+ if prevat < len(text) {
+ buf.WriteString(string(text[prevat:]))
+ }
+ } else {
+ prevat := len(text)
+ var al []string
+
+ for m != nil {
+ if m.Index+m.Length != prevat {
+ al = append(al, string(text[m.Index+m.Length:prevat]))
+ }
+ prevat = m.Index
+ if evaluator == nil {
+ replacementImplRTL(data, &al, m)
+ } else {
+ al = append(al, evaluator(*m))
+ }
+
+ count--
+ if count == 0 {
+ break
+ }
+ m, err = regex.FindNextMatch(m)
+ if err != nil {
+ return "", nil
+ }
+ }
+
+ if prevat > 0 {
+ buf.WriteString(string(text[:prevat]))
+ }
+
+ for i := len(al) - 1; i >= 0; i-- {
+ buf.WriteString(al[i])
+ }
+ }
+
+ return buf.String(), nil
+}
+
+// Given a Match, emits into the StringBuilder the evaluated
+// substitution pattern.
+func replacementImpl(data *syntax.ReplacerData, buf *bytes.Buffer, m *Match) {
+ for _, r := range data.Rules {
+
+ if r >= 0 { // string lookup
+ buf.WriteString(data.Strings[r])
+ } else if r < -replaceSpecials { // group lookup
+ m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
+ } else {
+ switch -replaceSpecials - 1 - r { // special insertion patterns
+ case replaceLeftPortion:
+ for i := 0; i < m.Index; i++ {
+ buf.WriteRune(m.text[i])
+ }
+ case replaceRightPortion:
+ for i := m.Index + m.Length; i < len(m.text); i++ {
+ buf.WriteRune(m.text[i])
+ }
+ case replaceLastGroup:
+ m.groupValueAppendToBuf(m.GroupCount()-1, buf)
+ case replaceWholeString:
+ for i := 0; i < len(m.text); i++ {
+ buf.WriteRune(m.text[i])
+ }
+ }
+ }
+ }
+}
+
+func replacementImplRTL(data *syntax.ReplacerData, al *[]string, m *Match) {
+ l := *al
+ buf := &bytes.Buffer{}
+
+ for _, r := range data.Rules {
+ buf.Reset()
+ if r >= 0 { // string lookup
+ l = append(l, data.Strings[r])
+ } else if r < -replaceSpecials { // group lookup
+ m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
+ l = append(l, buf.String())
+ } else {
+ switch -replaceSpecials - 1 - r { // special insertion patterns
+ case replaceLeftPortion:
+ for i := 0; i < m.Index; i++ {
+ buf.WriteRune(m.text[i])
+ }
+ case replaceRightPortion:
+ for i := m.Index + m.Length; i < len(m.text); i++ {
+ buf.WriteRune(m.text[i])
+ }
+ case replaceLastGroup:
+ m.groupValueAppendToBuf(m.GroupCount()-1, buf)
+ case replaceWholeString:
+ for i := 0; i < len(m.text); i++ {
+ buf.WriteRune(m.text[i])
+ }
+ }
+ l = append(l, buf.String())
+ }
+ }
+
+ *al = l
+}
diff --git a/vendor/github.com/dlclark/regexp2/runner.go b/vendor/github.com/dlclark/regexp2/runner.go
new file mode 100644
index 00000000..494dcef9
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/runner.go
@@ -0,0 +1,1609 @@
+package regexp2
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "math"
+ "strconv"
+ "strings"
+ "time"
+ "unicode"
+
+ "github.com/dlclark/regexp2/syntax"
+)
+
+type runner struct {
+ re *Regexp
+ code *syntax.Code
+
+ runtextstart int // starting point for search
+
+ runtext []rune // text to search
+ runtextpos int // current position in text
+ runtextend int
+
+ // The backtracking stack. Opcodes use this to store data regarding
+ // what they have matched and where to backtrack to. Each "frame" on
+ // the stack takes the form of [CodePosition Data1 Data2...], where
+ // CodePosition is the position of the current opcode and
+ // the data values are all optional. The CodePosition can be negative, and
+ // these values (also called "back2") are used by the BranchMark family of opcodes
+ // to indicate whether they are backtracking after a successful or failed
+ // match.
+ // When we backtrack, we pop the CodePosition off the stack, set the current
+ // instruction pointer to that code position, and mark the opcode
+ // with a backtracking flag ("Back"). Each opcode then knows how to
+ // handle its own data.
+ runtrack []int
+ runtrackpos int
+
+ // This stack is used to track text positions across different opcodes.
+ // For example, in /(a*b)+/, the parentheses result in a SetMark/CaptureMark
+ // pair. SetMark records the text position before we match a*b. Then
+ // CaptureMark uses that position to figure out where the capture starts.
+ // Opcodes which push onto this stack are always paired with other opcodes
+ // which will pop the value from it later. A successful match should mean
+ // that this stack is empty.
+ runstack []int
+ runstackpos int
+
+ // The crawl stack is used to keep track of captures. Every time a group
+ // has a capture, we push its group number onto the runcrawl stack. In
+ // the case of a balanced match, we push BOTH groups onto the stack.
+ runcrawl []int
+ runcrawlpos int
+
+ runtrackcount int // count of states that may do backtracking
+
+ runmatch *Match // result object
+
+ ignoreTimeout bool
+ timeout time.Duration // timeout in milliseconds (needed for actual)
+ deadline fasttime
+
+ operator syntax.InstOp
+ codepos int
+ rightToLeft bool
+ caseInsensitive bool
+}
+
+// run searches for matches and can continue from the previous match
+//
+// quick is usually false, but can be true to not return matches, just put it in caches
+// textstart is -1 to start at the "beginning" (depending on Right-To-Left), otherwise an index in input
+// input is the string to search for our regex pattern
+func (re *Regexp) run(quick bool, textstart int, input []rune) (*Match, error) {
+
+ // get a cached runner
+ runner := re.getRunner()
+ defer re.putRunner(runner)
+
+ if textstart < 0 {
+ if re.RightToLeft() {
+ textstart = len(input)
+ } else {
+ textstart = 0
+ }
+ }
+
+ return runner.scan(input, textstart, quick, re.MatchTimeout)
+}
+
+// Scans the string to find the first match. Uses the Match object
+// both to feed text in and as a place to store matches that come out.
+//
+// All the action is in the Go() method. Our
+// responsibility is to load up the class members before
+// calling Go.
+//
+// The optimizer can compute a set of candidate starting characters,
+// and we could use a separate method Skip() that will quickly scan past
+// any characters that we know can't match.
+func (r *runner) scan(rt []rune, textstart int, quick bool, timeout time.Duration) (*Match, error) {
+ r.timeout = timeout
+ r.ignoreTimeout = (time.Duration(math.MaxInt64) == timeout)
+ r.runtextstart = textstart
+ r.runtext = rt
+ r.runtextend = len(rt)
+
+ stoppos := r.runtextend
+ bump := 1
+
+ if r.re.RightToLeft() {
+ bump = -1
+ stoppos = 0
+ }
+
+ r.runtextpos = textstart
+ initted := false
+
+ r.startTimeoutWatch()
+ for {
+ if r.re.Debug() {
+ //fmt.Printf("\nSearch content: %v\n", string(r.runtext))
+ fmt.Printf("\nSearch range: from 0 to %v\n", r.runtextend)
+ fmt.Printf("Firstchar search starting at %v stopping at %v\n", r.runtextpos, stoppos)
+ }
+
+ if r.findFirstChar() {
+ if err := r.checkTimeout(); err != nil {
+ return nil, err
+ }
+
+ if !initted {
+ r.initMatch()
+ initted = true
+ }
+
+ if r.re.Debug() {
+ fmt.Printf("Executing engine starting at %v\n\n", r.runtextpos)
+ }
+
+ if err := r.execute(); err != nil {
+ return nil, err
+ }
+
+ if r.runmatch.matchcount[0] > 0 {
+ // We'll return a match even if it touches a previous empty match
+ return r.tidyMatch(quick), nil
+ }
+
+ // reset state for another go
+ r.runtrackpos = len(r.runtrack)
+ r.runstackpos = len(r.runstack)
+ r.runcrawlpos = len(r.runcrawl)
+ }
+
+ // failure!
+
+ if r.runtextpos == stoppos {
+ r.tidyMatch(true)
+ return nil, nil
+ }
+
+ // Recognize leading []* and various anchors, and bump on failure accordingly
+
+ // r.bump by one and start again
+
+ r.runtextpos += bump
+ }
+ // We never get here
+}
+
+func (r *runner) execute() error {
+
+ r.goTo(0)
+
+ for {
+
+ if r.re.Debug() {
+ r.dumpState()
+ }
+
+ if err := r.checkTimeout(); err != nil {
+ return err
+ }
+
+ switch r.operator {
+ case syntax.Stop:
+ return nil
+
+ case syntax.Nothing:
+ break
+
+ case syntax.Goto:
+ r.goTo(r.operand(0))
+ continue
+
+ case syntax.Testref:
+ if !r.runmatch.isMatched(r.operand(0)) {
+ break
+ }
+ r.advance(1)
+ continue
+
+ case syntax.Lazybranch:
+ r.trackPush1(r.textPos())
+ r.advance(1)
+ continue
+
+ case syntax.Lazybranch | syntax.Back:
+ r.trackPop()
+ r.textto(r.trackPeek())
+ r.goTo(r.operand(0))
+ continue
+
+ case syntax.Setmark:
+ r.stackPush(r.textPos())
+ r.trackPush()
+ r.advance(0)
+ continue
+
+ case syntax.Nullmark:
+ r.stackPush(-1)
+ r.trackPush()
+ r.advance(0)
+ continue
+
+ case syntax.Setmark | syntax.Back, syntax.Nullmark | syntax.Back:
+ r.stackPop()
+ break
+
+ case syntax.Getmark:
+ r.stackPop()
+ r.trackPush1(r.stackPeek())
+ r.textto(r.stackPeek())
+ r.advance(0)
+ continue
+
+ case syntax.Getmark | syntax.Back:
+ r.trackPop()
+ r.stackPush(r.trackPeek())
+ break
+
+ case syntax.Capturemark:
+ if r.operand(1) != -1 && !r.runmatch.isMatched(r.operand(1)) {
+ break
+ }
+ r.stackPop()
+ if r.operand(1) != -1 {
+ r.transferCapture(r.operand(0), r.operand(1), r.stackPeek(), r.textPos())
+ } else {
+ r.capture(r.operand(0), r.stackPeek(), r.textPos())
+ }
+ r.trackPush1(r.stackPeek())
+
+ r.advance(2)
+
+ continue
+
+ case syntax.Capturemark | syntax.Back:
+ r.trackPop()
+ r.stackPush(r.trackPeek())
+ r.uncapture()
+ if r.operand(0) != -1 && r.operand(1) != -1 {
+ r.uncapture()
+ }
+
+ break
+
+ case syntax.Branchmark:
+ r.stackPop()
+
+ matched := r.textPos() - r.stackPeek()
+
+ if matched != 0 { // Nonempty match -> loop now
+ r.trackPush2(r.stackPeek(), r.textPos()) // Save old mark, textpos
+ r.stackPush(r.textPos()) // Make new mark
+ r.goTo(r.operand(0)) // Loop
+ } else { // Empty match -> straight now
+ r.trackPushNeg1(r.stackPeek()) // Save old mark
+ r.advance(1) // Straight
+ }
+ continue
+
+ case syntax.Branchmark | syntax.Back:
+ r.trackPopN(2)
+ r.stackPop()
+ r.textto(r.trackPeekN(1)) // Recall position
+ r.trackPushNeg1(r.trackPeek()) // Save old mark
+ r.advance(1) // Straight
+ continue
+
+ case syntax.Branchmark | syntax.Back2:
+ r.trackPop()
+ r.stackPush(r.trackPeek()) // Recall old mark
+ break // Backtrack
+
+ case syntax.Lazybranchmark:
+ {
+ // We hit this the first time through a lazy loop and after each
+ // successful match of the inner expression. It simply continues
+ // on and doesn't loop.
+ r.stackPop()
+
+ oldMarkPos := r.stackPeek()
+
+ if r.textPos() != oldMarkPos { // Nonempty match -> try to loop again by going to 'back' state
+ if oldMarkPos != -1 {
+ r.trackPush2(oldMarkPos, r.textPos()) // Save old mark, textpos
+ } else {
+ r.trackPush2(r.textPos(), r.textPos())
+ }
+ } else {
+ // The inner expression found an empty match, so we'll go directly to 'back2' if we
+ // backtrack. In this case, we need to push something on the stack, since back2 pops.
+ // However, in the case of ()+? or similar, this empty match may be legitimate, so push the text
+ // position associated with that empty match.
+ r.stackPush(oldMarkPos)
+
+ r.trackPushNeg1(r.stackPeek()) // Save old mark
+ }
+ r.advance(1)
+ continue
+ }
+
+ case syntax.Lazybranchmark | syntax.Back:
+
+ // After the first time, Lazybranchmark | syntax.Back occurs
+ // with each iteration of the loop, and therefore with every attempted
+ // match of the inner expression. We'll try to match the inner expression,
+ // then go back to Lazybranchmark if successful. If the inner expression
+ // fails, we go to Lazybranchmark | syntax.Back2
+
+ r.trackPopN(2)
+ pos := r.trackPeekN(1)
+ r.trackPushNeg1(r.trackPeek()) // Save old mark
+ r.stackPush(pos) // Make new mark
+ r.textto(pos) // Recall position
+ r.goTo(r.operand(0)) // Loop
+ continue
+
+ case syntax.Lazybranchmark | syntax.Back2:
+ // The lazy loop has failed. We'll do a true backtrack and
+ // start over before the lazy loop.
+ r.stackPop()
+ r.trackPop()
+ r.stackPush(r.trackPeek()) // Recall old mark
+ break
+
+ case syntax.Setcount:
+ r.stackPush2(r.textPos(), r.operand(0))
+ r.trackPush()
+ r.advance(1)
+ continue
+
+ case syntax.Nullcount:
+ r.stackPush2(-1, r.operand(0))
+ r.trackPush()
+ r.advance(1)
+ continue
+
+ case syntax.Setcount | syntax.Back:
+ r.stackPopN(2)
+ break
+
+ case syntax.Nullcount | syntax.Back:
+ r.stackPopN(2)
+ break
+
+ case syntax.Branchcount:
+ // r.stackPush:
+ // 0: Mark
+ // 1: Count
+
+ r.stackPopN(2)
+ mark := r.stackPeek()
+ count := r.stackPeekN(1)
+ matched := r.textPos() - mark
+
+ if count >= r.operand(1) || (matched == 0 && count >= 0) { // Max loops or empty match -> straight now
+ r.trackPushNeg2(mark, count) // Save old mark, count
+ r.advance(2) // Straight
+ } else { // Nonempty match -> count+loop now
+ r.trackPush1(mark) // remember mark
+ r.stackPush2(r.textPos(), count+1) // Make new mark, incr count
+ r.goTo(r.operand(0)) // Loop
+ }
+ continue
+
+ case syntax.Branchcount | syntax.Back:
+ // r.trackPush:
+ // 0: Previous mark
+ // r.stackPush:
+ // 0: Mark (= current pos, discarded)
+ // 1: Count
+ r.trackPop()
+ r.stackPopN(2)
+ if r.stackPeekN(1) > 0 { // Positive -> can go straight
+ r.textto(r.stackPeek()) // Zap to mark
+ r.trackPushNeg2(r.trackPeek(), r.stackPeekN(1)-1) // Save old mark, old count
+ r.advance(2) // Straight
+ continue
+ }
+ r.stackPush2(r.trackPeek(), r.stackPeekN(1)-1) // recall old mark, old count
+ break
+
+ case syntax.Branchcount | syntax.Back2:
+ // r.trackPush:
+ // 0: Previous mark
+ // 1: Previous count
+ r.trackPopN(2)
+ r.stackPush2(r.trackPeek(), r.trackPeekN(1)) // Recall old mark, old count
+ break // Backtrack
+
+ case syntax.Lazybranchcount:
+ // r.stackPush:
+ // 0: Mark
+ // 1: Count
+
+ r.stackPopN(2)
+ mark := r.stackPeek()
+ count := r.stackPeekN(1)
+
+ if count < 0 { // Negative count -> loop now
+ r.trackPushNeg1(mark) // Save old mark
+ r.stackPush2(r.textPos(), count+1) // Make new mark, incr count
+ r.goTo(r.operand(0)) // Loop
+ } else { // Nonneg count -> straight now
+ r.trackPush3(mark, count, r.textPos()) // Save mark, count, position
+ r.advance(2) // Straight
+ }
+ continue
+
+ case syntax.Lazybranchcount | syntax.Back:
+ // r.trackPush:
+ // 0: Mark
+ // 1: Count
+ // 2: r.textPos
+
+ r.trackPopN(3)
+ mark := r.trackPeek()
+ textpos := r.trackPeekN(2)
+
+ if r.trackPeekN(1) < r.operand(1) && textpos != mark { // Under limit and not empty match -> loop
+ r.textto(textpos) // Recall position
+ r.stackPush2(textpos, r.trackPeekN(1)+1) // Make new mark, incr count
+ r.trackPushNeg1(mark) // Save old mark
+ r.goTo(r.operand(0)) // Loop
+ continue
+ } else { // Max loops or empty match -> backtrack
+ r.stackPush2(r.trackPeek(), r.trackPeekN(1)) // Recall old mark, count
+ break // backtrack
+ }
+
+ case syntax.Lazybranchcount | syntax.Back2:
+ // r.trackPush:
+ // 0: Previous mark
+ // r.stackPush:
+ // 0: Mark (== current pos, discarded)
+ // 1: Count
+ r.trackPop()
+ r.stackPopN(2)
+ r.stackPush2(r.trackPeek(), r.stackPeekN(1)-1) // Recall old mark, count
+ break // Backtrack
+
+ case syntax.Setjump:
+ r.stackPush2(r.trackpos(), r.crawlpos())
+ r.trackPush()
+ r.advance(0)
+ continue
+
+ case syntax.Setjump | syntax.Back:
+ r.stackPopN(2)
+ break
+
+ case syntax.Backjump:
+ // r.stackPush:
+ // 0: Saved trackpos
+ // 1: r.crawlpos
+ r.stackPopN(2)
+ r.trackto(r.stackPeek())
+
+ for r.crawlpos() != r.stackPeekN(1) {
+ r.uncapture()
+ }
+
+ break
+
+ case syntax.Forejump:
+ // r.stackPush:
+ // 0: Saved trackpos
+ // 1: r.crawlpos
+ r.stackPopN(2)
+ r.trackto(r.stackPeek())
+ r.trackPush1(r.stackPeekN(1))
+ r.advance(0)
+ continue
+
+ case syntax.Forejump | syntax.Back:
+ // r.trackPush:
+ // 0: r.crawlpos
+ r.trackPop()
+
+ for r.crawlpos() != r.trackPeek() {
+ r.uncapture()
+ }
+
+ break
+
+ case syntax.Bol:
+ if r.leftchars() > 0 && r.charAt(r.textPos()-1) != '\n' {
+ break
+ }
+ r.advance(0)
+ continue
+
+ case syntax.Eol:
+ if r.rightchars() > 0 && r.charAt(r.textPos()) != '\n' {
+ break
+ }
+ r.advance(0)
+ continue
+
+ case syntax.Boundary:
+ if !r.isBoundary(r.textPos(), 0, r.runtextend) {
+ break
+ }
+ r.advance(0)
+ continue
+
+ case syntax.Nonboundary:
+ if r.isBoundary(r.textPos(), 0, r.runtextend) {
+ break
+ }
+ r.advance(0)
+ continue
+
+ case syntax.ECMABoundary:
+ if !r.isECMABoundary(r.textPos(), 0, r.runtextend) {
+ break
+ }
+ r.advance(0)
+ continue
+
+ case syntax.NonECMABoundary:
+ if r.isECMABoundary(r.textPos(), 0, r.runtextend) {
+ break
+ }
+ r.advance(0)
+ continue
+
+ case syntax.Beginning:
+ if r.leftchars() > 0 {
+ break
+ }
+ r.advance(0)
+ continue
+
+ case syntax.Start:
+ if r.textPos() != r.textstart() {
+ break
+ }
+ r.advance(0)
+ continue
+
+ case syntax.EndZ:
+ rchars := r.rightchars()
+ if rchars > 1 {
+ break
+ }
+ // RE2 and EcmaScript define $ as "asserts position at the end of the string"
+ // PCRE/.NET adds "or before the line terminator right at the end of the string (if any)"
+ if (r.re.options & (RE2 | ECMAScript)) != 0 {
+ // RE2/Ecmascript mode
+ if rchars > 0 {
+ break
+ }
+ } else if rchars == 1 && r.charAt(r.textPos()) != '\n' {
+ // "regular" mode
+ break
+ }
+
+ r.advance(0)
+ continue
+
+ case syntax.End:
+ if r.rightchars() > 0 {
+ break
+ }
+ r.advance(0)
+ continue
+
+ case syntax.One:
+ if r.forwardchars() < 1 || r.forwardcharnext() != rune(r.operand(0)) {
+ break
+ }
+
+ r.advance(1)
+ continue
+
+ case syntax.Notone:
+ if r.forwardchars() < 1 || r.forwardcharnext() == rune(r.operand(0)) {
+ break
+ }
+
+ r.advance(1)
+ continue
+
+ case syntax.Set:
+
+ if r.forwardchars() < 1 || !r.code.Sets[r.operand(0)].CharIn(r.forwardcharnext()) {
+ break
+ }
+
+ r.advance(1)
+ continue
+
+ case syntax.Multi:
+ if !r.runematch(r.code.Strings[r.operand(0)]) {
+ break
+ }
+
+ r.advance(1)
+ continue
+
+ case syntax.Ref:
+
+ capnum := r.operand(0)
+
+ if r.runmatch.isMatched(capnum) {
+ if !r.refmatch(r.runmatch.matchIndex(capnum), r.runmatch.matchLength(capnum)) {
+ break
+ }
+ } else {
+ if (r.re.options & ECMAScript) == 0 {
+ break
+ }
+ }
+
+ r.advance(1)
+ continue
+
+ case syntax.Onerep:
+
+ c := r.operand(1)
+
+ if r.forwardchars() < c {
+ break
+ }
+
+ ch := rune(r.operand(0))
+
+ for c > 0 {
+ if r.forwardcharnext() != ch {
+ goto BreakBackward
+ }
+ c--
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Notonerep:
+
+ c := r.operand(1)
+
+ if r.forwardchars() < c {
+ break
+ }
+ ch := rune(r.operand(0))
+
+ for c > 0 {
+ if r.forwardcharnext() == ch {
+ goto BreakBackward
+ }
+ c--
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Setrep:
+
+ c := r.operand(1)
+
+ if r.forwardchars() < c {
+ break
+ }
+
+ set := r.code.Sets[r.operand(0)]
+
+ for c > 0 {
+ if !set.CharIn(r.forwardcharnext()) {
+ goto BreakBackward
+ }
+ c--
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Oneloop:
+
+ c := r.operand(1)
+
+ if c > r.forwardchars() {
+ c = r.forwardchars()
+ }
+
+ ch := rune(r.operand(0))
+ i := c
+
+ for ; i > 0; i-- {
+ if r.forwardcharnext() != ch {
+ r.backwardnext()
+ break
+ }
+ }
+
+ if c > i {
+ r.trackPush2(c-i-1, r.textPos()-r.bump())
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Notoneloop:
+
+ c := r.operand(1)
+
+ if c > r.forwardchars() {
+ c = r.forwardchars()
+ }
+
+ ch := rune(r.operand(0))
+ i := c
+
+ for ; i > 0; i-- {
+ if r.forwardcharnext() == ch {
+ r.backwardnext()
+ break
+ }
+ }
+
+ if c > i {
+ r.trackPush2(c-i-1, r.textPos()-r.bump())
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Setloop:
+
+ c := r.operand(1)
+
+ if c > r.forwardchars() {
+ c = r.forwardchars()
+ }
+
+ set := r.code.Sets[r.operand(0)]
+ i := c
+
+ for ; i > 0; i-- {
+ if !set.CharIn(r.forwardcharnext()) {
+ r.backwardnext()
+ break
+ }
+ }
+
+ if c > i {
+ r.trackPush2(c-i-1, r.textPos()-r.bump())
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Oneloop | syntax.Back, syntax.Notoneloop | syntax.Back:
+
+ r.trackPopN(2)
+ i := r.trackPeek()
+ pos := r.trackPeekN(1)
+
+ r.textto(pos)
+
+ if i > 0 {
+ r.trackPush2(i-1, pos-r.bump())
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Setloop | syntax.Back:
+
+ r.trackPopN(2)
+ i := r.trackPeek()
+ pos := r.trackPeekN(1)
+
+ r.textto(pos)
+
+ if i > 0 {
+ r.trackPush2(i-1, pos-r.bump())
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Onelazy, syntax.Notonelazy:
+
+ c := r.operand(1)
+
+ if c > r.forwardchars() {
+ c = r.forwardchars()
+ }
+
+ if c > 0 {
+ r.trackPush2(c-1, r.textPos())
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Setlazy:
+
+ c := r.operand(1)
+
+ if c > r.forwardchars() {
+ c = r.forwardchars()
+ }
+
+ if c > 0 {
+ r.trackPush2(c-1, r.textPos())
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Onelazy | syntax.Back:
+
+ r.trackPopN(2)
+ pos := r.trackPeekN(1)
+ r.textto(pos)
+
+ if r.forwardcharnext() != rune(r.operand(0)) {
+ break
+ }
+
+ i := r.trackPeek()
+
+ if i > 0 {
+ r.trackPush2(i-1, pos+r.bump())
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Notonelazy | syntax.Back:
+
+ r.trackPopN(2)
+ pos := r.trackPeekN(1)
+ r.textto(pos)
+
+ if r.forwardcharnext() == rune(r.operand(0)) {
+ break
+ }
+
+ i := r.trackPeek()
+
+ if i > 0 {
+ r.trackPush2(i-1, pos+r.bump())
+ }
+
+ r.advance(2)
+ continue
+
+ case syntax.Setlazy | syntax.Back:
+
+ r.trackPopN(2)
+ pos := r.trackPeekN(1)
+ r.textto(pos)
+
+ if !r.code.Sets[r.operand(0)].CharIn(r.forwardcharnext()) {
+ break
+ }
+
+ i := r.trackPeek()
+
+ if i > 0 {
+ r.trackPush2(i-1, pos+r.bump())
+ }
+
+ r.advance(2)
+ continue
+
+ default:
+ return errors.New("unknown state in regex runner")
+ }
+
+ BreakBackward:
+ ;
+
+ // "break Backward" comes here:
+ r.backtrack()
+ }
+}
+
+// increase the size of stack and track storage
+func (r *runner) ensureStorage() {
+ if r.runstackpos < r.runtrackcount*4 {
+ doubleIntSlice(&r.runstack, &r.runstackpos)
+ }
+ if r.runtrackpos < r.runtrackcount*4 {
+ doubleIntSlice(&r.runtrack, &r.runtrackpos)
+ }
+}
+
+func doubleIntSlice(s *[]int, pos *int) {
+ oldLen := len(*s)
+ newS := make([]int, oldLen*2)
+
+ copy(newS[oldLen:], *s)
+ *pos += oldLen
+ *s = newS
+}
+
+// Save a number on the longjump unrolling stack
+func (r *runner) crawl(i int) {
+ if r.runcrawlpos == 0 {
+ doubleIntSlice(&r.runcrawl, &r.runcrawlpos)
+ }
+ r.runcrawlpos--
+ r.runcrawl[r.runcrawlpos] = i
+}
+
+// Remove a number from the longjump unrolling stack
+func (r *runner) popcrawl() int {
+ val := r.runcrawl[r.runcrawlpos]
+ r.runcrawlpos++
+ return val
+}
+
+// Get the height of the stack
+func (r *runner) crawlpos() int {
+ return len(r.runcrawl) - r.runcrawlpos
+}
+
+func (r *runner) advance(i int) {
+ r.codepos += (i + 1)
+ r.setOperator(r.code.Codes[r.codepos])
+}
+
+func (r *runner) goTo(newpos int) {
+ // when branching backward or in place, ensure storage
+ if newpos <= r.codepos {
+ r.ensureStorage()
+ }
+
+ r.setOperator(r.code.Codes[newpos])
+ r.codepos = newpos
+}
+
+func (r *runner) textto(newpos int) {
+ r.runtextpos = newpos
+}
+
+func (r *runner) trackto(newpos int) {
+ r.runtrackpos = len(r.runtrack) - newpos
+}
+
+func (r *runner) textstart() int {
+ return r.runtextstart
+}
+
+func (r *runner) textPos() int {
+ return r.runtextpos
+}
+
+// push onto the backtracking stack
+func (r *runner) trackpos() int {
+ return len(r.runtrack) - r.runtrackpos
+}
+
+func (r *runner) trackPush() {
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = r.codepos
+}
+
+func (r *runner) trackPush1(I1 int) {
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = I1
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = r.codepos
+}
+
+func (r *runner) trackPush2(I1, I2 int) {
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = I1
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = I2
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = r.codepos
+}
+
+func (r *runner) trackPush3(I1, I2, I3 int) {
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = I1
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = I2
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = I3
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = r.codepos
+}
+
+func (r *runner) trackPushNeg1(I1 int) {
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = I1
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = -r.codepos
+}
+
+func (r *runner) trackPushNeg2(I1, I2 int) {
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = I1
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = I2
+ r.runtrackpos--
+ r.runtrack[r.runtrackpos] = -r.codepos
+}
+
+func (r *runner) backtrack() {
+ newpos := r.runtrack[r.runtrackpos]
+ r.runtrackpos++
+
+ if r.re.Debug() {
+ if newpos < 0 {
+ fmt.Printf(" Backtracking (back2) to code position %v\n", -newpos)
+ } else {
+ fmt.Printf(" Backtracking to code position %v\n", newpos)
+ }
+ }
+
+ if newpos < 0 {
+ newpos = -newpos
+ r.setOperator(r.code.Codes[newpos] | syntax.Back2)
+ } else {
+ r.setOperator(r.code.Codes[newpos] | syntax.Back)
+ }
+
+ // When branching backward, ensure storage
+ if newpos < r.codepos {
+ r.ensureStorage()
+ }
+
+ r.codepos = newpos
+}
+
+func (r *runner) setOperator(op int) {
+ r.caseInsensitive = (0 != (op & syntax.Ci))
+ r.rightToLeft = (0 != (op & syntax.Rtl))
+ r.operator = syntax.InstOp(op & ^(syntax.Rtl | syntax.Ci))
+}
+
+func (r *runner) trackPop() {
+ r.runtrackpos++
+}
+
+// pop framesize items from the backtracking stack
+func (r *runner) trackPopN(framesize int) {
+ r.runtrackpos += framesize
+}
+
+// Technically we are actually peeking at items already popped. So if you want to
+// get and pop the top item from the stack, you do
+// r.trackPop();
+// r.trackPeek();
+func (r *runner) trackPeek() int {
+ return r.runtrack[r.runtrackpos-1]
+}
+
+// get the ith element down on the backtracking stack
+func (r *runner) trackPeekN(i int) int {
+ return r.runtrack[r.runtrackpos-i-1]
+}
+
+// Push onto the grouping stack
+func (r *runner) stackPush(I1 int) {
+ r.runstackpos--
+ r.runstack[r.runstackpos] = I1
+}
+
+func (r *runner) stackPush2(I1, I2 int) {
+ r.runstackpos--
+ r.runstack[r.runstackpos] = I1
+ r.runstackpos--
+ r.runstack[r.runstackpos] = I2
+}
+
+func (r *runner) stackPop() {
+ r.runstackpos++
+}
+
+// pop framesize items from the grouping stack
+func (r *runner) stackPopN(framesize int) {
+ r.runstackpos += framesize
+}
+
+// Technically we are actually peeking at items already popped. So if you want to
+// get and pop the top item from the stack, you do
+// r.stackPop();
+// r.stackPeek();
+func (r *runner) stackPeek() int {
+ return r.runstack[r.runstackpos-1]
+}
+
+// get the ith element down on the grouping stack
+func (r *runner) stackPeekN(i int) int {
+ return r.runstack[r.runstackpos-i-1]
+}
+
+func (r *runner) operand(i int) int {
+ return r.code.Codes[r.codepos+i+1]
+}
+
+func (r *runner) leftchars() int {
+ return r.runtextpos
+}
+
+func (r *runner) rightchars() int {
+ return r.runtextend - r.runtextpos
+}
+
+func (r *runner) bump() int {
+ if r.rightToLeft {
+ return -1
+ }
+ return 1
+}
+
+func (r *runner) forwardchars() int {
+ if r.rightToLeft {
+ return r.runtextpos
+ }
+ return r.runtextend - r.runtextpos
+}
+
+func (r *runner) forwardcharnext() rune {
+ var ch rune
+ if r.rightToLeft {
+ r.runtextpos--
+ ch = r.runtext[r.runtextpos]
+ } else {
+ ch = r.runtext[r.runtextpos]
+ r.runtextpos++
+ }
+
+ if r.caseInsensitive {
+ return unicode.ToLower(ch)
+ }
+ return ch
+}
+
+func (r *runner) runematch(str []rune) bool {
+ var pos int
+
+ c := len(str)
+ if !r.rightToLeft {
+ if r.runtextend-r.runtextpos < c {
+ return false
+ }
+
+ pos = r.runtextpos + c
+ } else {
+ if r.runtextpos-0 < c {
+ return false
+ }
+
+ pos = r.runtextpos
+ }
+
+ if !r.caseInsensitive {
+ for c != 0 {
+ c--
+ pos--
+ if str[c] != r.runtext[pos] {
+ return false
+ }
+ }
+ } else {
+ for c != 0 {
+ c--
+ pos--
+ if str[c] != unicode.ToLower(r.runtext[pos]) {
+ return false
+ }
+ }
+ }
+
+ if !r.rightToLeft {
+ pos += len(str)
+ }
+
+ r.runtextpos = pos
+
+ return true
+}
+
+func (r *runner) refmatch(index, len int) bool {
+ var c, pos, cmpos int
+
+ if !r.rightToLeft {
+ if r.runtextend-r.runtextpos < len {
+ return false
+ }
+
+ pos = r.runtextpos + len
+ } else {
+ if r.runtextpos-0 < len {
+ return false
+ }
+
+ pos = r.runtextpos
+ }
+ cmpos = index + len
+
+ c = len
+
+ if !r.caseInsensitive {
+ for c != 0 {
+ c--
+ cmpos--
+ pos--
+ if r.runtext[cmpos] != r.runtext[pos] {
+ return false
+ }
+
+ }
+ } else {
+ for c != 0 {
+ c--
+ cmpos--
+ pos--
+
+ if unicode.ToLower(r.runtext[cmpos]) != unicode.ToLower(r.runtext[pos]) {
+ return false
+ }
+ }
+ }
+
+ if !r.rightToLeft {
+ pos += len
+ }
+
+ r.runtextpos = pos
+
+ return true
+}
+
+func (r *runner) backwardnext() {
+ if r.rightToLeft {
+ r.runtextpos++
+ } else {
+ r.runtextpos--
+ }
+}
+
+func (r *runner) charAt(j int) rune {
+ return r.runtext[j]
+}
+
+func (r *runner) findFirstChar() bool {
+
+ if 0 != (r.code.Anchors & (syntax.AnchorBeginning | syntax.AnchorStart | syntax.AnchorEndZ | syntax.AnchorEnd)) {
+ if !r.code.RightToLeft {
+ if (0 != (r.code.Anchors&syntax.AnchorBeginning) && r.runtextpos > 0) ||
+ (0 != (r.code.Anchors&syntax.AnchorStart) && r.runtextpos > r.runtextstart) {
+ r.runtextpos = r.runtextend
+ return false
+ }
+ if 0 != (r.code.Anchors&syntax.AnchorEndZ) && r.runtextpos < r.runtextend-1 {
+ r.runtextpos = r.runtextend - 1
+ } else if 0 != (r.code.Anchors&syntax.AnchorEnd) && r.runtextpos < r.runtextend {
+ r.runtextpos = r.runtextend
+ }
+ } else {
+ if (0 != (r.code.Anchors&syntax.AnchorEnd) && r.runtextpos < r.runtextend) ||
+ (0 != (r.code.Anchors&syntax.AnchorEndZ) && (r.runtextpos < r.runtextend-1 ||
+ (r.runtextpos == r.runtextend-1 && r.charAt(r.runtextpos) != '\n'))) ||
+ (0 != (r.code.Anchors&syntax.AnchorStart) && r.runtextpos < r.runtextstart) {
+ r.runtextpos = 0
+ return false
+ }
+ if 0 != (r.code.Anchors&syntax.AnchorBeginning) && r.runtextpos > 0 {
+ r.runtextpos = 0
+ }
+ }
+
+ if r.code.BmPrefix != nil {
+ return r.code.BmPrefix.IsMatch(r.runtext, r.runtextpos, 0, r.runtextend)
+ }
+
+ return true // found a valid start or end anchor
+ } else if r.code.BmPrefix != nil {
+ r.runtextpos = r.code.BmPrefix.Scan(r.runtext, r.runtextpos, 0, r.runtextend)
+
+ if r.runtextpos == -1 {
+ if r.code.RightToLeft {
+ r.runtextpos = 0
+ } else {
+ r.runtextpos = r.runtextend
+ }
+ return false
+ }
+
+ return true
+ } else if r.code.FcPrefix == nil {
+ return true
+ }
+
+ r.rightToLeft = r.code.RightToLeft
+ r.caseInsensitive = r.code.FcPrefix.CaseInsensitive
+
+ set := r.code.FcPrefix.PrefixSet
+ if set.IsSingleton() {
+ ch := set.SingletonChar()
+ for i := r.forwardchars(); i > 0; i-- {
+ if ch == r.forwardcharnext() {
+ r.backwardnext()
+ return true
+ }
+ }
+ } else {
+ for i := r.forwardchars(); i > 0; i-- {
+ n := r.forwardcharnext()
+ //fmt.Printf("%v in %v: %v\n", string(n), set.String(), set.CharIn(n))
+ if set.CharIn(n) {
+ r.backwardnext()
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+func (r *runner) initMatch() {
+ // Use a hashtable'ed Match object if the capture numbers are sparse
+
+ if r.runmatch == nil {
+ if r.re.caps != nil {
+ r.runmatch = newMatchSparse(r.re, r.re.caps, r.re.capsize, r.runtext, r.runtextstart)
+ } else {
+ r.runmatch = newMatch(r.re, r.re.capsize, r.runtext, r.runtextstart)
+ }
+ } else {
+ r.runmatch.reset(r.runtext, r.runtextstart)
+ }
+
+ // note we test runcrawl, because it is the last one to be allocated
+ // If there is an alloc failure in the middle of the three allocations,
+ // we may still return to reuse this instance, and we want to behave
+ // as if the allocations didn't occur. (we used to test _trackcount != 0)
+
+ if r.runcrawl != nil {
+ r.runtrackpos = len(r.runtrack)
+ r.runstackpos = len(r.runstack)
+ r.runcrawlpos = len(r.runcrawl)
+ return
+ }
+
+ r.initTrackCount()
+
+ tracksize := r.runtrackcount * 8
+ stacksize := r.runtrackcount * 8
+
+ if tracksize < 32 {
+ tracksize = 32
+ }
+ if stacksize < 16 {
+ stacksize = 16
+ }
+
+ r.runtrack = make([]int, tracksize)
+ r.runtrackpos = tracksize
+
+ r.runstack = make([]int, stacksize)
+ r.runstackpos = stacksize
+
+ r.runcrawl = make([]int, 32)
+ r.runcrawlpos = 32
+}
+
+func (r *runner) tidyMatch(quick bool) *Match {
+ if !quick {
+ match := r.runmatch
+
+ r.runmatch = nil
+
+ match.tidy(r.runtextpos)
+ return match
+ } else {
+ // send back our match -- it's not leaving the package, so it's safe to not clean it up
+ // this reduces allocs for frequent calls to the "IsMatch" bool-only functions
+ return r.runmatch
+ }
+}
+
+// capture captures a subexpression. Note that the
+// capnum used here has already been mapped to a non-sparse
+// index (by the code generator RegexWriter).
+func (r *runner) capture(capnum, start, end int) {
+ if end < start {
+ T := end
+ end = start
+ start = T
+ }
+
+ r.crawl(capnum)
+ r.runmatch.addMatch(capnum, start, end-start)
+}
+
+// transferCapture captures a subexpression. Note that the
+// capnum used here has already been mapped to a non-sparse
+// index (by the code generator RegexWriter).
+func (r *runner) transferCapture(capnum, uncapnum, start, end int) {
+ var start2, end2 int
+
+ // these are the two intervals that are cancelling each other
+
+ if end < start {
+ T := end
+ end = start
+ start = T
+ }
+
+ start2 = r.runmatch.matchIndex(uncapnum)
+ end2 = start2 + r.runmatch.matchLength(uncapnum)
+
+ // The new capture gets the innermost defined interval
+
+ if start >= end2 {
+ end = start
+ start = end2
+ } else if end <= start2 {
+ start = start2
+ } else {
+ if end > end2 {
+ end = end2
+ }
+ if start2 > start {
+ start = start2
+ }
+ }
+
+ r.crawl(uncapnum)
+ r.runmatch.balanceMatch(uncapnum)
+
+ if capnum != -1 {
+ r.crawl(capnum)
+ r.runmatch.addMatch(capnum, start, end-start)
+ }
+}
+
+// revert the last capture
+func (r *runner) uncapture() {
+ capnum := r.popcrawl()
+ r.runmatch.removeMatch(capnum)
+}
+
+//debug
+
+func (r *runner) dumpState() {
+ back := ""
+ if r.operator&syntax.Back != 0 {
+ back = " Back"
+ }
+ if r.operator&syntax.Back2 != 0 {
+ back += " Back2"
+ }
+ fmt.Printf("Text: %v\nTrack: %v\nStack: %v\n %s%s\n\n",
+ r.textposDescription(),
+ r.stackDescription(r.runtrack, r.runtrackpos),
+ r.stackDescription(r.runstack, r.runstackpos),
+ r.code.OpcodeDescription(r.codepos),
+ back)
+}
+
+func (r *runner) stackDescription(a []int, index int) string {
+ buf := &bytes.Buffer{}
+
+ fmt.Fprintf(buf, "%v/%v", len(a)-index, len(a))
+ if buf.Len() < 8 {
+ buf.WriteString(strings.Repeat(" ", 8-buf.Len()))
+ }
+
+ buf.WriteRune('(')
+ for i := index; i < len(a); i++ {
+ if i > index {
+ buf.WriteRune(' ')
+ }
+
+ buf.WriteString(strconv.Itoa(a[i]))
+ }
+
+ buf.WriteRune(')')
+
+ return buf.String()
+}
+
+func (r *runner) textposDescription() string {
+ buf := &bytes.Buffer{}
+
+ buf.WriteString(strconv.Itoa(r.runtextpos))
+
+ if buf.Len() < 8 {
+ buf.WriteString(strings.Repeat(" ", 8-buf.Len()))
+ }
+
+ if r.runtextpos > 0 {
+ buf.WriteString(syntax.CharDescription(r.runtext[r.runtextpos-1]))
+ } else {
+ buf.WriteRune('^')
+ }
+
+ buf.WriteRune('>')
+
+ for i := r.runtextpos; i < r.runtextend; i++ {
+ buf.WriteString(syntax.CharDescription(r.runtext[i]))
+ }
+ if buf.Len() >= 64 {
+ buf.Truncate(61)
+ buf.WriteString("...")
+ } else {
+ buf.WriteRune('$')
+ }
+
+ return buf.String()
+}
+
+// decide whether the pos
+// at the specified index is a boundary or not. It's just not worth
+// emitting inline code for this logic.
+func (r *runner) isBoundary(index, startpos, endpos int) bool {
+ return (index > startpos && syntax.IsWordChar(r.runtext[index-1])) !=
+ (index < endpos && syntax.IsWordChar(r.runtext[index]))
+}
+
+func (r *runner) isECMABoundary(index, startpos, endpos int) bool {
+ return (index > startpos && syntax.IsECMAWordChar(r.runtext[index-1])) !=
+ (index < endpos && syntax.IsECMAWordChar(r.runtext[index]))
+}
+
+func (r *runner) startTimeoutWatch() {
+ if r.ignoreTimeout {
+ return
+ }
+ r.deadline = makeDeadline(r.timeout)
+}
+
+func (r *runner) checkTimeout() error {
+ if r.ignoreTimeout || !r.deadline.reached() {
+ return nil
+ }
+
+ if r.re.Debug() {
+ //Debug.WriteLine("")
+ //Debug.WriteLine("RegEx match timeout occurred!")
+ //Debug.WriteLine("Specified timeout: " + TimeSpan.FromMilliseconds(_timeout).ToString())
+ //Debug.WriteLine("Timeout check frequency: " + TimeoutCheckFrequency)
+ //Debug.WriteLine("Search pattern: " + _runregex._pattern)
+ //Debug.WriteLine("Input: " + r.runtext)
+ //Debug.WriteLine("About to throw RegexMatchTimeoutException.")
+ }
+
+ return fmt.Errorf("match timeout after %v on input `%v`", r.timeout, string(r.runtext))
+}
+
+func (r *runner) initTrackCount() {
+ r.runtrackcount = r.code.TrackCount
+}
+
+// getRunner returns a run to use for matching re.
+// It uses the re's runner cache if possible, to avoid
+// unnecessary allocation.
+func (re *Regexp) getRunner() *runner {
+ re.muRun.Lock()
+ if n := len(re.runner); n > 0 {
+ z := re.runner[n-1]
+ re.runner = re.runner[:n-1]
+ re.muRun.Unlock()
+ return z
+ }
+ re.muRun.Unlock()
+ z := &runner{
+ re: re,
+ code: re.code,
+ }
+ return z
+}
+
+// putRunner returns a runner to the re's cache.
+// There is no attempt to limit the size of the cache, so it will
+// grow to the maximum number of simultaneous matches
+// run using re. (The cache empties when re gets garbage collected.)
+func (re *Regexp) putRunner(r *runner) {
+ re.muRun.Lock()
+ re.runner = append(re.runner, r)
+ re.muRun.Unlock()
+}
diff --git a/vendor/github.com/dlclark/regexp2/syntax/charclass.go b/vendor/github.com/dlclark/regexp2/syntax/charclass.go
new file mode 100644
index 00000000..6881a0e2
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/syntax/charclass.go
@@ -0,0 +1,865 @@
+package syntax
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "sort"
+ "unicode"
+ "unicode/utf8"
+)
+
+// CharSet combines start-end rune ranges and unicode categories representing a set of characters
+type CharSet struct {
+ ranges []singleRange
+ categories []category
+ sub *CharSet //optional subtractor
+ negate bool
+ anything bool
+}
+
+type category struct {
+ negate bool
+ cat string
+}
+
+type singleRange struct {
+ first rune
+ last rune
+}
+
+const (
+ spaceCategoryText = " "
+ wordCategoryText = "W"
+)
+
+var (
+ ecmaSpace = []rune{0x0009, 0x000e, 0x0020, 0x0021, 0x00a0, 0x00a1, 0x1680, 0x1681, 0x2000, 0x200b, 0x2028, 0x202a, 0x202f, 0x2030, 0x205f, 0x2060, 0x3000, 0x3001, 0xfeff, 0xff00}
+ ecmaWord = []rune{0x0030, 0x003a, 0x0041, 0x005b, 0x005f, 0x0060, 0x0061, 0x007b}
+ ecmaDigit = []rune{0x0030, 0x003a}
+
+ re2Space = []rune{0x0009, 0x000b, 0x000c, 0x000e, 0x0020, 0x0021}
+)
+
+var (
+ AnyClass = getCharSetFromOldString([]rune{0}, false)
+ ECMAAnyClass = getCharSetFromOldString([]rune{0, 0x000a, 0x000b, 0x000d, 0x000e}, false)
+ NoneClass = getCharSetFromOldString(nil, false)
+ ECMAWordClass = getCharSetFromOldString(ecmaWord, false)
+ NotECMAWordClass = getCharSetFromOldString(ecmaWord, true)
+ ECMASpaceClass = getCharSetFromOldString(ecmaSpace, false)
+ NotECMASpaceClass = getCharSetFromOldString(ecmaSpace, true)
+ ECMADigitClass = getCharSetFromOldString(ecmaDigit, false)
+ NotECMADigitClass = getCharSetFromOldString(ecmaDigit, true)
+
+ WordClass = getCharSetFromCategoryString(false, false, wordCategoryText)
+ NotWordClass = getCharSetFromCategoryString(true, false, wordCategoryText)
+ SpaceClass = getCharSetFromCategoryString(false, false, spaceCategoryText)
+ NotSpaceClass = getCharSetFromCategoryString(true, false, spaceCategoryText)
+ DigitClass = getCharSetFromCategoryString(false, false, "Nd")
+ NotDigitClass = getCharSetFromCategoryString(false, true, "Nd")
+
+ RE2SpaceClass = getCharSetFromOldString(re2Space, false)
+ NotRE2SpaceClass = getCharSetFromOldString(re2Space, true)
+)
+
+var unicodeCategories = func() map[string]*unicode.RangeTable {
+ retVal := make(map[string]*unicode.RangeTable)
+ for k, v := range unicode.Scripts {
+ retVal[k] = v
+ }
+ for k, v := range unicode.Categories {
+ retVal[k] = v
+ }
+ for k, v := range unicode.Properties {
+ retVal[k] = v
+ }
+ return retVal
+}()
+
+func getCharSetFromCategoryString(negateSet bool, negateCat bool, cats ...string) func() *CharSet {
+ if negateCat && negateSet {
+ panic("BUG! You should only negate the set OR the category in a constant setup, but not both")
+ }
+
+ c := CharSet{negate: negateSet}
+
+ c.categories = make([]category, len(cats))
+ for i, cat := range cats {
+ c.categories[i] = category{cat: cat, negate: negateCat}
+ }
+ return func() *CharSet {
+ //make a copy each time
+ local := c
+ //return that address
+ return &local
+ }
+}
+
+func getCharSetFromOldString(setText []rune, negate bool) func() *CharSet {
+ c := CharSet{}
+ if len(setText) > 0 {
+ fillFirst := false
+ l := len(setText)
+ if negate {
+ if setText[0] == 0 {
+ setText = setText[1:]
+ } else {
+ l++
+ fillFirst = true
+ }
+ }
+
+ if l%2 == 0 {
+ c.ranges = make([]singleRange, l/2)
+ } else {
+ c.ranges = make([]singleRange, l/2+1)
+ }
+
+ first := true
+ if fillFirst {
+ c.ranges[0] = singleRange{first: 0}
+ first = false
+ }
+
+ i := 0
+ for _, r := range setText {
+ if first {
+ // lower bound in a new range
+ c.ranges[i] = singleRange{first: r}
+ first = false
+ } else {
+ c.ranges[i].last = r - 1
+ i++
+ first = true
+ }
+ }
+ if !first {
+ c.ranges[i].last = utf8.MaxRune
+ }
+ }
+
+ return func() *CharSet {
+ local := c
+ return &local
+ }
+}
+
+// Copy makes a deep copy to prevent accidental mutation of a set
+func (c CharSet) Copy() CharSet {
+ ret := CharSet{
+ anything: c.anything,
+ negate: c.negate,
+ }
+
+ ret.ranges = append(ret.ranges, c.ranges...)
+ ret.categories = append(ret.categories, c.categories...)
+
+ if c.sub != nil {
+ sub := c.sub.Copy()
+ ret.sub = &sub
+ }
+
+ return ret
+}
+
+// gets a human-readable description for a set string
+func (c CharSet) String() string {
+ buf := &bytes.Buffer{}
+ buf.WriteRune('[')
+
+ if c.IsNegated() {
+ buf.WriteRune('^')
+ }
+
+ for _, r := range c.ranges {
+
+ buf.WriteString(CharDescription(r.first))
+ if r.first != r.last {
+ if r.last-r.first != 1 {
+ //groups that are 1 char apart skip the dash
+ buf.WriteRune('-')
+ }
+ buf.WriteString(CharDescription(r.last))
+ }
+ }
+
+ for _, c := range c.categories {
+ buf.WriteString(c.String())
+ }
+
+ if c.sub != nil {
+ buf.WriteRune('-')
+ buf.WriteString(c.sub.String())
+ }
+
+ buf.WriteRune(']')
+
+ return buf.String()
+}
+
+// mapHashFill converts a charset into a buffer for use in maps
+func (c CharSet) mapHashFill(buf *bytes.Buffer) {
+ if c.negate {
+ buf.WriteByte(0)
+ } else {
+ buf.WriteByte(1)
+ }
+
+ binary.Write(buf, binary.LittleEndian, len(c.ranges))
+ binary.Write(buf, binary.LittleEndian, len(c.categories))
+ for _, r := range c.ranges {
+ buf.WriteRune(r.first)
+ buf.WriteRune(r.last)
+ }
+ for _, ct := range c.categories {
+ buf.WriteString(ct.cat)
+ if ct.negate {
+ buf.WriteByte(1)
+ } else {
+ buf.WriteByte(0)
+ }
+ }
+
+ if c.sub != nil {
+ c.sub.mapHashFill(buf)
+ }
+}
+
+// CharIn returns true if the rune is in our character set (either ranges or categories).
+// It handles negations and subtracted sub-charsets.
+func (c CharSet) CharIn(ch rune) bool {
+ val := false
+ // in s && !s.subtracted
+
+ //check ranges
+ for _, r := range c.ranges {
+ if ch < r.first {
+ continue
+ }
+ if ch <= r.last {
+ val = true
+ break
+ }
+ }
+
+ //check categories if we haven't already found a range
+ if !val && len(c.categories) > 0 {
+ for _, ct := range c.categories {
+ // special categories...then unicode
+ if ct.cat == spaceCategoryText {
+ if unicode.IsSpace(ch) {
+ // we found a space so we're done
+ // negate means this is a "bad" thing
+ val = !ct.negate
+ break
+ } else if ct.negate {
+ val = true
+ break
+ }
+ } else if ct.cat == wordCategoryText {
+ if IsWordChar(ch) {
+ val = !ct.negate
+ break
+ } else if ct.negate {
+ val = true
+ break
+ }
+ } else if unicode.Is(unicodeCategories[ct.cat], ch) {
+ // if we're in this unicode category then we're done
+ // if negate=true on this category then we "failed" our test
+ // otherwise we're good that we found it
+ val = !ct.negate
+ break
+ } else if ct.negate {
+ val = true
+ break
+ }
+ }
+ }
+
+ // negate the whole char set
+ if c.negate {
+ val = !val
+ }
+
+ // get subtracted recurse
+ if val && c.sub != nil {
+ val = !c.sub.CharIn(ch)
+ }
+
+ //log.Printf("Char '%v' in %v == %v", string(ch), c.String(), val)
+ return val
+}
+
+func (c category) String() string {
+ switch c.cat {
+ case spaceCategoryText:
+ if c.negate {
+ return "\\S"
+ }
+ return "\\s"
+ case wordCategoryText:
+ if c.negate {
+ return "\\W"
+ }
+ return "\\w"
+ }
+ if _, ok := unicodeCategories[c.cat]; ok {
+
+ if c.negate {
+ return "\\P{" + c.cat + "}"
+ }
+ return "\\p{" + c.cat + "}"
+ }
+ return "Unknown category: " + c.cat
+}
+
+// CharDescription Produces a human-readable description for a single character.
+func CharDescription(ch rune) string {
+ /*if ch == '\\' {
+ return "\\\\"
+ }
+
+ if ch > ' ' && ch <= '~' {
+ return string(ch)
+ } else if ch == '\n' {
+ return "\\n"
+ } else if ch == ' ' {
+ return "\\ "
+ }*/
+
+ b := &bytes.Buffer{}
+ escape(b, ch, false) //fmt.Sprintf("%U", ch)
+ return b.String()
+}
+
+// According to UTS#18 Unicode Regular Expressions (http://www.unicode.org/reports/tr18/)
+// RL 1.4 Simple Word Boundaries The class of includes all Alphabetic
+// values from the Unicode character database, from UnicodeData.txt [UData], plus the U+200C
+// ZERO WIDTH NON-JOINER and U+200D ZERO WIDTH JOINER.
+func IsWordChar(r rune) bool {
+ //"L", "Mn", "Nd", "Pc"
+ return unicode.In(r,
+ unicode.Categories["L"], unicode.Categories["Mn"],
+ unicode.Categories["Nd"], unicode.Categories["Pc"]) || r == '\u200D' || r == '\u200C'
+ //return 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' || '0' <= r && r <= '9' || r == '_'
+}
+
+func IsECMAWordChar(r rune) bool {
+ return unicode.In(r,
+ unicode.Categories["L"], unicode.Categories["Mn"],
+ unicode.Categories["Nd"], unicode.Categories["Pc"])
+
+ //return 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' || '0' <= r && r <= '9' || r == '_'
+}
+
+// SingletonChar will return the char from the first range without validation.
+// It assumes you have checked for IsSingleton or IsSingletonInverse and will panic given bad input
+func (c CharSet) SingletonChar() rune {
+ return c.ranges[0].first
+}
+
+func (c CharSet) IsSingleton() bool {
+ return !c.negate && //negated is multiple chars
+ len(c.categories) == 0 && len(c.ranges) == 1 && // multiple ranges and unicode classes represent multiple chars
+ c.sub == nil && // subtraction means we've got multiple chars
+ c.ranges[0].first == c.ranges[0].last // first and last equal means we're just 1 char
+}
+
+func (c CharSet) IsSingletonInverse() bool {
+ return c.negate && //same as above, but requires negated
+ len(c.categories) == 0 && len(c.ranges) == 1 && // multiple ranges and unicode classes represent multiple chars
+ c.sub == nil && // subtraction means we've got multiple chars
+ c.ranges[0].first == c.ranges[0].last // first and last equal means we're just 1 char
+}
+
+func (c CharSet) IsMergeable() bool {
+ return !c.IsNegated() && !c.HasSubtraction()
+}
+
+func (c CharSet) IsNegated() bool {
+ return c.negate
+}
+
+func (c CharSet) HasSubtraction() bool {
+ return c.sub != nil
+}
+
+func (c CharSet) IsEmpty() bool {
+ return len(c.ranges) == 0 && len(c.categories) == 0 && c.sub == nil
+}
+
+func (c *CharSet) addDigit(ecma, negate bool, pattern string) {
+ if ecma {
+ if negate {
+ c.addRanges(NotECMADigitClass().ranges)
+ } else {
+ c.addRanges(ECMADigitClass().ranges)
+ }
+ } else {
+ c.addCategories(category{cat: "Nd", negate: negate})
+ }
+}
+
+func (c *CharSet) addChar(ch rune) {
+ c.addRange(ch, ch)
+}
+
+func (c *CharSet) addSpace(ecma, re2, negate bool) {
+ if ecma {
+ if negate {
+ c.addRanges(NotECMASpaceClass().ranges)
+ } else {
+ c.addRanges(ECMASpaceClass().ranges)
+ }
+ } else if re2 {
+ if negate {
+ c.addRanges(NotRE2SpaceClass().ranges)
+ } else {
+ c.addRanges(RE2SpaceClass().ranges)
+ }
+ } else {
+ c.addCategories(category{cat: spaceCategoryText, negate: negate})
+ }
+}
+
+func (c *CharSet) addWord(ecma, negate bool) {
+ if ecma {
+ if negate {
+ c.addRanges(NotECMAWordClass().ranges)
+ } else {
+ c.addRanges(ECMAWordClass().ranges)
+ }
+ } else {
+ c.addCategories(category{cat: wordCategoryText, negate: negate})
+ }
+}
+
+// Add set ranges and categories into ours -- no deduping or anything
+func (c *CharSet) addSet(set CharSet) {
+ if c.anything {
+ return
+ }
+ if set.anything {
+ c.makeAnything()
+ return
+ }
+ // just append here to prevent double-canon
+ c.ranges = append(c.ranges, set.ranges...)
+ c.addCategories(set.categories...)
+ c.canonicalize()
+}
+
+func (c *CharSet) makeAnything() {
+ c.anything = true
+ c.categories = []category{}
+ c.ranges = AnyClass().ranges
+}
+
+func (c *CharSet) addCategories(cats ...category) {
+ // don't add dupes and remove positive+negative
+ if c.anything {
+ // if we've had a previous positive+negative group then
+ // just return, we're as broad as we can get
+ return
+ }
+
+ for _, ct := range cats {
+ found := false
+ for _, ct2 := range c.categories {
+ if ct.cat == ct2.cat {
+ if ct.negate != ct2.negate {
+ // oposite negations...this mean we just
+ // take us as anything and move on
+ c.makeAnything()
+ return
+ }
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ c.categories = append(c.categories, ct)
+ }
+ }
+}
+
+// Merges new ranges to our own
+func (c *CharSet) addRanges(ranges []singleRange) {
+ if c.anything {
+ return
+ }
+ c.ranges = append(c.ranges, ranges...)
+ c.canonicalize()
+}
+
+// Merges everything but the new ranges into our own
+func (c *CharSet) addNegativeRanges(ranges []singleRange) {
+ if c.anything {
+ return
+ }
+
+ var hi rune
+
+ // convert incoming ranges into opposites, assume they are in order
+ for _, r := range ranges {
+ if hi < r.first {
+ c.ranges = append(c.ranges, singleRange{hi, r.first - 1})
+ }
+ hi = r.last + 1
+ }
+
+ if hi < utf8.MaxRune {
+ c.ranges = append(c.ranges, singleRange{hi, utf8.MaxRune})
+ }
+
+ c.canonicalize()
+}
+
+func isValidUnicodeCat(catName string) bool {
+ _, ok := unicodeCategories[catName]
+ return ok
+}
+
+func (c *CharSet) addCategory(categoryName string, negate, caseInsensitive bool, pattern string) {
+ if !isValidUnicodeCat(categoryName) {
+ // unknown unicode category, script, or property "blah"
+ panic(fmt.Errorf("Unknown unicode category, script, or property '%v'", categoryName))
+
+ }
+
+ if caseInsensitive && (categoryName == "Ll" || categoryName == "Lu" || categoryName == "Lt") {
+ // when RegexOptions.IgnoreCase is specified then {Ll} {Lu} and {Lt} cases should all match
+ c.addCategories(
+ category{cat: "Ll", negate: negate},
+ category{cat: "Lu", negate: negate},
+ category{cat: "Lt", negate: negate})
+ }
+ c.addCategories(category{cat: categoryName, negate: negate})
+}
+
+func (c *CharSet) addSubtraction(sub *CharSet) {
+ c.sub = sub
+}
+
+func (c *CharSet) addRange(chMin, chMax rune) {
+ c.ranges = append(c.ranges, singleRange{first: chMin, last: chMax})
+ c.canonicalize()
+}
+
+func (c *CharSet) addNamedASCII(name string, negate bool) bool {
+ var rs []singleRange
+
+ switch name {
+ case "alnum":
+ rs = []singleRange{singleRange{'0', '9'}, singleRange{'A', 'Z'}, singleRange{'a', 'z'}}
+ case "alpha":
+ rs = []singleRange{singleRange{'A', 'Z'}, singleRange{'a', 'z'}}
+ case "ascii":
+ rs = []singleRange{singleRange{0, 0x7f}}
+ case "blank":
+ rs = []singleRange{singleRange{'\t', '\t'}, singleRange{' ', ' '}}
+ case "cntrl":
+ rs = []singleRange{singleRange{0, 0x1f}, singleRange{0x7f, 0x7f}}
+ case "digit":
+ c.addDigit(false, negate, "")
+ case "graph":
+ rs = []singleRange{singleRange{'!', '~'}}
+ case "lower":
+ rs = []singleRange{singleRange{'a', 'z'}}
+ case "print":
+ rs = []singleRange{singleRange{' ', '~'}}
+ case "punct": //[!-/:-@[-`{-~]
+ rs = []singleRange{singleRange{'!', '/'}, singleRange{':', '@'}, singleRange{'[', '`'}, singleRange{'{', '~'}}
+ case "space":
+ c.addSpace(true, false, negate)
+ case "upper":
+ rs = []singleRange{singleRange{'A', 'Z'}}
+ case "word":
+ c.addWord(true, negate)
+ case "xdigit":
+ rs = []singleRange{singleRange{'0', '9'}, singleRange{'A', 'F'}, singleRange{'a', 'f'}}
+ default:
+ return false
+ }
+
+ if len(rs) > 0 {
+ if negate {
+ c.addNegativeRanges(rs)
+ } else {
+ c.addRanges(rs)
+ }
+ }
+
+ return true
+}
+
+type singleRangeSorter []singleRange
+
+func (p singleRangeSorter) Len() int { return len(p) }
+func (p singleRangeSorter) Less(i, j int) bool { return p[i].first < p[j].first }
+func (p singleRangeSorter) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+// Logic to reduce a character class to a unique, sorted form.
+func (c *CharSet) canonicalize() {
+ var i, j int
+ var last rune
+
+ //
+ // Find and eliminate overlapping or abutting ranges
+ //
+
+ if len(c.ranges) > 1 {
+ sort.Sort(singleRangeSorter(c.ranges))
+
+ done := false
+
+ for i, j = 1, 0; ; i++ {
+ for last = c.ranges[j].last; ; i++ {
+ if i == len(c.ranges) || last == utf8.MaxRune {
+ done = true
+ break
+ }
+
+ CurrentRange := c.ranges[i]
+ if CurrentRange.first > last+1 {
+ break
+ }
+
+ if last < CurrentRange.last {
+ last = CurrentRange.last
+ }
+ }
+
+ c.ranges[j] = singleRange{first: c.ranges[j].first, last: last}
+
+ j++
+
+ if done {
+ break
+ }
+
+ if j < i {
+ c.ranges[j] = c.ranges[i]
+ }
+ }
+
+ c.ranges = append(c.ranges[:j], c.ranges[len(c.ranges):]...)
+ }
+}
+
+// Adds to the class any lowercase versions of characters already
+// in the class. Used for case-insensitivity.
+func (c *CharSet) addLowercase() {
+ if c.anything {
+ return
+ }
+ toAdd := []singleRange{}
+ for i := 0; i < len(c.ranges); i++ {
+ r := c.ranges[i]
+ if r.first == r.last {
+ lower := unicode.ToLower(r.first)
+ c.ranges[i] = singleRange{first: lower, last: lower}
+ } else {
+ toAdd = append(toAdd, r)
+ }
+ }
+
+ for _, r := range toAdd {
+ c.addLowercaseRange(r.first, r.last)
+ }
+ c.canonicalize()
+}
+
+/**************************************************************************
+ Let U be the set of Unicode character values and let L be the lowercase
+ function, mapping from U to U. To perform case insensitive matching of
+ character sets, we need to be able to map an interval I in U, say
+
+ I = [chMin, chMax] = { ch : chMin <= ch <= chMax }
+
+ to a set A such that A contains L(I) and A is contained in the union of
+ I and L(I).
+
+ The table below partitions U into intervals on which L is non-decreasing.
+ Thus, for any interval J = [a, b] contained in one of these intervals,
+ L(J) is contained in [L(a), L(b)].
+
+ It is also true that for any such J, [L(a), L(b)] is contained in the
+ union of J and L(J). This does not follow from L being non-decreasing on
+ these intervals. It follows from the nature of the L on each interval.
+ On each interval, L has one of the following forms:
+
+ (1) L(ch) = constant (LowercaseSet)
+ (2) L(ch) = ch + offset (LowercaseAdd)
+ (3) L(ch) = ch | 1 (LowercaseBor)
+ (4) L(ch) = ch + (ch & 1) (LowercaseBad)
+
+ It is easy to verify that for any of these forms [L(a), L(b)] is
+ contained in the union of [a, b] and L([a, b]).
+***************************************************************************/
+
+const (
+ LowercaseSet = 0 // Set to arg.
+ LowercaseAdd = 1 // Add arg.
+ LowercaseBor = 2 // Bitwise or with 1.
+ LowercaseBad = 3 // Bitwise and with 1 and add original.
+)
+
+type lcMap struct {
+ chMin, chMax rune
+ op, data int32
+}
+
+var lcTable = []lcMap{
+ lcMap{'\u0041', '\u005A', LowercaseAdd, 32},
+ lcMap{'\u00C0', '\u00DE', LowercaseAdd, 32},
+ lcMap{'\u0100', '\u012E', LowercaseBor, 0},
+ lcMap{'\u0130', '\u0130', LowercaseSet, 0x0069},
+ lcMap{'\u0132', '\u0136', LowercaseBor, 0},
+ lcMap{'\u0139', '\u0147', LowercaseBad, 0},
+ lcMap{'\u014A', '\u0176', LowercaseBor, 0},
+ lcMap{'\u0178', '\u0178', LowercaseSet, 0x00FF},
+ lcMap{'\u0179', '\u017D', LowercaseBad, 0},
+ lcMap{'\u0181', '\u0181', LowercaseSet, 0x0253},
+ lcMap{'\u0182', '\u0184', LowercaseBor, 0},
+ lcMap{'\u0186', '\u0186', LowercaseSet, 0x0254},
+ lcMap{'\u0187', '\u0187', LowercaseSet, 0x0188},
+ lcMap{'\u0189', '\u018A', LowercaseAdd, 205},
+ lcMap{'\u018B', '\u018B', LowercaseSet, 0x018C},
+ lcMap{'\u018E', '\u018E', LowercaseSet, 0x01DD},
+ lcMap{'\u018F', '\u018F', LowercaseSet, 0x0259},
+ lcMap{'\u0190', '\u0190', LowercaseSet, 0x025B},
+ lcMap{'\u0191', '\u0191', LowercaseSet, 0x0192},
+ lcMap{'\u0193', '\u0193', LowercaseSet, 0x0260},
+ lcMap{'\u0194', '\u0194', LowercaseSet, 0x0263},
+ lcMap{'\u0196', '\u0196', LowercaseSet, 0x0269},
+ lcMap{'\u0197', '\u0197', LowercaseSet, 0x0268},
+ lcMap{'\u0198', '\u0198', LowercaseSet, 0x0199},
+ lcMap{'\u019C', '\u019C', LowercaseSet, 0x026F},
+ lcMap{'\u019D', '\u019D', LowercaseSet, 0x0272},
+ lcMap{'\u019F', '\u019F', LowercaseSet, 0x0275},
+ lcMap{'\u01A0', '\u01A4', LowercaseBor, 0},
+ lcMap{'\u01A7', '\u01A7', LowercaseSet, 0x01A8},
+ lcMap{'\u01A9', '\u01A9', LowercaseSet, 0x0283},
+ lcMap{'\u01AC', '\u01AC', LowercaseSet, 0x01AD},
+ lcMap{'\u01AE', '\u01AE', LowercaseSet, 0x0288},
+ lcMap{'\u01AF', '\u01AF', LowercaseSet, 0x01B0},
+ lcMap{'\u01B1', '\u01B2', LowercaseAdd, 217},
+ lcMap{'\u01B3', '\u01B5', LowercaseBad, 0},
+ lcMap{'\u01B7', '\u01B7', LowercaseSet, 0x0292},
+ lcMap{'\u01B8', '\u01B8', LowercaseSet, 0x01B9},
+ lcMap{'\u01BC', '\u01BC', LowercaseSet, 0x01BD},
+ lcMap{'\u01C4', '\u01C5', LowercaseSet, 0x01C6},
+ lcMap{'\u01C7', '\u01C8', LowercaseSet, 0x01C9},
+ lcMap{'\u01CA', '\u01CB', LowercaseSet, 0x01CC},
+ lcMap{'\u01CD', '\u01DB', LowercaseBad, 0},
+ lcMap{'\u01DE', '\u01EE', LowercaseBor, 0},
+ lcMap{'\u01F1', '\u01F2', LowercaseSet, 0x01F3},
+ lcMap{'\u01F4', '\u01F4', LowercaseSet, 0x01F5},
+ lcMap{'\u01FA', '\u0216', LowercaseBor, 0},
+ lcMap{'\u0386', '\u0386', LowercaseSet, 0x03AC},
+ lcMap{'\u0388', '\u038A', LowercaseAdd, 37},
+ lcMap{'\u038C', '\u038C', LowercaseSet, 0x03CC},
+ lcMap{'\u038E', '\u038F', LowercaseAdd, 63},
+ lcMap{'\u0391', '\u03AB', LowercaseAdd, 32},
+ lcMap{'\u03E2', '\u03EE', LowercaseBor, 0},
+ lcMap{'\u0401', '\u040F', LowercaseAdd, 80},
+ lcMap{'\u0410', '\u042F', LowercaseAdd, 32},
+ lcMap{'\u0460', '\u0480', LowercaseBor, 0},
+ lcMap{'\u0490', '\u04BE', LowercaseBor, 0},
+ lcMap{'\u04C1', '\u04C3', LowercaseBad, 0},
+ lcMap{'\u04C7', '\u04C7', LowercaseSet, 0x04C8},
+ lcMap{'\u04CB', '\u04CB', LowercaseSet, 0x04CC},
+ lcMap{'\u04D0', '\u04EA', LowercaseBor, 0},
+ lcMap{'\u04EE', '\u04F4', LowercaseBor, 0},
+ lcMap{'\u04F8', '\u04F8', LowercaseSet, 0x04F9},
+ lcMap{'\u0531', '\u0556', LowercaseAdd, 48},
+ lcMap{'\u10A0', '\u10C5', LowercaseAdd, 48},
+ lcMap{'\u1E00', '\u1EF8', LowercaseBor, 0},
+ lcMap{'\u1F08', '\u1F0F', LowercaseAdd, -8},
+ lcMap{'\u1F18', '\u1F1F', LowercaseAdd, -8},
+ lcMap{'\u1F28', '\u1F2F', LowercaseAdd, -8},
+ lcMap{'\u1F38', '\u1F3F', LowercaseAdd, -8},
+ lcMap{'\u1F48', '\u1F4D', LowercaseAdd, -8},
+ lcMap{'\u1F59', '\u1F59', LowercaseSet, 0x1F51},
+ lcMap{'\u1F5B', '\u1F5B', LowercaseSet, 0x1F53},
+ lcMap{'\u1F5D', '\u1F5D', LowercaseSet, 0x1F55},
+ lcMap{'\u1F5F', '\u1F5F', LowercaseSet, 0x1F57},
+ lcMap{'\u1F68', '\u1F6F', LowercaseAdd, -8},
+ lcMap{'\u1F88', '\u1F8F', LowercaseAdd, -8},
+ lcMap{'\u1F98', '\u1F9F', LowercaseAdd, -8},
+ lcMap{'\u1FA8', '\u1FAF', LowercaseAdd, -8},
+ lcMap{'\u1FB8', '\u1FB9', LowercaseAdd, -8},
+ lcMap{'\u1FBA', '\u1FBB', LowercaseAdd, -74},
+ lcMap{'\u1FBC', '\u1FBC', LowercaseSet, 0x1FB3},
+ lcMap{'\u1FC8', '\u1FCB', LowercaseAdd, -86},
+ lcMap{'\u1FCC', '\u1FCC', LowercaseSet, 0x1FC3},
+ lcMap{'\u1FD8', '\u1FD9', LowercaseAdd, -8},
+ lcMap{'\u1FDA', '\u1FDB', LowercaseAdd, -100},
+ lcMap{'\u1FE8', '\u1FE9', LowercaseAdd, -8},
+ lcMap{'\u1FEA', '\u1FEB', LowercaseAdd, -112},
+ lcMap{'\u1FEC', '\u1FEC', LowercaseSet, 0x1FE5},
+ lcMap{'\u1FF8', '\u1FF9', LowercaseAdd, -128},
+ lcMap{'\u1FFA', '\u1FFB', LowercaseAdd, -126},
+ lcMap{'\u1FFC', '\u1FFC', LowercaseSet, 0x1FF3},
+ lcMap{'\u2160', '\u216F', LowercaseAdd, 16},
+ lcMap{'\u24B6', '\u24D0', LowercaseAdd, 26},
+ lcMap{'\uFF21', '\uFF3A', LowercaseAdd, 32},
+}
+
+func (c *CharSet) addLowercaseRange(chMin, chMax rune) {
+ var i, iMax, iMid int
+ var chMinT, chMaxT rune
+ var lc lcMap
+
+ for i, iMax = 0, len(lcTable); i < iMax; {
+ iMid = (i + iMax) / 2
+ if lcTable[iMid].chMax < chMin {
+ i = iMid + 1
+ } else {
+ iMax = iMid
+ }
+ }
+
+ for ; i < len(lcTable); i++ {
+ lc = lcTable[i]
+ if lc.chMin > chMax {
+ return
+ }
+ chMinT = lc.chMin
+ if chMinT < chMin {
+ chMinT = chMin
+ }
+
+ chMaxT = lc.chMax
+ if chMaxT > chMax {
+ chMaxT = chMax
+ }
+
+ switch lc.op {
+ case LowercaseSet:
+ chMinT = rune(lc.data)
+ chMaxT = rune(lc.data)
+ break
+ case LowercaseAdd:
+ chMinT += lc.data
+ chMaxT += lc.data
+ break
+ case LowercaseBor:
+ chMinT |= 1
+ chMaxT |= 1
+ break
+ case LowercaseBad:
+ chMinT += (chMinT & 1)
+ chMaxT += (chMaxT & 1)
+ break
+ }
+
+ if chMinT < chMin || chMaxT > chMax {
+ c.addRange(chMinT, chMaxT)
+ }
+ }
+}
diff --git a/vendor/github.com/dlclark/regexp2/syntax/code.go b/vendor/github.com/dlclark/regexp2/syntax/code.go
new file mode 100644
index 00000000..686e822a
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/syntax/code.go
@@ -0,0 +1,274 @@
+package syntax
+
+import (
+ "bytes"
+ "fmt"
+ "math"
+)
+
+// similar to prog.go in the go regex package...also with comment 'may not belong in this package'
+
+// File provides operator constants for use by the Builder and the Machine.
+
+// Implementation notes:
+//
+// Regexps are built into RegexCodes, which contain an operation array,
+// a string table, and some constants.
+//
+// Each operation is one of the codes below, followed by the integer
+// operands specified for each op.
+//
+// Strings and sets are indices into a string table.
+
+type InstOp int
+
+const (
+ // lef/back operands description
+
+ Onerep InstOp = 0 // lef,back char,min,max a {n}
+ Notonerep = 1 // lef,back char,min,max .{n}
+ Setrep = 2 // lef,back set,min,max [\d]{n}
+
+ Oneloop = 3 // lef,back char,min,max a {,n}
+ Notoneloop = 4 // lef,back char,min,max .{,n}
+ Setloop = 5 // lef,back set,min,max [\d]{,n}
+
+ Onelazy = 6 // lef,back char,min,max a {,n}?
+ Notonelazy = 7 // lef,back char,min,max .{,n}?
+ Setlazy = 8 // lef,back set,min,max [\d]{,n}?
+
+ One = 9 // lef char a
+ Notone = 10 // lef char [^a]
+ Set = 11 // lef set [a-z\s] \w \s \d
+
+ Multi = 12 // lef string abcd
+ Ref = 13 // lef group \#
+
+ Bol = 14 // ^
+ Eol = 15 // $
+ Boundary = 16 // \b
+ Nonboundary = 17 // \B
+ Beginning = 18 // \A
+ Start = 19 // \G
+ EndZ = 20 // \Z
+ End = 21 // \Z
+
+ Nothing = 22 // Reject!
+
+ // Primitive control structures
+
+ Lazybranch = 23 // back jump straight first
+ Branchmark = 24 // back jump branch first for loop
+ Lazybranchmark = 25 // back jump straight first for loop
+ Nullcount = 26 // back val set counter, null mark
+ Setcount = 27 // back val set counter, make mark
+ Branchcount = 28 // back jump,limit branch++ if zero<=c impl group slots
+ Capsize int // number of impl group slots
+ FcPrefix *Prefix // the set of candidate first characters (may be null)
+ BmPrefix *BmPrefix // the fixed prefix string as a Boyer-Moore machine (may be null)
+ Anchors AnchorLoc // the set of zero-length start anchors (RegexFCD.Bol, etc)
+ RightToLeft bool // true if right to left
+}
+
+func opcodeBacktracks(op InstOp) bool {
+ op &= Mask
+
+ switch op {
+ case Oneloop, Notoneloop, Setloop, Onelazy, Notonelazy, Setlazy, Lazybranch, Branchmark, Lazybranchmark,
+ Nullcount, Setcount, Branchcount, Lazybranchcount, Setmark, Capturemark, Getmark, Setjump, Backjump,
+ Forejump, Goto:
+ return true
+
+ default:
+ return false
+ }
+}
+
+func opcodeSize(op InstOp) int {
+ op &= Mask
+
+ switch op {
+ case Nothing, Bol, Eol, Boundary, Nonboundary, ECMABoundary, NonECMABoundary, Beginning, Start, EndZ,
+ End, Nullmark, Setmark, Getmark, Setjump, Backjump, Forejump, Stop:
+ return 1
+
+ case One, Notone, Multi, Ref, Testref, Goto, Nullcount, Setcount, Lazybranch, Branchmark, Lazybranchmark,
+ Prune, Set:
+ return 2
+
+ case Capturemark, Branchcount, Lazybranchcount, Onerep, Notonerep, Oneloop, Notoneloop, Onelazy, Notonelazy,
+ Setlazy, Setrep, Setloop:
+ return 3
+
+ default:
+ panic(fmt.Errorf("Unexpected op code: %v", op))
+ }
+}
+
+var codeStr = []string{
+ "Onerep", "Notonerep", "Setrep",
+ "Oneloop", "Notoneloop", "Setloop",
+ "Onelazy", "Notonelazy", "Setlazy",
+ "One", "Notone", "Set",
+ "Multi", "Ref",
+ "Bol", "Eol", "Boundary", "Nonboundary", "Beginning", "Start", "EndZ", "End",
+ "Nothing",
+ "Lazybranch", "Branchmark", "Lazybranchmark",
+ "Nullcount", "Setcount", "Branchcount", "Lazybranchcount",
+ "Nullmark", "Setmark", "Capturemark", "Getmark",
+ "Setjump", "Backjump", "Forejump", "Testref", "Goto",
+ "Prune", "Stop",
+ "ECMABoundary", "NonECMABoundary",
+}
+
+func operatorDescription(op InstOp) string {
+ desc := codeStr[op&Mask]
+ if (op & Ci) != 0 {
+ desc += "-Ci"
+ }
+ if (op & Rtl) != 0 {
+ desc += "-Rtl"
+ }
+ if (op & Back) != 0 {
+ desc += "-Back"
+ }
+ if (op & Back2) != 0 {
+ desc += "-Back2"
+ }
+
+ return desc
+}
+
+// OpcodeDescription is a humman readable string of the specific offset
+func (c *Code) OpcodeDescription(offset int) string {
+ buf := &bytes.Buffer{}
+
+ op := InstOp(c.Codes[offset])
+ fmt.Fprintf(buf, "%06d ", offset)
+
+ if opcodeBacktracks(op & Mask) {
+ buf.WriteString("*")
+ } else {
+ buf.WriteString(" ")
+ }
+ buf.WriteString(operatorDescription(op))
+ buf.WriteString("(")
+ op &= Mask
+
+ switch op {
+ case One, Notone, Onerep, Notonerep, Oneloop, Notoneloop, Onelazy, Notonelazy:
+ buf.WriteString("Ch = ")
+ buf.WriteString(CharDescription(rune(c.Codes[offset+1])))
+
+ case Set, Setrep, Setloop, Setlazy:
+ buf.WriteString("Set = ")
+ buf.WriteString(c.Sets[c.Codes[offset+1]].String())
+
+ case Multi:
+ fmt.Fprintf(buf, "String = %s", string(c.Strings[c.Codes[offset+1]]))
+
+ case Ref, Testref:
+ fmt.Fprintf(buf, "Index = %d", c.Codes[offset+1])
+
+ case Capturemark:
+ fmt.Fprintf(buf, "Index = %d", c.Codes[offset+1])
+ if c.Codes[offset+2] != -1 {
+ fmt.Fprintf(buf, ", Unindex = %d", c.Codes[offset+2])
+ }
+
+ case Nullcount, Setcount:
+ fmt.Fprintf(buf, "Value = %d", c.Codes[offset+1])
+
+ case Goto, Lazybranch, Branchmark, Lazybranchmark, Branchcount, Lazybranchcount:
+ fmt.Fprintf(buf, "Addr = %d", c.Codes[offset+1])
+ }
+
+ switch op {
+ case Onerep, Notonerep, Oneloop, Notoneloop, Onelazy, Notonelazy, Setrep, Setloop, Setlazy:
+ buf.WriteString(", Rep = ")
+ if c.Codes[offset+2] == math.MaxInt32 {
+ buf.WriteString("inf")
+ } else {
+ fmt.Fprintf(buf, "%d", c.Codes[offset+2])
+ }
+
+ case Branchcount, Lazybranchcount:
+ buf.WriteString(", Limit = ")
+ if c.Codes[offset+2] == math.MaxInt32 {
+ buf.WriteString("inf")
+ } else {
+ fmt.Fprintf(buf, "%d", c.Codes[offset+2])
+ }
+
+ }
+
+ buf.WriteString(")")
+
+ return buf.String()
+}
+
+func (c *Code) Dump() string {
+ buf := &bytes.Buffer{}
+
+ if c.RightToLeft {
+ fmt.Fprintln(buf, "Direction: right-to-left")
+ } else {
+ fmt.Fprintln(buf, "Direction: left-to-right")
+ }
+ if c.FcPrefix == nil {
+ fmt.Fprintln(buf, "Firstchars: n/a")
+ } else {
+ fmt.Fprintf(buf, "Firstchars: %v\n", c.FcPrefix.PrefixSet.String())
+ }
+
+ if c.BmPrefix == nil {
+ fmt.Fprintln(buf, "Prefix: n/a")
+ } else {
+ fmt.Fprintf(buf, "Prefix: %v\n", Escape(c.BmPrefix.String()))
+ }
+
+ fmt.Fprintf(buf, "Anchors: %v\n", c.Anchors)
+ fmt.Fprintln(buf)
+
+ if c.BmPrefix != nil {
+ fmt.Fprintln(buf, "BoyerMoore:")
+ fmt.Fprintln(buf, c.BmPrefix.Dump(" "))
+ }
+ for i := 0; i < len(c.Codes); i += opcodeSize(InstOp(c.Codes[i])) {
+ fmt.Fprintln(buf, c.OpcodeDescription(i))
+ }
+
+ return buf.String()
+}
diff --git a/vendor/github.com/dlclark/regexp2/syntax/escape.go b/vendor/github.com/dlclark/regexp2/syntax/escape.go
new file mode 100644
index 00000000..609df107
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/syntax/escape.go
@@ -0,0 +1,94 @@
+package syntax
+
+import (
+ "bytes"
+ "strconv"
+ "strings"
+ "unicode"
+)
+
+func Escape(input string) string {
+ b := &bytes.Buffer{}
+ for _, r := range input {
+ escape(b, r, false)
+ }
+ return b.String()
+}
+
+const meta = `\.+*?()|[]{}^$# `
+
+func escape(b *bytes.Buffer, r rune, force bool) {
+ if unicode.IsPrint(r) {
+ if strings.IndexRune(meta, r) >= 0 || force {
+ b.WriteRune('\\')
+ }
+ b.WriteRune(r)
+ return
+ }
+
+ switch r {
+ case '\a':
+ b.WriteString(`\a`)
+ case '\f':
+ b.WriteString(`\f`)
+ case '\n':
+ b.WriteString(`\n`)
+ case '\r':
+ b.WriteString(`\r`)
+ case '\t':
+ b.WriteString(`\t`)
+ case '\v':
+ b.WriteString(`\v`)
+ default:
+ if r < 0x100 {
+ b.WriteString(`\x`)
+ s := strconv.FormatInt(int64(r), 16)
+ if len(s) == 1 {
+ b.WriteRune('0')
+ }
+ b.WriteString(s)
+ break
+ }
+ b.WriteString(`\u`)
+ b.WriteString(strconv.FormatInt(int64(r), 16))
+ }
+}
+
+func Unescape(input string) (string, error) {
+ idx := strings.IndexRune(input, '\\')
+ // no slashes means no unescape needed
+ if idx == -1 {
+ return input, nil
+ }
+
+ buf := bytes.NewBufferString(input[:idx])
+ // get the runes for the rest of the string -- we're going full parser scan on this
+
+ p := parser{}
+ p.setPattern(input[idx+1:])
+ for {
+ if p.rightMost() {
+ return "", p.getErr(ErrIllegalEndEscape)
+ }
+ r, err := p.scanCharEscape()
+ if err != nil {
+ return "", err
+ }
+ buf.WriteRune(r)
+ // are we done?
+ if p.rightMost() {
+ return buf.String(), nil
+ }
+
+ r = p.moveRightGetChar()
+ for r != '\\' {
+ buf.WriteRune(r)
+ if p.rightMost() {
+ // we're done, no more slashes
+ return buf.String(), nil
+ }
+ // keep scanning until we get another slash
+ r = p.moveRightGetChar()
+ }
+ }
+}
diff --git a/vendor/github.com/dlclark/regexp2/syntax/fuzz.go b/vendor/github.com/dlclark/regexp2/syntax/fuzz.go
new file mode 100644
index 00000000..ee863866
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/syntax/fuzz.go
@@ -0,0 +1,20 @@
+// +build gofuzz
+
+package syntax
+
+// Fuzz is the input point for go-fuzz
+func Fuzz(data []byte) int {
+ sdata := string(data)
+ tree, err := Parse(sdata, RegexOptions(0))
+ if err != nil {
+ return 0
+ }
+
+ // translate it to code
+ _, err = Write(tree)
+ if err != nil {
+ panic(err)
+ }
+
+ return 1
+}
diff --git a/vendor/github.com/dlclark/regexp2/syntax/parser.go b/vendor/github.com/dlclark/regexp2/syntax/parser.go
new file mode 100644
index 00000000..b6c3670c
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/syntax/parser.go
@@ -0,0 +1,2262 @@
+package syntax
+
+import (
+ "fmt"
+ "math"
+ "os"
+ "sort"
+ "strconv"
+ "unicode"
+)
+
+type RegexOptions int32
+
+const (
+ IgnoreCase RegexOptions = 0x0001 // "i"
+ Multiline = 0x0002 // "m"
+ ExplicitCapture = 0x0004 // "n"
+ Compiled = 0x0008 // "c"
+ Singleline = 0x0010 // "s"
+ IgnorePatternWhitespace = 0x0020 // "x"
+ RightToLeft = 0x0040 // "r"
+ Debug = 0x0080 // "d"
+ ECMAScript = 0x0100 // "e"
+ RE2 = 0x0200 // RE2 compat mode
+ Unicode = 0x0400 // "u"
+)
+
+func optionFromCode(ch rune) RegexOptions {
+ // case-insensitive
+ switch ch {
+ case 'i', 'I':
+ return IgnoreCase
+ case 'r', 'R':
+ return RightToLeft
+ case 'm', 'M':
+ return Multiline
+ case 'n', 'N':
+ return ExplicitCapture
+ case 's', 'S':
+ return Singleline
+ case 'x', 'X':
+ return IgnorePatternWhitespace
+ case 'd', 'D':
+ return Debug
+ case 'e', 'E':
+ return ECMAScript
+ case 'u', 'U':
+ return Unicode
+ default:
+ return 0
+ }
+}
+
+// An Error describes a failure to parse a regular expression
+// and gives the offending expression.
+type Error struct {
+ Code ErrorCode
+ Expr string
+ Args []interface{}
+}
+
+func (e *Error) Error() string {
+ if len(e.Args) == 0 {
+ return "error parsing regexp: " + e.Code.String() + " in `" + e.Expr + "`"
+ }
+ return "error parsing regexp: " + fmt.Sprintf(e.Code.String(), e.Args...) + " in `" + e.Expr + "`"
+}
+
+// An ErrorCode describes a failure to parse a regular expression.
+type ErrorCode string
+
+const (
+ // internal issue
+ ErrInternalError ErrorCode = "regexp/syntax: internal error"
+ // Parser errors
+ ErrUnterminatedComment = "unterminated comment"
+ ErrInvalidCharRange = "invalid character class range"
+ ErrInvalidRepeatSize = "invalid repeat count"
+ ErrInvalidUTF8 = "invalid UTF-8"
+ ErrCaptureGroupOutOfRange = "capture group number out of range"
+ ErrUnexpectedParen = "unexpected )"
+ ErrMissingParen = "missing closing )"
+ ErrMissingBrace = "missing closing }"
+ ErrInvalidRepeatOp = "invalid nested repetition operator"
+ ErrMissingRepeatArgument = "missing argument to repetition operator"
+ ErrConditionalExpression = "illegal conditional (?(...)) expression"
+ ErrTooManyAlternates = "too many | in (?()|)"
+ ErrUnrecognizedGrouping = "unrecognized grouping construct: (%v"
+ ErrInvalidGroupName = "invalid group name: group names must begin with a word character and have a matching terminator"
+ ErrCapNumNotZero = "capture number cannot be zero"
+ ErrUndefinedBackRef = "reference to undefined group number %v"
+ ErrUndefinedNameRef = "reference to undefined group name %v"
+ ErrAlternationCantCapture = "alternation conditions do not capture and cannot be named"
+ ErrAlternationCantHaveComment = "alternation conditions cannot be comments"
+ ErrMalformedReference = "(?(%v) ) malformed"
+ ErrUndefinedReference = "(?(%v) ) reference to undefined group"
+ ErrIllegalEndEscape = "illegal \\ at end of pattern"
+ ErrMalformedSlashP = "malformed \\p{X} character escape"
+ ErrIncompleteSlashP = "incomplete \\p{X} character escape"
+ ErrUnknownSlashP = "unknown unicode category, script, or property '%v'"
+ ErrUnrecognizedEscape = "unrecognized escape sequence \\%v"
+ ErrMissingControl = "missing control character"
+ ErrUnrecognizedControl = "unrecognized control character"
+ ErrTooFewHex = "insufficient hexadecimal digits"
+ ErrInvalidHex = "hex values may not be larger than 0x10FFFF"
+ ErrMalformedNameRef = "malformed \\k<...> named back reference"
+ ErrBadClassInCharRange = "cannot include class \\%v in character range"
+ ErrUnterminatedBracket = "unterminated [] set"
+ ErrSubtractionMustBeLast = "a subtraction must be the last element in a character class"
+ ErrReversedCharRange = "[%c-%c] range in reverse order"
+)
+
+func (e ErrorCode) String() string {
+ return string(e)
+}
+
+type parser struct {
+ stack *regexNode
+ group *regexNode
+ alternation *regexNode
+ concatenation *regexNode
+ unit *regexNode
+
+ patternRaw string
+ pattern []rune
+
+ currentPos int
+ specialCase *unicode.SpecialCase
+
+ autocap int
+ capcount int
+ captop int
+ capsize int
+
+ caps map[int]int
+ capnames map[string]int
+
+ capnumlist []int
+ capnamelist []string
+
+ options RegexOptions
+ optionsStack []RegexOptions
+ ignoreNextParen bool
+}
+
+const (
+ maxValueDiv10 int = math.MaxInt32 / 10
+ maxValueMod10 = math.MaxInt32 % 10
+)
+
+// Parse converts a regex string into a parse tree
+func Parse(re string, op RegexOptions) (*RegexTree, error) {
+ p := parser{
+ options: op,
+ caps: make(map[int]int),
+ }
+ p.setPattern(re)
+
+ if err := p.countCaptures(); err != nil {
+ return nil, err
+ }
+
+ p.reset(op)
+ root, err := p.scanRegex()
+
+ if err != nil {
+ return nil, err
+ }
+ tree := &RegexTree{
+ root: root,
+ caps: p.caps,
+ capnumlist: p.capnumlist,
+ captop: p.captop,
+ Capnames: p.capnames,
+ Caplist: p.capnamelist,
+ options: op,
+ }
+
+ if tree.options&Debug > 0 {
+ os.Stdout.WriteString(tree.Dump())
+ }
+
+ return tree, nil
+}
+
+func (p *parser) setPattern(pattern string) {
+ p.patternRaw = pattern
+ p.pattern = make([]rune, 0, len(pattern))
+
+ //populate our rune array to handle utf8 encoding
+ for _, r := range pattern {
+ p.pattern = append(p.pattern, r)
+ }
+}
+func (p *parser) getErr(code ErrorCode, args ...interface{}) error {
+ return &Error{Code: code, Expr: p.patternRaw, Args: args}
+}
+
+func (p *parser) noteCaptureSlot(i, pos int) {
+ if _, ok := p.caps[i]; !ok {
+ // the rhs of the hashtable isn't used in the parser
+ p.caps[i] = pos
+ p.capcount++
+
+ if p.captop <= i {
+ if i == math.MaxInt32 {
+ p.captop = i
+ } else {
+ p.captop = i + 1
+ }
+ }
+ }
+}
+
+func (p *parser) noteCaptureName(name string, pos int) {
+ if p.capnames == nil {
+ p.capnames = make(map[string]int)
+ }
+
+ if _, ok := p.capnames[name]; !ok {
+ p.capnames[name] = pos
+ p.capnamelist = append(p.capnamelist, name)
+ }
+}
+
+func (p *parser) assignNameSlots() {
+ if p.capnames != nil {
+ for _, name := range p.capnamelist {
+ for p.isCaptureSlot(p.autocap) {
+ p.autocap++
+ }
+ pos := p.capnames[name]
+ p.capnames[name] = p.autocap
+ p.noteCaptureSlot(p.autocap, pos)
+
+ p.autocap++
+ }
+ }
+
+ // if the caps array has at least one gap, construct the list of used slots
+ if p.capcount < p.captop {
+ p.capnumlist = make([]int, p.capcount)
+ i := 0
+
+ for k := range p.caps {
+ p.capnumlist[i] = k
+ i++
+ }
+
+ sort.Ints(p.capnumlist)
+ }
+
+ // merge capsnumlist into capnamelist
+ if p.capnames != nil || p.capnumlist != nil {
+ var oldcapnamelist []string
+ var next int
+ var k int
+
+ if p.capnames == nil {
+ oldcapnamelist = nil
+ p.capnames = make(map[string]int)
+ p.capnamelist = []string{}
+ next = -1
+ } else {
+ oldcapnamelist = p.capnamelist
+ p.capnamelist = []string{}
+ next = p.capnames[oldcapnamelist[0]]
+ }
+
+ for i := 0; i < p.capcount; i++ {
+ j := i
+ if p.capnumlist != nil {
+ j = p.capnumlist[i]
+ }
+
+ if next == j {
+ p.capnamelist = append(p.capnamelist, oldcapnamelist[k])
+ k++
+
+ if k == len(oldcapnamelist) {
+ next = -1
+ } else {
+ next = p.capnames[oldcapnamelist[k]]
+ }
+
+ } else {
+ //feature: culture?
+ str := strconv.Itoa(j)
+ p.capnamelist = append(p.capnamelist, str)
+ p.capnames[str] = j
+ }
+ }
+ }
+}
+
+func (p *parser) consumeAutocap() int {
+ r := p.autocap
+ p.autocap++
+ return r
+}
+
+// CountCaptures is a prescanner for deducing the slots used for
+// captures by doing a partial tokenization of the pattern.
+func (p *parser) countCaptures() error {
+ var ch rune
+
+ p.noteCaptureSlot(0, 0)
+
+ p.autocap = 1
+
+ for p.charsRight() > 0 {
+ pos := p.textpos()
+ ch = p.moveRightGetChar()
+ switch ch {
+ case '\\':
+ if p.charsRight() > 0 {
+ p.scanBackslash(true)
+ }
+
+ case '#':
+ if p.useOptionX() {
+ p.moveLeft()
+ p.scanBlank()
+ }
+
+ case '[':
+ p.scanCharSet(false, true)
+
+ case ')':
+ if !p.emptyOptionsStack() {
+ p.popOptions()
+ }
+
+ case '(':
+ if p.charsRight() >= 2 && p.rightChar(1) == '#' && p.rightChar(0) == '?' {
+ p.moveLeft()
+ p.scanBlank()
+ } else {
+ p.pushOptions()
+ if p.charsRight() > 0 && p.rightChar(0) == '?' {
+ // we have (?...
+ p.moveRight(1)
+
+ if p.charsRight() > 1 && (p.rightChar(0) == '<' || p.rightChar(0) == '\'') {
+ // named group: (?<... or (?'...
+
+ p.moveRight(1)
+ ch = p.rightChar(0)
+
+ if ch != '0' && IsWordChar(ch) {
+ if ch >= '1' && ch <= '9' {
+ dec, err := p.scanDecimal()
+ if err != nil {
+ return err
+ }
+ p.noteCaptureSlot(dec, pos)
+ } else {
+ p.noteCaptureName(p.scanCapname(), pos)
+ }
+ }
+ } else if p.useRE2() && p.charsRight() > 2 && (p.rightChar(0) == 'P' && p.rightChar(1) == '<') {
+ // RE2-compat (?P<)
+ p.moveRight(2)
+ ch = p.rightChar(0)
+ if IsWordChar(ch) {
+ p.noteCaptureName(p.scanCapname(), pos)
+ }
+
+ } else {
+ // (?...
+
+ // get the options if it's an option construct (?cimsx-cimsx...)
+ p.scanOptions()
+
+ if p.charsRight() > 0 {
+ if p.rightChar(0) == ')' {
+ // (?cimsx-cimsx)
+ p.moveRight(1)
+ p.popKeepOptions()
+ } else if p.rightChar(0) == '(' {
+ // alternation construct: (?(foo)yes|no)
+ // ignore the next paren so we don't capture the condition
+ p.ignoreNextParen = true
+
+ // break from here so we don't reset ignoreNextParen
+ continue
+ }
+ }
+ }
+ } else {
+ if !p.useOptionN() && !p.ignoreNextParen {
+ p.noteCaptureSlot(p.consumeAutocap(), pos)
+ }
+ }
+ }
+
+ p.ignoreNextParen = false
+
+ }
+ }
+
+ p.assignNameSlots()
+ return nil
+}
+
+func (p *parser) reset(topopts RegexOptions) {
+ p.currentPos = 0
+ p.autocap = 1
+ p.ignoreNextParen = false
+
+ if len(p.optionsStack) > 0 {
+ p.optionsStack = p.optionsStack[:0]
+ }
+
+ p.options = topopts
+ p.stack = nil
+}
+
+func (p *parser) scanRegex() (*regexNode, error) {
+ ch := '@' // nonspecial ch, means at beginning
+ isQuant := false
+
+ p.startGroup(newRegexNodeMN(ntCapture, p.options, 0, -1))
+
+ for p.charsRight() > 0 {
+ wasPrevQuantifier := isQuant
+ isQuant = false
+
+ if err := p.scanBlank(); err != nil {
+ return nil, err
+ }
+
+ startpos := p.textpos()
+
+ // move past all of the normal characters. We'll stop when we hit some kind of control character,
+ // or if IgnorePatternWhiteSpace is on, we'll stop when we see some whitespace.
+ if p.useOptionX() {
+ for p.charsRight() > 0 {
+ ch = p.rightChar(0)
+ //UGLY: clean up, this is ugly
+ if !(!isStopperX(ch) || (ch == '{' && !p.isTrueQuantifier())) {
+ break
+ }
+ p.moveRight(1)
+ }
+ } else {
+ for p.charsRight() > 0 {
+ ch = p.rightChar(0)
+ if !(!isSpecial(ch) || ch == '{' && !p.isTrueQuantifier()) {
+ break
+ }
+ p.moveRight(1)
+ }
+ }
+
+ endpos := p.textpos()
+
+ p.scanBlank()
+
+ if p.charsRight() == 0 {
+ ch = '!' // nonspecial, means at end
+ } else if ch = p.rightChar(0); isSpecial(ch) {
+ isQuant = isQuantifier(ch)
+ p.moveRight(1)
+ } else {
+ ch = ' ' // nonspecial, means at ordinary char
+ }
+
+ if startpos < endpos {
+ cchUnquantified := endpos - startpos
+ if isQuant {
+ cchUnquantified--
+ }
+ wasPrevQuantifier = false
+
+ if cchUnquantified > 0 {
+ p.addToConcatenate(startpos, cchUnquantified, false)
+ }
+
+ if isQuant {
+ p.addUnitOne(p.charAt(endpos - 1))
+ }
+ }
+
+ switch ch {
+ case '!':
+ goto BreakOuterScan
+
+ case ' ':
+ goto ContinueOuterScan
+
+ case '[':
+ cc, err := p.scanCharSet(p.useOptionI(), false)
+ if err != nil {
+ return nil, err
+ }
+ p.addUnitSet(cc)
+
+ case '(':
+ p.pushOptions()
+
+ if grouper, err := p.scanGroupOpen(); err != nil {
+ return nil, err
+ } else if grouper == nil {
+ p.popKeepOptions()
+ } else {
+ p.pushGroup()
+ p.startGroup(grouper)
+ }
+
+ continue
+
+ case '|':
+ p.addAlternate()
+ goto ContinueOuterScan
+
+ case ')':
+ if p.emptyStack() {
+ return nil, p.getErr(ErrUnexpectedParen)
+ }
+
+ if err := p.addGroup(); err != nil {
+ return nil, err
+ }
+ if err := p.popGroup(); err != nil {
+ return nil, err
+ }
+ p.popOptions()
+
+ if p.unit == nil {
+ goto ContinueOuterScan
+ }
+
+ case '\\':
+ n, err := p.scanBackslash(false)
+ if err != nil {
+ return nil, err
+ }
+ p.addUnitNode(n)
+
+ case '^':
+ if p.useOptionM() {
+ p.addUnitType(ntBol)
+ } else {
+ p.addUnitType(ntBeginning)
+ }
+
+ case '$':
+ if p.useOptionM() {
+ p.addUnitType(ntEol)
+ } else {
+ p.addUnitType(ntEndZ)
+ }
+
+ case '.':
+ if p.useOptionE() {
+ p.addUnitSet(ECMAAnyClass())
+ } else if p.useOptionS() {
+ p.addUnitSet(AnyClass())
+ } else {
+ p.addUnitNotone('\n')
+ }
+
+ case '{', '*', '+', '?':
+ if p.unit == nil {
+ if wasPrevQuantifier {
+ return nil, p.getErr(ErrInvalidRepeatOp)
+ } else {
+ return nil, p.getErr(ErrMissingRepeatArgument)
+ }
+ }
+ p.moveLeft()
+
+ default:
+ return nil, p.getErr(ErrInternalError)
+ }
+
+ if err := p.scanBlank(); err != nil {
+ return nil, err
+ }
+
+ if p.charsRight() > 0 {
+ isQuant = p.isTrueQuantifier()
+ }
+ if p.charsRight() == 0 || !isQuant {
+ //maintain odd C# assignment order -- not sure if required, could clean up?
+ p.addConcatenate()
+ goto ContinueOuterScan
+ }
+
+ ch = p.moveRightGetChar()
+
+ // Handle quantifiers
+ for p.unit != nil {
+ var min, max int
+ var lazy bool
+
+ switch ch {
+ case '*':
+ min = 0
+ max = math.MaxInt32
+
+ case '?':
+ min = 0
+ max = 1
+
+ case '+':
+ min = 1
+ max = math.MaxInt32
+
+ case '{':
+ {
+ var err error
+ startpos = p.textpos()
+ if min, err = p.scanDecimal(); err != nil {
+ return nil, err
+ }
+ max = min
+ if startpos < p.textpos() {
+ if p.charsRight() > 0 && p.rightChar(0) == ',' {
+ p.moveRight(1)
+ if p.charsRight() == 0 || p.rightChar(0) == '}' {
+ max = math.MaxInt32
+ } else {
+ if max, err = p.scanDecimal(); err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+
+ if startpos == p.textpos() || p.charsRight() == 0 || p.moveRightGetChar() != '}' {
+ p.addConcatenate()
+ p.textto(startpos - 1)
+ goto ContinueOuterScan
+ }
+ }
+
+ default:
+ return nil, p.getErr(ErrInternalError)
+ }
+
+ if err := p.scanBlank(); err != nil {
+ return nil, err
+ }
+
+ if p.charsRight() == 0 || p.rightChar(0) != '?' {
+ lazy = false
+ } else {
+ p.moveRight(1)
+ lazy = true
+ }
+
+ if min > max {
+ return nil, p.getErr(ErrInvalidRepeatSize)
+ }
+
+ p.addConcatenate3(lazy, min, max)
+ }
+
+ ContinueOuterScan:
+ }
+
+BreakOuterScan:
+ ;
+
+ if !p.emptyStack() {
+ return nil, p.getErr(ErrMissingParen)
+ }
+
+ if err := p.addGroup(); err != nil {
+ return nil, err
+ }
+
+ return p.unit, nil
+
+}
+
+/*
+ * Simple parsing for replacement patterns
+ */
+func (p *parser) scanReplacement() (*regexNode, error) {
+ var c, startpos int
+
+ p.concatenation = newRegexNode(ntConcatenate, p.options)
+
+ for {
+ c = p.charsRight()
+ if c == 0 {
+ break
+ }
+
+ startpos = p.textpos()
+
+ for c > 0 && p.rightChar(0) != '$' {
+ p.moveRight(1)
+ c--
+ }
+
+ p.addToConcatenate(startpos, p.textpos()-startpos, true)
+
+ if c > 0 {
+ if p.moveRightGetChar() == '$' {
+ n, err := p.scanDollar()
+ if err != nil {
+ return nil, err
+ }
+ p.addUnitNode(n)
+ }
+ p.addConcatenate()
+ }
+ }
+
+ return p.concatenation, nil
+}
+
+/*
+ * Scans $ patterns recognized within replacement patterns
+ */
+func (p *parser) scanDollar() (*regexNode, error) {
+ if p.charsRight() == 0 {
+ return newRegexNodeCh(ntOne, p.options, '$'), nil
+ }
+
+ ch := p.rightChar(0)
+ angled := false
+ backpos := p.textpos()
+ lastEndPos := backpos
+
+ // Note angle
+
+ if ch == '{' && p.charsRight() > 1 {
+ angled = true
+ p.moveRight(1)
+ ch = p.rightChar(0)
+ }
+
+ // Try to parse backreference: \1 or \{1} or \{cap}
+
+ if ch >= '0' && ch <= '9' {
+ if !angled && p.useOptionE() {
+ capnum := -1
+ newcapnum := int(ch - '0')
+ p.moveRight(1)
+ if p.isCaptureSlot(newcapnum) {
+ capnum = newcapnum
+ lastEndPos = p.textpos()
+ }
+
+ for p.charsRight() > 0 {
+ ch = p.rightChar(0)
+ if ch < '0' || ch > '9' {
+ break
+ }
+ digit := int(ch - '0')
+ if newcapnum > maxValueDiv10 || (newcapnum == maxValueDiv10 && digit > maxValueMod10) {
+ return nil, p.getErr(ErrCaptureGroupOutOfRange)
+ }
+
+ newcapnum = newcapnum*10 + digit
+
+ p.moveRight(1)
+ if p.isCaptureSlot(newcapnum) {
+ capnum = newcapnum
+ lastEndPos = p.textpos()
+ }
+ }
+ p.textto(lastEndPos)
+ if capnum >= 0 {
+ return newRegexNodeM(ntRef, p.options, capnum), nil
+ }
+ } else {
+ capnum, err := p.scanDecimal()
+ if err != nil {
+ return nil, err
+ }
+ if !angled || p.charsRight() > 0 && p.moveRightGetChar() == '}' {
+ if p.isCaptureSlot(capnum) {
+ return newRegexNodeM(ntRef, p.options, capnum), nil
+ }
+ }
+ }
+ } else if angled && IsWordChar(ch) {
+ capname := p.scanCapname()
+
+ if p.charsRight() > 0 && p.moveRightGetChar() == '}' {
+ if p.isCaptureName(capname) {
+ return newRegexNodeM(ntRef, p.options, p.captureSlotFromName(capname)), nil
+ }
+ }
+ } else if !angled {
+ capnum := 1
+
+ switch ch {
+ case '$':
+ p.moveRight(1)
+ return newRegexNodeCh(ntOne, p.options, '$'), nil
+ case '&':
+ capnum = 0
+ case '`':
+ capnum = replaceLeftPortion
+ case '\'':
+ capnum = replaceRightPortion
+ case '+':
+ capnum = replaceLastGroup
+ case '_':
+ capnum = replaceWholeString
+ }
+
+ if capnum != 1 {
+ p.moveRight(1)
+ return newRegexNodeM(ntRef, p.options, capnum), nil
+ }
+ }
+
+ // unrecognized $: literalize
+
+ p.textto(backpos)
+ return newRegexNodeCh(ntOne, p.options, '$'), nil
+}
+
+// scanGroupOpen scans chars following a '(' (not counting the '('), and returns
+// a RegexNode for the type of group scanned, or nil if the group
+// simply changed options (?cimsx-cimsx) or was a comment (#...).
+func (p *parser) scanGroupOpen() (*regexNode, error) {
+ var ch rune
+ var nt nodeType
+ var err error
+ close := '>'
+ start := p.textpos()
+
+ // just return a RegexNode if we have:
+ // 1. "(" followed by nothing
+ // 2. "(x" where x != ?
+ // 3. "(?)"
+ if p.charsRight() == 0 || p.rightChar(0) != '?' || (p.rightChar(0) == '?' && (p.charsRight() > 1 && p.rightChar(1) == ')')) {
+ if p.useOptionN() || p.ignoreNextParen {
+ p.ignoreNextParen = false
+ return newRegexNode(ntGroup, p.options), nil
+ }
+ return newRegexNodeMN(ntCapture, p.options, p.consumeAutocap(), -1), nil
+ }
+
+ p.moveRight(1)
+
+ for {
+ if p.charsRight() == 0 {
+ break
+ }
+
+ switch ch = p.moveRightGetChar(); ch {
+ case ':':
+ nt = ntGroup
+
+ case '=':
+ p.options &= ^RightToLeft
+ nt = ntRequire
+
+ case '!':
+ p.options &= ^RightToLeft
+ nt = ntPrevent
+
+ case '>':
+ nt = ntGreedy
+
+ case '\'':
+ close = '\''
+ fallthrough
+
+ case '<':
+ if p.charsRight() == 0 {
+ goto BreakRecognize
+ }
+
+ switch ch = p.moveRightGetChar(); ch {
+ case '=':
+ if close == '\'' {
+ goto BreakRecognize
+ }
+
+ p.options |= RightToLeft
+ nt = ntRequire
+
+ case '!':
+ if close == '\'' {
+ goto BreakRecognize
+ }
+
+ p.options |= RightToLeft
+ nt = ntPrevent
+
+ default:
+ p.moveLeft()
+ capnum := -1
+ uncapnum := -1
+ proceed := false
+
+ // grab part before -
+
+ if ch >= '0' && ch <= '9' {
+ if capnum, err = p.scanDecimal(); err != nil {
+ return nil, err
+ }
+
+ if !p.isCaptureSlot(capnum) {
+ capnum = -1
+ }
+
+ // check if we have bogus characters after the number
+ if p.charsRight() > 0 && !(p.rightChar(0) == close || p.rightChar(0) == '-') {
+ return nil, p.getErr(ErrInvalidGroupName)
+ }
+ if capnum == 0 {
+ return nil, p.getErr(ErrCapNumNotZero)
+ }
+ } else if IsWordChar(ch) {
+ capname := p.scanCapname()
+
+ if p.isCaptureName(capname) {
+ capnum = p.captureSlotFromName(capname)
+ }
+
+ // check if we have bogus character after the name
+ if p.charsRight() > 0 && !(p.rightChar(0) == close || p.rightChar(0) == '-') {
+ return nil, p.getErr(ErrInvalidGroupName)
+ }
+ } else if ch == '-' {
+ proceed = true
+ } else {
+ // bad group name - starts with something other than a word character and isn't a number
+ return nil, p.getErr(ErrInvalidGroupName)
+ }
+
+ // grab part after - if any
+
+ if (capnum != -1 || proceed == true) && p.charsRight() > 0 && p.rightChar(0) == '-' {
+ p.moveRight(1)
+
+ //no more chars left, no closing char, etc
+ if p.charsRight() == 0 {
+ return nil, p.getErr(ErrInvalidGroupName)
+ }
+
+ ch = p.rightChar(0)
+ if ch >= '0' && ch <= '9' {
+ if uncapnum, err = p.scanDecimal(); err != nil {
+ return nil, err
+ }
+
+ if !p.isCaptureSlot(uncapnum) {
+ return nil, p.getErr(ErrUndefinedBackRef, uncapnum)
+ }
+
+ // check if we have bogus characters after the number
+ if p.charsRight() > 0 && p.rightChar(0) != close {
+ return nil, p.getErr(ErrInvalidGroupName)
+ }
+ } else if IsWordChar(ch) {
+ uncapname := p.scanCapname()
+
+ if !p.isCaptureName(uncapname) {
+ return nil, p.getErr(ErrUndefinedNameRef, uncapname)
+ }
+ uncapnum = p.captureSlotFromName(uncapname)
+
+ // check if we have bogus character after the name
+ if p.charsRight() > 0 && p.rightChar(0) != close {
+ return nil, p.getErr(ErrInvalidGroupName)
+ }
+ } else {
+ // bad group name - starts with something other than a word character and isn't a number
+ return nil, p.getErr(ErrInvalidGroupName)
+ }
+ }
+
+ // actually make the node
+
+ if (capnum != -1 || uncapnum != -1) && p.charsRight() > 0 && p.moveRightGetChar() == close {
+ return newRegexNodeMN(ntCapture, p.options, capnum, uncapnum), nil
+ }
+ goto BreakRecognize
+ }
+
+ case '(':
+ // alternation construct (?(...) | )
+
+ parenPos := p.textpos()
+ if p.charsRight() > 0 {
+ ch = p.rightChar(0)
+
+ // check if the alternation condition is a backref
+ if ch >= '0' && ch <= '9' {
+ var capnum int
+ if capnum, err = p.scanDecimal(); err != nil {
+ return nil, err
+ }
+ if p.charsRight() > 0 && p.moveRightGetChar() == ')' {
+ if p.isCaptureSlot(capnum) {
+ return newRegexNodeM(ntTestref, p.options, capnum), nil
+ }
+ return nil, p.getErr(ErrUndefinedReference, capnum)
+ }
+
+ return nil, p.getErr(ErrMalformedReference, capnum)
+
+ } else if IsWordChar(ch) {
+ capname := p.scanCapname()
+
+ if p.isCaptureName(capname) && p.charsRight() > 0 && p.moveRightGetChar() == ')' {
+ return newRegexNodeM(ntTestref, p.options, p.captureSlotFromName(capname)), nil
+ }
+ }
+ }
+ // not a backref
+ nt = ntTestgroup
+ p.textto(parenPos - 1) // jump to the start of the parentheses
+ p.ignoreNextParen = true // but make sure we don't try to capture the insides
+
+ charsRight := p.charsRight()
+ if charsRight >= 3 && p.rightChar(1) == '?' {
+ rightchar2 := p.rightChar(2)
+ // disallow comments in the condition
+ if rightchar2 == '#' {
+ return nil, p.getErr(ErrAlternationCantHaveComment)
+ }
+
+ // disallow named capture group (?<..>..) in the condition
+ if rightchar2 == '\'' {
+ return nil, p.getErr(ErrAlternationCantCapture)
+ }
+
+ if charsRight >= 4 && (rightchar2 == '<' && p.rightChar(3) != '!' && p.rightChar(3) != '=') {
+ return nil, p.getErr(ErrAlternationCantCapture)
+ }
+ }
+
+ case 'P':
+ if p.useRE2() {
+ // support for P syntax
+ if p.charsRight() < 3 {
+ goto BreakRecognize
+ }
+
+ ch = p.moveRightGetChar()
+ if ch != '<' {
+ goto BreakRecognize
+ }
+
+ ch = p.moveRightGetChar()
+ p.moveLeft()
+
+ if IsWordChar(ch) {
+ capnum := -1
+ capname := p.scanCapname()
+
+ if p.isCaptureName(capname) {
+ capnum = p.captureSlotFromName(capname)
+ }
+
+ // check if we have bogus character after the name
+ if p.charsRight() > 0 && p.rightChar(0) != '>' {
+ return nil, p.getErr(ErrInvalidGroupName)
+ }
+
+ // actually make the node
+
+ if capnum != -1 && p.charsRight() > 0 && p.moveRightGetChar() == '>' {
+ return newRegexNodeMN(ntCapture, p.options, capnum, -1), nil
+ }
+ goto BreakRecognize
+
+ } else {
+ // bad group name - starts with something other than a word character and isn't a number
+ return nil, p.getErr(ErrInvalidGroupName)
+ }
+ }
+ // if we're not using RE2 compat mode then
+ // we just behave like normal
+ fallthrough
+
+ default:
+ p.moveLeft()
+
+ nt = ntGroup
+ // disallow options in the children of a testgroup node
+ if p.group.t != ntTestgroup {
+ p.scanOptions()
+ }
+ if p.charsRight() == 0 {
+ goto BreakRecognize
+ }
+
+ if ch = p.moveRightGetChar(); ch == ')' {
+ return nil, nil
+ }
+
+ if ch != ':' {
+ goto BreakRecognize
+ }
+
+ }
+
+ return newRegexNode(nt, p.options), nil
+ }
+
+BreakRecognize:
+
+ // break Recognize comes here
+
+ return nil, p.getErr(ErrUnrecognizedGrouping, string(p.pattern[start:p.textpos()]))
+}
+
+// scans backslash specials and basics
+func (p *parser) scanBackslash(scanOnly bool) (*regexNode, error) {
+
+ if p.charsRight() == 0 {
+ return nil, p.getErr(ErrIllegalEndEscape)
+ }
+
+ switch ch := p.rightChar(0); ch {
+ case 'b', 'B', 'A', 'G', 'Z', 'z':
+ p.moveRight(1)
+ return newRegexNode(p.typeFromCode(ch), p.options), nil
+
+ case 'w':
+ p.moveRight(1)
+ if p.useOptionE() || p.useRE2() {
+ return newRegexNodeSet(ntSet, p.options, ECMAWordClass()), nil
+ }
+ return newRegexNodeSet(ntSet, p.options, WordClass()), nil
+
+ case 'W':
+ p.moveRight(1)
+ if p.useOptionE() || p.useRE2() {
+ return newRegexNodeSet(ntSet, p.options, NotECMAWordClass()), nil
+ }
+ return newRegexNodeSet(ntSet, p.options, NotWordClass()), nil
+
+ case 's':
+ p.moveRight(1)
+ if p.useOptionE() {
+ return newRegexNodeSet(ntSet, p.options, ECMASpaceClass()), nil
+ } else if p.useRE2() {
+ return newRegexNodeSet(ntSet, p.options, RE2SpaceClass()), nil
+ }
+ return newRegexNodeSet(ntSet, p.options, SpaceClass()), nil
+
+ case 'S':
+ p.moveRight(1)
+ if p.useOptionE() {
+ return newRegexNodeSet(ntSet, p.options, NotECMASpaceClass()), nil
+ } else if p.useRE2() {
+ return newRegexNodeSet(ntSet, p.options, NotRE2SpaceClass()), nil
+ }
+ return newRegexNodeSet(ntSet, p.options, NotSpaceClass()), nil
+
+ case 'd':
+ p.moveRight(1)
+ if p.useOptionE() || p.useRE2() {
+ return newRegexNodeSet(ntSet, p.options, ECMADigitClass()), nil
+ }
+ return newRegexNodeSet(ntSet, p.options, DigitClass()), nil
+
+ case 'D':
+ p.moveRight(1)
+ if p.useOptionE() || p.useRE2() {
+ return newRegexNodeSet(ntSet, p.options, NotECMADigitClass()), nil
+ }
+ return newRegexNodeSet(ntSet, p.options, NotDigitClass()), nil
+
+ case 'p', 'P':
+ p.moveRight(1)
+ prop, err := p.parseProperty()
+ if err != nil {
+ return nil, err
+ }
+ cc := &CharSet{}
+ cc.addCategory(prop, (ch != 'p'), p.useOptionI(), p.patternRaw)
+ if p.useOptionI() {
+ cc.addLowercase()
+ }
+
+ return newRegexNodeSet(ntSet, p.options, cc), nil
+
+ default:
+ return p.scanBasicBackslash(scanOnly)
+ }
+}
+
+// Scans \-style backreferences and character escapes
+func (p *parser) scanBasicBackslash(scanOnly bool) (*regexNode, error) {
+ if p.charsRight() == 0 {
+ return nil, p.getErr(ErrIllegalEndEscape)
+ }
+ angled := false
+ k := false
+ close := '\x00'
+
+ backpos := p.textpos()
+ ch := p.rightChar(0)
+
+ // Allow \k instead of \, which is now deprecated.
+
+ // According to ECMAScript specification, \k is only parsed as a named group reference if
+ // there is at least one group name in the regexp.
+ // See https://www.ecma-international.org/ecma-262/#sec-isvalidregularexpressionliteral, step 7.
+ // Note, during the first (scanOnly) run we may not have all group names scanned, but that's ok.
+ if ch == 'k' && (!p.useOptionE() || len(p.capnames) > 0) {
+ if p.charsRight() >= 2 {
+ p.moveRight(1)
+ ch = p.moveRightGetChar()
+
+ if ch == '<' || (!p.useOptionE() && ch == '\'') { // No support for \k'name' in ECMAScript
+ angled = true
+ if ch == '\'' {
+ close = '\''
+ } else {
+ close = '>'
+ }
+ }
+ }
+
+ if !angled || p.charsRight() <= 0 {
+ return nil, p.getErr(ErrMalformedNameRef)
+ }
+
+ ch = p.rightChar(0)
+ k = true
+
+ } else if !p.useOptionE() && (ch == '<' || ch == '\'') && p.charsRight() > 1 { // Note angle without \g
+ angled = true
+ if ch == '\'' {
+ close = '\''
+ } else {
+ close = '>'
+ }
+
+ p.moveRight(1)
+ ch = p.rightChar(0)
+ }
+
+ // Try to parse backreference: \<1> or \
+
+ if angled && ch >= '0' && ch <= '9' {
+ capnum, err := p.scanDecimal()
+ if err != nil {
+ return nil, err
+ }
+
+ if p.charsRight() > 0 && p.moveRightGetChar() == close {
+ if p.isCaptureSlot(capnum) {
+ return newRegexNodeM(ntRef, p.options, capnum), nil
+ }
+ return nil, p.getErr(ErrUndefinedBackRef, capnum)
+ }
+ } else if !angled && ch >= '1' && ch <= '9' { // Try to parse backreference or octal: \1
+ capnum, err := p.scanDecimal()
+ if err != nil {
+ return nil, err
+ }
+
+ if scanOnly {
+ return nil, nil
+ }
+
+ if p.isCaptureSlot(capnum) {
+ return newRegexNodeM(ntRef, p.options, capnum), nil
+ }
+ if capnum <= 9 && !p.useOptionE() {
+ return nil, p.getErr(ErrUndefinedBackRef, capnum)
+ }
+
+ } else if angled {
+ capname := p.scanCapname()
+
+ if capname != "" && p.charsRight() > 0 && p.moveRightGetChar() == close {
+
+ if scanOnly {
+ return nil, nil
+ }
+
+ if p.isCaptureName(capname) {
+ return newRegexNodeM(ntRef, p.options, p.captureSlotFromName(capname)), nil
+ }
+ return nil, p.getErr(ErrUndefinedNameRef, capname)
+ } else {
+ if k {
+ return nil, p.getErr(ErrMalformedNameRef)
+ }
+ }
+ }
+
+ // Not backreference: must be char code
+
+ p.textto(backpos)
+ ch, err := p.scanCharEscape()
+ if err != nil {
+ return nil, err
+ }
+
+ if scanOnly {
+ return nil, nil
+ }
+
+ if p.useOptionI() {
+ ch = unicode.ToLower(ch)
+ }
+
+ return newRegexNodeCh(ntOne, p.options, ch), nil
+}
+
+// Scans X for \p{X} or \P{X}
+func (p *parser) parseProperty() (string, error) {
+ // RE2 and PCRE supports \pX syntax (no {} and only 1 letter unicode cats supported)
+ // since this is purely additive syntax it's not behind a flag
+ if p.charsRight() >= 1 && p.rightChar(0) != '{' {
+ ch := string(p.moveRightGetChar())
+ // check if it's a valid cat
+ if !isValidUnicodeCat(ch) {
+ return "", p.getErr(ErrUnknownSlashP, ch)
+ }
+ return ch, nil
+ }
+
+ if p.charsRight() < 3 {
+ return "", p.getErr(ErrIncompleteSlashP)
+ }
+ ch := p.moveRightGetChar()
+ if ch != '{' {
+ return "", p.getErr(ErrMalformedSlashP)
+ }
+
+ startpos := p.textpos()
+ for p.charsRight() > 0 {
+ ch = p.moveRightGetChar()
+ if !(IsWordChar(ch) || ch == '-') {
+ p.moveLeft()
+ break
+ }
+ }
+ capname := string(p.pattern[startpos:p.textpos()])
+
+ if p.charsRight() == 0 || p.moveRightGetChar() != '}' {
+ return "", p.getErr(ErrIncompleteSlashP)
+ }
+
+ if !isValidUnicodeCat(capname) {
+ return "", p.getErr(ErrUnknownSlashP, capname)
+ }
+
+ return capname, nil
+}
+
+// Returns ReNode type for zero-length assertions with a \ code.
+func (p *parser) typeFromCode(ch rune) nodeType {
+ switch ch {
+ case 'b':
+ if p.useOptionE() {
+ return ntECMABoundary
+ }
+ return ntBoundary
+ case 'B':
+ if p.useOptionE() {
+ return ntNonECMABoundary
+ }
+ return ntNonboundary
+ case 'A':
+ return ntBeginning
+ case 'G':
+ return ntStart
+ case 'Z':
+ return ntEndZ
+ case 'z':
+ return ntEnd
+ default:
+ return ntNothing
+ }
+}
+
+// Scans whitespace or x-mode comments.
+func (p *parser) scanBlank() error {
+ if p.useOptionX() {
+ for {
+ for p.charsRight() > 0 && isSpace(p.rightChar(0)) {
+ p.moveRight(1)
+ }
+
+ if p.charsRight() == 0 {
+ break
+ }
+
+ if p.rightChar(0) == '#' {
+ for p.charsRight() > 0 && p.rightChar(0) != '\n' {
+ p.moveRight(1)
+ }
+ } else if p.charsRight() >= 3 && p.rightChar(2) == '#' &&
+ p.rightChar(1) == '?' && p.rightChar(0) == '(' {
+ for p.charsRight() > 0 && p.rightChar(0) != ')' {
+ p.moveRight(1)
+ }
+ if p.charsRight() == 0 {
+ return p.getErr(ErrUnterminatedComment)
+ }
+ p.moveRight(1)
+ } else {
+ break
+ }
+ }
+ } else {
+ for {
+ if p.charsRight() < 3 || p.rightChar(2) != '#' ||
+ p.rightChar(1) != '?' || p.rightChar(0) != '(' {
+ return nil
+ }
+
+ for p.charsRight() > 0 && p.rightChar(0) != ')' {
+ p.moveRight(1)
+ }
+ if p.charsRight() == 0 {
+ return p.getErr(ErrUnterminatedComment)
+ }
+ p.moveRight(1)
+ }
+ }
+ return nil
+}
+
+func (p *parser) scanCapname() string {
+ startpos := p.textpos()
+
+ for p.charsRight() > 0 {
+ if !IsWordChar(p.moveRightGetChar()) {
+ p.moveLeft()
+ break
+ }
+ }
+
+ return string(p.pattern[startpos:p.textpos()])
+}
+
+// Scans contents of [] (not including []'s), and converts to a set.
+func (p *parser) scanCharSet(caseInsensitive, scanOnly bool) (*CharSet, error) {
+ ch := '\x00'
+ chPrev := '\x00'
+ inRange := false
+ firstChar := true
+ closed := false
+
+ var cc *CharSet
+ if !scanOnly {
+ cc = &CharSet{}
+ }
+
+ if p.charsRight() > 0 && p.rightChar(0) == '^' {
+ p.moveRight(1)
+ if !scanOnly {
+ cc.negate = true
+ }
+ }
+
+ for ; p.charsRight() > 0; firstChar = false {
+ fTranslatedChar := false
+ ch = p.moveRightGetChar()
+ if ch == ']' {
+ if !firstChar {
+ closed = true
+ break
+ } else if p.useOptionE() {
+ if !scanOnly {
+ cc.addRanges(NoneClass().ranges)
+ }
+ closed = true
+ break
+ }
+
+ } else if ch == '\\' && p.charsRight() > 0 {
+ switch ch = p.moveRightGetChar(); ch {
+ case 'D', 'd':
+ if !scanOnly {
+ if inRange {
+ return nil, p.getErr(ErrBadClassInCharRange, ch)
+ }
+ cc.addDigit(p.useOptionE() || p.useRE2(), ch == 'D', p.patternRaw)
+ }
+ continue
+
+ case 'S', 's':
+ if !scanOnly {
+ if inRange {
+ return nil, p.getErr(ErrBadClassInCharRange, ch)
+ }
+ cc.addSpace(p.useOptionE(), p.useRE2(), ch == 'S')
+ }
+ continue
+
+ case 'W', 'w':
+ if !scanOnly {
+ if inRange {
+ return nil, p.getErr(ErrBadClassInCharRange, ch)
+ }
+
+ cc.addWord(p.useOptionE() || p.useRE2(), ch == 'W')
+ }
+ continue
+
+ case 'p', 'P':
+ if !scanOnly {
+ if inRange {
+ return nil, p.getErr(ErrBadClassInCharRange, ch)
+ }
+ prop, err := p.parseProperty()
+ if err != nil {
+ return nil, err
+ }
+ cc.addCategory(prop, (ch != 'p'), caseInsensitive, p.patternRaw)
+ } else {
+ p.parseProperty()
+ }
+
+ continue
+
+ case '-':
+ if !scanOnly {
+ cc.addRange(ch, ch)
+ }
+ continue
+
+ default:
+ p.moveLeft()
+ var err error
+ ch, err = p.scanCharEscape() // non-literal character
+ if err != nil {
+ return nil, err
+ }
+ fTranslatedChar = true
+ break // this break will only break out of the switch
+ }
+ } else if ch == '[' {
+ // This is code for Posix style properties - [:Ll:] or [:IsTibetan:].
+ // It currently doesn't do anything other than skip the whole thing!
+ if p.charsRight() > 0 && p.rightChar(0) == ':' && !inRange {
+ savePos := p.textpos()
+
+ p.moveRight(1)
+ negate := false
+ if p.charsRight() > 1 && p.rightChar(0) == '^' {
+ negate = true
+ p.moveRight(1)
+ }
+
+ nm := p.scanCapname() // snag the name
+ if !scanOnly && p.useRE2() {
+ // look up the name since these are valid for RE2
+ // add the group based on the name
+ if ok := cc.addNamedASCII(nm, negate); !ok {
+ return nil, p.getErr(ErrInvalidCharRange)
+ }
+ }
+ if p.charsRight() < 2 || p.moveRightGetChar() != ':' || p.moveRightGetChar() != ']' {
+ p.textto(savePos)
+ } else if p.useRE2() {
+ // move on
+ continue
+ }
+ }
+ }
+
+ if inRange {
+ inRange = false
+ if !scanOnly {
+ if ch == '[' && !fTranslatedChar && !firstChar {
+ // We thought we were in a range, but we're actually starting a subtraction.
+ // In that case, we'll add chPrev to our char class, skip the opening [, and
+ // scan the new character class recursively.
+ cc.addChar(chPrev)
+ sub, err := p.scanCharSet(caseInsensitive, false)
+ if err != nil {
+ return nil, err
+ }
+ cc.addSubtraction(sub)
+
+ if p.charsRight() > 0 && p.rightChar(0) != ']' {
+ return nil, p.getErr(ErrSubtractionMustBeLast)
+ }
+ } else {
+ // a regular range, like a-z
+ if chPrev > ch {
+ return nil, p.getErr(ErrReversedCharRange, chPrev, ch)
+ }
+ cc.addRange(chPrev, ch)
+ }
+ }
+ } else if p.charsRight() >= 2 && p.rightChar(0) == '-' && p.rightChar(1) != ']' {
+ // this could be the start of a range
+ chPrev = ch
+ inRange = true
+ p.moveRight(1)
+ } else if p.charsRight() >= 1 && ch == '-' && !fTranslatedChar && p.rightChar(0) == '[' && !firstChar {
+ // we aren't in a range, and now there is a subtraction. Usually this happens
+ // only when a subtraction follows a range, like [a-z-[b]]
+ if !scanOnly {
+ p.moveRight(1)
+ sub, err := p.scanCharSet(caseInsensitive, false)
+ if err != nil {
+ return nil, err
+ }
+ cc.addSubtraction(sub)
+
+ if p.charsRight() > 0 && p.rightChar(0) != ']' {
+ return nil, p.getErr(ErrSubtractionMustBeLast)
+ }
+ } else {
+ p.moveRight(1)
+ p.scanCharSet(caseInsensitive, true)
+ }
+ } else {
+ if !scanOnly {
+ cc.addRange(ch, ch)
+ }
+ }
+ }
+
+ if !closed {
+ return nil, p.getErr(ErrUnterminatedBracket)
+ }
+
+ if !scanOnly && caseInsensitive {
+ cc.addLowercase()
+ }
+
+ return cc, nil
+}
+
+// Scans any number of decimal digits (pegs value at 2^31-1 if too large)
+func (p *parser) scanDecimal() (int, error) {
+ i := 0
+ var d int
+
+ for p.charsRight() > 0 {
+ d = int(p.rightChar(0) - '0')
+ if d < 0 || d > 9 {
+ break
+ }
+ p.moveRight(1)
+
+ if i > maxValueDiv10 || (i == maxValueDiv10 && d > maxValueMod10) {
+ return 0, p.getErr(ErrCaptureGroupOutOfRange)
+ }
+
+ i *= 10
+ i += d
+ }
+
+ return int(i), nil
+}
+
+// Returns true for options allowed only at the top level
+func isOnlyTopOption(option RegexOptions) bool {
+ return option == RightToLeft || option == ECMAScript || option == RE2
+}
+
+// Scans cimsx-cimsx option string, stops at the first unrecognized char.
+func (p *parser) scanOptions() {
+
+ for off := false; p.charsRight() > 0; p.moveRight(1) {
+ ch := p.rightChar(0)
+
+ if ch == '-' {
+ off = true
+ } else if ch == '+' {
+ off = false
+ } else {
+ option := optionFromCode(ch)
+ if option == 0 || isOnlyTopOption(option) {
+ return
+ }
+
+ if off {
+ p.options &= ^option
+ } else {
+ p.options |= option
+ }
+ }
+ }
+}
+
+// Scans \ code for escape codes that map to single unicode chars.
+func (p *parser) scanCharEscape() (r rune, err error) {
+
+ ch := p.moveRightGetChar()
+
+ if ch >= '0' && ch <= '7' {
+ p.moveLeft()
+ return p.scanOctal(), nil
+ }
+
+ pos := p.textpos()
+
+ switch ch {
+ case 'x':
+ // support for \x{HEX} syntax from Perl and PCRE
+ if p.charsRight() > 0 && p.rightChar(0) == '{' {
+ if p.useOptionE() {
+ return ch, nil
+ }
+ p.moveRight(1)
+ return p.scanHexUntilBrace()
+ } else {
+ r, err = p.scanHex(2)
+ }
+ case 'u':
+ // ECMAscript suppot \u{HEX} only if `u` is also set
+ if p.useOptionE() && p.useOptionU() && p.charsRight() > 0 && p.rightChar(0) == '{' {
+ p.moveRight(1)
+ return p.scanHexUntilBrace()
+ } else {
+ r, err = p.scanHex(4)
+ }
+ case 'a':
+ return '\u0007', nil
+ case 'b':
+ return '\b', nil
+ case 'e':
+ return '\u001B', nil
+ case 'f':
+ return '\f', nil
+ case 'n':
+ return '\n', nil
+ case 'r':
+ return '\r', nil
+ case 't':
+ return '\t', nil
+ case 'v':
+ return '\u000B', nil
+ case 'c':
+ r, err = p.scanControl()
+ default:
+ if !p.useOptionE() && !p.useRE2() && IsWordChar(ch) {
+ return 0, p.getErr(ErrUnrecognizedEscape, string(ch))
+ }
+ return ch, nil
+ }
+ if err != nil && p.useOptionE() {
+ p.textto(pos)
+ return ch, nil
+ }
+ return
+}
+
+// Grabs and converts an ascii control character
+func (p *parser) scanControl() (rune, error) {
+ if p.charsRight() <= 0 {
+ return 0, p.getErr(ErrMissingControl)
+ }
+
+ ch := p.moveRightGetChar()
+
+ // \ca interpreted as \cA
+
+ if ch >= 'a' && ch <= 'z' {
+ ch = (ch - ('a' - 'A'))
+ }
+ ch = (ch - '@')
+ if ch >= 0 && ch < ' ' {
+ return ch, nil
+ }
+
+ return 0, p.getErr(ErrUnrecognizedControl)
+
+}
+
+// Scan hex digits until we hit a closing brace.
+// Non-hex digits, hex value too large for UTF-8, or running out of chars are errors
+func (p *parser) scanHexUntilBrace() (rune, error) {
+ // PCRE spec reads like unlimited hex digits are allowed, but unicode has a limit
+ // so we can enforce that
+ i := 0
+ hasContent := false
+
+ for p.charsRight() > 0 {
+ ch := p.moveRightGetChar()
+ if ch == '}' {
+ // hit our close brace, we're done here
+ // prevent \x{}
+ if !hasContent {
+ return 0, p.getErr(ErrTooFewHex)
+ }
+ return rune(i), nil
+ }
+ hasContent = true
+ // no brace needs to be hex digit
+ d := hexDigit(ch)
+ if d < 0 {
+ return 0, p.getErr(ErrMissingBrace)
+ }
+
+ i *= 0x10
+ i += d
+
+ if i > unicode.MaxRune {
+ return 0, p.getErr(ErrInvalidHex)
+ }
+ }
+
+ // we only make it here if we run out of digits without finding the brace
+ return 0, p.getErr(ErrMissingBrace)
+}
+
+// Scans exactly c hex digits (c=2 for \xFF, c=4 for \uFFFF)
+func (p *parser) scanHex(c int) (rune, error) {
+
+ i := 0
+
+ if p.charsRight() >= c {
+ for c > 0 {
+ d := hexDigit(p.moveRightGetChar())
+ if d < 0 {
+ break
+ }
+ i *= 0x10
+ i += d
+ c--
+ }
+ }
+
+ if c > 0 {
+ return 0, p.getErr(ErrTooFewHex)
+ }
+
+ return rune(i), nil
+}
+
+// Returns n <= 0xF for a hex digit.
+func hexDigit(ch rune) int {
+
+ if d := uint(ch - '0'); d <= 9 {
+ return int(d)
+ }
+
+ if d := uint(ch - 'a'); d <= 5 {
+ return int(d + 0xa)
+ }
+
+ if d := uint(ch - 'A'); d <= 5 {
+ return int(d + 0xa)
+ }
+
+ return -1
+}
+
+// Scans up to three octal digits (stops before exceeding 0377).
+func (p *parser) scanOctal() rune {
+ // Consume octal chars only up to 3 digits and value 0377
+
+ c := 3
+
+ if c > p.charsRight() {
+ c = p.charsRight()
+ }
+
+ //we know the first char is good because the caller had to check
+ i := 0
+ d := int(p.rightChar(0) - '0')
+ for c > 0 && d <= 7 && d >= 0 {
+ if i >= 0x20 && p.useOptionE() {
+ break
+ }
+ i *= 8
+ i += d
+ c--
+
+ p.moveRight(1)
+ if !p.rightMost() {
+ d = int(p.rightChar(0) - '0')
+ }
+ }
+
+ // Octal codes only go up to 255. Any larger and the behavior that Perl follows
+ // is simply to truncate the high bits.
+ i &= 0xFF
+
+ return rune(i)
+}
+
+// Returns the current parsing position.
+func (p *parser) textpos() int {
+ return p.currentPos
+}
+
+// Zaps to a specific parsing position.
+func (p *parser) textto(pos int) {
+ p.currentPos = pos
+}
+
+// Returns the char at the right of the current parsing position and advances to the right.
+func (p *parser) moveRightGetChar() rune {
+ ch := p.pattern[p.currentPos]
+ p.currentPos++
+ return ch
+}
+
+// Moves the current position to the right.
+func (p *parser) moveRight(i int) {
+ // default would be 1
+ p.currentPos += i
+}
+
+// Moves the current parsing position one to the left.
+func (p *parser) moveLeft() {
+ p.currentPos--
+}
+
+// Returns the char left of the current parsing position.
+func (p *parser) charAt(i int) rune {
+ return p.pattern[i]
+}
+
+// Returns the char i chars right of the current parsing position.
+func (p *parser) rightChar(i int) rune {
+ // default would be 0
+ return p.pattern[p.currentPos+i]
+}
+
+// Number of characters to the right of the current parsing position.
+func (p *parser) charsRight() int {
+ return len(p.pattern) - p.currentPos
+}
+
+func (p *parser) rightMost() bool {
+ return p.currentPos == len(p.pattern)
+}
+
+// Looks up the slot number for a given name
+func (p *parser) captureSlotFromName(capname string) int {
+ return p.capnames[capname]
+}
+
+// True if the capture slot was noted
+func (p *parser) isCaptureSlot(i int) bool {
+ if p.caps != nil {
+ _, ok := p.caps[i]
+ return ok
+ }
+
+ return (i >= 0 && i < p.capsize)
+}
+
+// Looks up the slot number for a given name
+func (p *parser) isCaptureName(capname string) bool {
+ if p.capnames == nil {
+ return false
+ }
+
+ _, ok := p.capnames[capname]
+ return ok
+}
+
+// option shortcuts
+
+// True if N option disabling '(' autocapture is on.
+func (p *parser) useOptionN() bool {
+ return (p.options & ExplicitCapture) != 0
+}
+
+// True if I option enabling case-insensitivity is on.
+func (p *parser) useOptionI() bool {
+ return (p.options & IgnoreCase) != 0
+}
+
+// True if M option altering meaning of $ and ^ is on.
+func (p *parser) useOptionM() bool {
+ return (p.options & Multiline) != 0
+}
+
+// True if S option altering meaning of . is on.
+func (p *parser) useOptionS() bool {
+ return (p.options & Singleline) != 0
+}
+
+// True if X option enabling whitespace/comment mode is on.
+func (p *parser) useOptionX() bool {
+ return (p.options & IgnorePatternWhitespace) != 0
+}
+
+// True if E option enabling ECMAScript behavior on.
+func (p *parser) useOptionE() bool {
+ return (p.options & ECMAScript) != 0
+}
+
+// true to use RE2 compatibility parsing behavior.
+func (p *parser) useRE2() bool {
+ return (p.options & RE2) != 0
+}
+
+// True if U option enabling ECMAScript's Unicode behavior on.
+func (p *parser) useOptionU() bool {
+ return (p.options & Unicode) != 0
+}
+
+// True if options stack is empty.
+func (p *parser) emptyOptionsStack() bool {
+ return len(p.optionsStack) == 0
+}
+
+// Finish the current quantifiable (when a quantifier is not found or is not possible)
+func (p *parser) addConcatenate() {
+ // The first (| inside a Testgroup group goes directly to the group
+ p.concatenation.addChild(p.unit)
+ p.unit = nil
+}
+
+// Finish the current quantifiable (when a quantifier is found)
+func (p *parser) addConcatenate3(lazy bool, min, max int) {
+ p.concatenation.addChild(p.unit.makeQuantifier(lazy, min, max))
+ p.unit = nil
+}
+
+// Sets the current unit to a single char node
+func (p *parser) addUnitOne(ch rune) {
+ if p.useOptionI() {
+ ch = unicode.ToLower(ch)
+ }
+
+ p.unit = newRegexNodeCh(ntOne, p.options, ch)
+}
+
+// Sets the current unit to a single inverse-char node
+func (p *parser) addUnitNotone(ch rune) {
+ if p.useOptionI() {
+ ch = unicode.ToLower(ch)
+ }
+
+ p.unit = newRegexNodeCh(ntNotone, p.options, ch)
+}
+
+// Sets the current unit to a single set node
+func (p *parser) addUnitSet(set *CharSet) {
+ p.unit = newRegexNodeSet(ntSet, p.options, set)
+}
+
+// Sets the current unit to a subtree
+func (p *parser) addUnitNode(node *regexNode) {
+ p.unit = node
+}
+
+// Sets the current unit to an assertion of the specified type
+func (p *parser) addUnitType(t nodeType) {
+ p.unit = newRegexNode(t, p.options)
+}
+
+// Finish the current group (in response to a ')' or end)
+func (p *parser) addGroup() error {
+ if p.group.t == ntTestgroup || p.group.t == ntTestref {
+ p.group.addChild(p.concatenation.reverseLeft())
+ if (p.group.t == ntTestref && len(p.group.children) > 2) || len(p.group.children) > 3 {
+ return p.getErr(ErrTooManyAlternates)
+ }
+ } else {
+ p.alternation.addChild(p.concatenation.reverseLeft())
+ p.group.addChild(p.alternation)
+ }
+
+ p.unit = p.group
+ return nil
+}
+
+// Pops the option stack, but keeps the current options unchanged.
+func (p *parser) popKeepOptions() {
+ lastIdx := len(p.optionsStack) - 1
+ p.optionsStack = p.optionsStack[:lastIdx]
+}
+
+// Recalls options from the stack.
+func (p *parser) popOptions() {
+ lastIdx := len(p.optionsStack) - 1
+ // get the last item on the stack and then remove it by reslicing
+ p.options = p.optionsStack[lastIdx]
+ p.optionsStack = p.optionsStack[:lastIdx]
+}
+
+// Saves options on a stack.
+func (p *parser) pushOptions() {
+ p.optionsStack = append(p.optionsStack, p.options)
+}
+
+// Add a string to the last concatenate.
+func (p *parser) addToConcatenate(pos, cch int, isReplacement bool) {
+ var node *regexNode
+
+ if cch == 0 {
+ return
+ }
+
+ if cch > 1 {
+ str := make([]rune, cch)
+ copy(str, p.pattern[pos:pos+cch])
+
+ if p.useOptionI() && !isReplacement {
+ // We do the ToLower character by character for consistency. With surrogate chars, doing
+ // a ToLower on the entire string could actually change the surrogate pair. This is more correct
+ // linguistically, but since Regex doesn't support surrogates, it's more important to be
+ // consistent.
+ for i := 0; i < len(str); i++ {
+ str[i] = unicode.ToLower(str[i])
+ }
+ }
+
+ node = newRegexNodeStr(ntMulti, p.options, str)
+ } else {
+ ch := p.charAt(pos)
+
+ if p.useOptionI() && !isReplacement {
+ ch = unicode.ToLower(ch)
+ }
+
+ node = newRegexNodeCh(ntOne, p.options, ch)
+ }
+
+ p.concatenation.addChild(node)
+}
+
+// Push the parser state (in response to an open paren)
+func (p *parser) pushGroup() {
+ p.group.next = p.stack
+ p.alternation.next = p.group
+ p.concatenation.next = p.alternation
+ p.stack = p.concatenation
+}
+
+// Remember the pushed state (in response to a ')')
+func (p *parser) popGroup() error {
+ p.concatenation = p.stack
+ p.alternation = p.concatenation.next
+ p.group = p.alternation.next
+ p.stack = p.group.next
+
+ // The first () inside a Testgroup group goes directly to the group
+ if p.group.t == ntTestgroup && len(p.group.children) == 0 {
+ if p.unit == nil {
+ return p.getErr(ErrConditionalExpression)
+ }
+
+ p.group.addChild(p.unit)
+ p.unit = nil
+ }
+ return nil
+}
+
+// True if the group stack is empty.
+func (p *parser) emptyStack() bool {
+ return p.stack == nil
+}
+
+// Start a new round for the parser state (in response to an open paren or string start)
+func (p *parser) startGroup(openGroup *regexNode) {
+ p.group = openGroup
+ p.alternation = newRegexNode(ntAlternate, p.options)
+ p.concatenation = newRegexNode(ntConcatenate, p.options)
+}
+
+// Finish the current concatenation (in response to a |)
+func (p *parser) addAlternate() {
+ // The | parts inside a Testgroup group go directly to the group
+
+ if p.group.t == ntTestgroup || p.group.t == ntTestref {
+ p.group.addChild(p.concatenation.reverseLeft())
+ } else {
+ p.alternation.addChild(p.concatenation.reverseLeft())
+ }
+
+ p.concatenation = newRegexNode(ntConcatenate, p.options)
+}
+
+// For categorizing ascii characters.
+
+const (
+ Q byte = 5 // quantifier
+ S = 4 // ordinary stopper
+ Z = 3 // ScanBlank stopper
+ X = 2 // whitespace
+ E = 1 // should be escaped
+)
+
+var _category = []byte{
+ //01 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, X, X, X, X, X, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ // ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
+ X, 0, 0, Z, S, 0, 0, 0, S, S, Q, Q, 0, 0, S, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Q,
+ //@A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, S, 0,
+ //'a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Q, S, 0, 0, 0,
+}
+
+func isSpace(ch rune) bool {
+ return (ch <= ' ' && _category[ch] == X)
+}
+
+// Returns true for those characters that terminate a string of ordinary chars.
+func isSpecial(ch rune) bool {
+ return (ch <= '|' && _category[ch] >= S)
+}
+
+// Returns true for those characters that terminate a string of ordinary chars.
+func isStopperX(ch rune) bool {
+ return (ch <= '|' && _category[ch] >= X)
+}
+
+// Returns true for those characters that begin a quantifier.
+func isQuantifier(ch rune) bool {
+ return (ch <= '{' && _category[ch] >= Q)
+}
+
+func (p *parser) isTrueQuantifier() bool {
+ nChars := p.charsRight()
+ if nChars == 0 {
+ return false
+ }
+
+ startpos := p.textpos()
+ ch := p.charAt(startpos)
+ if ch != '{' {
+ return ch <= '{' && _category[ch] >= Q
+ }
+
+ //UGLY: this is ugly -- the original code was ugly too
+ pos := startpos
+ for {
+ nChars--
+ if nChars <= 0 {
+ break
+ }
+ pos++
+ ch = p.charAt(pos)
+ if ch < '0' || ch > '9' {
+ break
+ }
+ }
+
+ if nChars == 0 || pos-startpos == 1 {
+ return false
+ }
+ if ch == '}' {
+ return true
+ }
+ if ch != ',' {
+ return false
+ }
+ for {
+ nChars--
+ if nChars <= 0 {
+ break
+ }
+ pos++
+ ch = p.charAt(pos)
+ if ch < '0' || ch > '9' {
+ break
+ }
+ }
+
+ return nChars > 0 && ch == '}'
+}
diff --git a/vendor/github.com/dlclark/regexp2/syntax/prefix.go b/vendor/github.com/dlclark/regexp2/syntax/prefix.go
new file mode 100644
index 00000000..f6716886
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/syntax/prefix.go
@@ -0,0 +1,896 @@
+package syntax
+
+import (
+ "bytes"
+ "fmt"
+ "strconv"
+ "unicode"
+ "unicode/utf8"
+)
+
+type Prefix struct {
+ PrefixStr []rune
+ PrefixSet CharSet
+ CaseInsensitive bool
+}
+
+// It takes a RegexTree and computes the set of chars that can start it.
+func getFirstCharsPrefix(tree *RegexTree) *Prefix {
+ s := regexFcd{
+ fcStack: make([]regexFc, 32),
+ intStack: make([]int, 32),
+ }
+ fc := s.regexFCFromRegexTree(tree)
+
+ if fc == nil || fc.nullable || fc.cc.IsEmpty() {
+ return nil
+ }
+ fcSet := fc.getFirstChars()
+ return &Prefix{PrefixSet: fcSet, CaseInsensitive: fc.caseInsensitive}
+}
+
+type regexFcd struct {
+ intStack []int
+ intDepth int
+ fcStack []regexFc
+ fcDepth int
+ skipAllChildren bool // don't process any more children at the current level
+ skipchild bool // don't process the current child.
+ failed bool
+}
+
+/*
+ * The main FC computation. It does a shortcutted depth-first walk
+ * through the tree and calls CalculateFC to emits code before
+ * and after each child of an interior node, and at each leaf.
+ */
+func (s *regexFcd) regexFCFromRegexTree(tree *RegexTree) *regexFc {
+ curNode := tree.root
+ curChild := 0
+
+ for {
+ if len(curNode.children) == 0 {
+ // This is a leaf node
+ s.calculateFC(curNode.t, curNode, 0)
+ } else if curChild < len(curNode.children) && !s.skipAllChildren {
+ // This is an interior node, and we have more children to analyze
+ s.calculateFC(curNode.t|beforeChild, curNode, curChild)
+
+ if !s.skipchild {
+ curNode = curNode.children[curChild]
+ // this stack is how we get a depth first walk of the tree.
+ s.pushInt(curChild)
+ curChild = 0
+ } else {
+ curChild++
+ s.skipchild = false
+ }
+ continue
+ }
+
+ // This is an interior node where we've finished analyzing all the children, or
+ // the end of a leaf node.
+ s.skipAllChildren = false
+
+ if s.intIsEmpty() {
+ break
+ }
+
+ curChild = s.popInt()
+ curNode = curNode.next
+
+ s.calculateFC(curNode.t|afterChild, curNode, curChild)
+ if s.failed {
+ return nil
+ }
+
+ curChild++
+ }
+
+ if s.fcIsEmpty() {
+ return nil
+ }
+
+ return s.popFC()
+}
+
+// To avoid recursion, we use a simple integer stack.
+// This is the push.
+func (s *regexFcd) pushInt(I int) {
+ if s.intDepth >= len(s.intStack) {
+ expanded := make([]int, s.intDepth*2)
+ copy(expanded, s.intStack)
+ s.intStack = expanded
+ }
+
+ s.intStack[s.intDepth] = I
+ s.intDepth++
+}
+
+// True if the stack is empty.
+func (s *regexFcd) intIsEmpty() bool {
+ return s.intDepth == 0
+}
+
+// This is the pop.
+func (s *regexFcd) popInt() int {
+ s.intDepth--
+ return s.intStack[s.intDepth]
+}
+
+// We also use a stack of RegexFC objects.
+// This is the push.
+func (s *regexFcd) pushFC(fc regexFc) {
+ if s.fcDepth >= len(s.fcStack) {
+ expanded := make([]regexFc, s.fcDepth*2)
+ copy(expanded, s.fcStack)
+ s.fcStack = expanded
+ }
+
+ s.fcStack[s.fcDepth] = fc
+ s.fcDepth++
+}
+
+// True if the stack is empty.
+func (s *regexFcd) fcIsEmpty() bool {
+ return s.fcDepth == 0
+}
+
+// This is the pop.
+func (s *regexFcd) popFC() *regexFc {
+ s.fcDepth--
+ return &s.fcStack[s.fcDepth]
+}
+
+// This is the top.
+func (s *regexFcd) topFC() *regexFc {
+ return &s.fcStack[s.fcDepth-1]
+}
+
+// Called in Beforechild to prevent further processing of the current child
+func (s *regexFcd) skipChild() {
+ s.skipchild = true
+}
+
+// FC computation and shortcut cases for each node type
+func (s *regexFcd) calculateFC(nt nodeType, node *regexNode, CurIndex int) {
+ //fmt.Printf("NodeType: %v, CurIndex: %v, Desc: %v\n", nt, CurIndex, node.description())
+ ci := false
+ rtl := false
+
+ if nt <= ntRef {
+ if (node.options & IgnoreCase) != 0 {
+ ci = true
+ }
+ if (node.options & RightToLeft) != 0 {
+ rtl = true
+ }
+ }
+
+ switch nt {
+ case ntConcatenate | beforeChild, ntAlternate | beforeChild, ntTestref | beforeChild, ntLoop | beforeChild, ntLazyloop | beforeChild:
+ break
+
+ case ntTestgroup | beforeChild:
+ if CurIndex == 0 {
+ s.skipChild()
+ }
+ break
+
+ case ntEmpty:
+ s.pushFC(regexFc{nullable: true})
+ break
+
+ case ntConcatenate | afterChild:
+ if CurIndex != 0 {
+ child := s.popFC()
+ cumul := s.topFC()
+
+ s.failed = !cumul.addFC(*child, true)
+ }
+
+ fc := s.topFC()
+ if !fc.nullable {
+ s.skipAllChildren = true
+ }
+ break
+
+ case ntTestgroup | afterChild:
+ if CurIndex > 1 {
+ child := s.popFC()
+ cumul := s.topFC()
+
+ s.failed = !cumul.addFC(*child, false)
+ }
+ break
+
+ case ntAlternate | afterChild, ntTestref | afterChild:
+ if CurIndex != 0 {
+ child := s.popFC()
+ cumul := s.topFC()
+
+ s.failed = !cumul.addFC(*child, false)
+ }
+ break
+
+ case ntLoop | afterChild, ntLazyloop | afterChild:
+ if node.m == 0 {
+ fc := s.topFC()
+ fc.nullable = true
+ }
+ break
+
+ case ntGroup | beforeChild, ntGroup | afterChild, ntCapture | beforeChild, ntCapture | afterChild, ntGreedy | beforeChild, ntGreedy | afterChild:
+ break
+
+ case ntRequire | beforeChild, ntPrevent | beforeChild:
+ s.skipChild()
+ s.pushFC(regexFc{nullable: true})
+ break
+
+ case ntRequire | afterChild, ntPrevent | afterChild:
+ break
+
+ case ntOne, ntNotone:
+ s.pushFC(newRegexFc(node.ch, nt == ntNotone, false, ci))
+ break
+
+ case ntOneloop, ntOnelazy:
+ s.pushFC(newRegexFc(node.ch, false, node.m == 0, ci))
+ break
+
+ case ntNotoneloop, ntNotonelazy:
+ s.pushFC(newRegexFc(node.ch, true, node.m == 0, ci))
+ break
+
+ case ntMulti:
+ if len(node.str) == 0 {
+ s.pushFC(regexFc{nullable: true})
+ } else if !rtl {
+ s.pushFC(newRegexFc(node.str[0], false, false, ci))
+ } else {
+ s.pushFC(newRegexFc(node.str[len(node.str)-1], false, false, ci))
+ }
+ break
+
+ case ntSet:
+ s.pushFC(regexFc{cc: node.set.Copy(), nullable: false, caseInsensitive: ci})
+ break
+
+ case ntSetloop, ntSetlazy:
+ s.pushFC(regexFc{cc: node.set.Copy(), nullable: node.m == 0, caseInsensitive: ci})
+ break
+
+ case ntRef:
+ s.pushFC(regexFc{cc: *AnyClass(), nullable: true, caseInsensitive: false})
+ break
+
+ case ntNothing, ntBol, ntEol, ntBoundary, ntNonboundary, ntECMABoundary, ntNonECMABoundary, ntBeginning, ntStart, ntEndZ, ntEnd:
+ s.pushFC(regexFc{nullable: true})
+ break
+
+ default:
+ panic(fmt.Sprintf("unexpected op code: %v", nt))
+ }
+}
+
+type regexFc struct {
+ cc CharSet
+ nullable bool
+ caseInsensitive bool
+}
+
+func newRegexFc(ch rune, not, nullable, caseInsensitive bool) regexFc {
+ r := regexFc{
+ caseInsensitive: caseInsensitive,
+ nullable: nullable,
+ }
+ if not {
+ if ch > 0 {
+ r.cc.addRange('\x00', ch-1)
+ }
+ if ch < 0xFFFF {
+ r.cc.addRange(ch+1, utf8.MaxRune)
+ }
+ } else {
+ r.cc.addRange(ch, ch)
+ }
+ return r
+}
+
+func (r *regexFc) getFirstChars() CharSet {
+ if r.caseInsensitive {
+ r.cc.addLowercase()
+ }
+
+ return r.cc
+}
+
+func (r *regexFc) addFC(fc regexFc, concatenate bool) bool {
+ if !r.cc.IsMergeable() || !fc.cc.IsMergeable() {
+ return false
+ }
+
+ if concatenate {
+ if !r.nullable {
+ return true
+ }
+
+ if !fc.nullable {
+ r.nullable = false
+ }
+ } else {
+ if fc.nullable {
+ r.nullable = true
+ }
+ }
+
+ r.caseInsensitive = r.caseInsensitive || fc.caseInsensitive
+ r.cc.addSet(fc.cc)
+
+ return true
+}
+
+// This is a related computation: it takes a RegexTree and computes the
+// leading substring if it sees one. It's quite trivial and gives up easily.
+func getPrefix(tree *RegexTree) *Prefix {
+ var concatNode *regexNode
+ nextChild := 0
+
+ curNode := tree.root
+
+ for {
+ switch curNode.t {
+ case ntConcatenate:
+ if len(curNode.children) > 0 {
+ concatNode = curNode
+ nextChild = 0
+ }
+
+ case ntGreedy, ntCapture:
+ curNode = curNode.children[0]
+ concatNode = nil
+ continue
+
+ case ntOneloop, ntOnelazy:
+ if curNode.m > 0 {
+ return &Prefix{
+ PrefixStr: repeat(curNode.ch, curNode.m),
+ CaseInsensitive: (curNode.options & IgnoreCase) != 0,
+ }
+ }
+ return nil
+
+ case ntOne:
+ return &Prefix{
+ PrefixStr: []rune{curNode.ch},
+ CaseInsensitive: (curNode.options & IgnoreCase) != 0,
+ }
+
+ case ntMulti:
+ return &Prefix{
+ PrefixStr: curNode.str,
+ CaseInsensitive: (curNode.options & IgnoreCase) != 0,
+ }
+
+ case ntBol, ntEol, ntBoundary, ntECMABoundary, ntBeginning, ntStart,
+ ntEndZ, ntEnd, ntEmpty, ntRequire, ntPrevent:
+
+ default:
+ return nil
+ }
+
+ if concatNode == nil || nextChild >= len(concatNode.children) {
+ return nil
+ }
+
+ curNode = concatNode.children[nextChild]
+ nextChild++
+ }
+}
+
+// repeat the rune r, c times... up to the max of MaxPrefixSize
+func repeat(r rune, c int) []rune {
+ if c > MaxPrefixSize {
+ c = MaxPrefixSize
+ }
+
+ ret := make([]rune, c)
+
+ // binary growth using copy for speed
+ ret[0] = r
+ bp := 1
+ for bp < len(ret) {
+ copy(ret[bp:], ret[:bp])
+ bp *= 2
+ }
+
+ return ret
+}
+
+// BmPrefix precomputes the Boyer-Moore
+// tables for fast string scanning. These tables allow
+// you to scan for the first occurrence of a string within
+// a large body of text without examining every character.
+// The performance of the heuristic depends on the actual
+// string and the text being searched, but usually, the longer
+// the string that is being searched for, the fewer characters
+// need to be examined.
+type BmPrefix struct {
+ positive []int
+ negativeASCII []int
+ negativeUnicode [][]int
+ pattern []rune
+ lowASCII rune
+ highASCII rune
+ rightToLeft bool
+ caseInsensitive bool
+}
+
+func newBmPrefix(pattern []rune, caseInsensitive, rightToLeft bool) *BmPrefix {
+
+ b := &BmPrefix{
+ rightToLeft: rightToLeft,
+ caseInsensitive: caseInsensitive,
+ pattern: pattern,
+ }
+
+ if caseInsensitive {
+ for i := 0; i < len(b.pattern); i++ {
+ // We do the ToLower character by character for consistency. With surrogate chars, doing
+ // a ToLower on the entire string could actually change the surrogate pair. This is more correct
+ // linguistically, but since Regex doesn't support surrogates, it's more important to be
+ // consistent.
+
+ b.pattern[i] = unicode.ToLower(b.pattern[i])
+ }
+ }
+
+ var beforefirst, last, bump int
+ var scan, match int
+
+ if !rightToLeft {
+ beforefirst = -1
+ last = len(b.pattern) - 1
+ bump = 1
+ } else {
+ beforefirst = len(b.pattern)
+ last = 0
+ bump = -1
+ }
+
+ // PART I - the good-suffix shift table
+ //
+ // compute the positive requirement:
+ // if char "i" is the first one from the right that doesn't match,
+ // then we know the matcher can advance by _positive[i].
+ //
+ // This algorithm is a simplified variant of the standard
+ // Boyer-Moore good suffix calculation.
+
+ b.positive = make([]int, len(b.pattern))
+
+ examine := last
+ ch := b.pattern[examine]
+ b.positive[examine] = bump
+ examine -= bump
+
+Outerloop:
+ for {
+ // find an internal char (examine) that matches the tail
+
+ for {
+ if examine == beforefirst {
+ break Outerloop
+ }
+ if b.pattern[examine] == ch {
+ break
+ }
+ examine -= bump
+ }
+
+ match = last
+ scan = examine
+
+ // find the length of the match
+ for {
+ if scan == beforefirst || b.pattern[match] != b.pattern[scan] {
+ // at the end of the match, note the difference in _positive
+ // this is not the length of the match, but the distance from the internal match
+ // to the tail suffix.
+ if b.positive[match] == 0 {
+ b.positive[match] = match - scan
+ }
+
+ // System.Diagnostics.Debug.WriteLine("Set positive[" + match + "] to " + (match - scan));
+
+ break
+ }
+
+ scan -= bump
+ match -= bump
+ }
+
+ examine -= bump
+ }
+
+ match = last - bump
+
+ // scan for the chars for which there are no shifts that yield a different candidate
+
+ // The inside of the if statement used to say
+ // "_positive[match] = last - beforefirst;"
+ // This is slightly less aggressive in how much we skip, but at worst it
+ // should mean a little more work rather than skipping a potential match.
+ for match != beforefirst {
+ if b.positive[match] == 0 {
+ b.positive[match] = bump
+ }
+
+ match -= bump
+ }
+
+ // PART II - the bad-character shift table
+ //
+ // compute the negative requirement:
+ // if char "ch" is the reject character when testing position "i",
+ // we can slide up by _negative[ch];
+ // (_negative[ch] = str.Length - 1 - str.LastIndexOf(ch))
+ //
+ // the lookup table is divided into ASCII and Unicode portions;
+ // only those parts of the Unicode 16-bit code set that actually
+ // appear in the string are in the table. (Maximum size with
+ // Unicode is 65K; ASCII only case is 512 bytes.)
+
+ b.negativeASCII = make([]int, 128)
+
+ for i := 0; i < len(b.negativeASCII); i++ {
+ b.negativeASCII[i] = last - beforefirst
+ }
+
+ b.lowASCII = 127
+ b.highASCII = 0
+
+ for examine = last; examine != beforefirst; examine -= bump {
+ ch = b.pattern[examine]
+
+ switch {
+ case ch < 128:
+ if b.lowASCII > ch {
+ b.lowASCII = ch
+ }
+
+ if b.highASCII < ch {
+ b.highASCII = ch
+ }
+
+ if b.negativeASCII[ch] == last-beforefirst {
+ b.negativeASCII[ch] = last - examine
+ }
+ case ch <= 0xffff:
+ i, j := ch>>8, ch&0xFF
+
+ if b.negativeUnicode == nil {
+ b.negativeUnicode = make([][]int, 256)
+ }
+
+ if b.negativeUnicode[i] == nil {
+ newarray := make([]int, 256)
+
+ for k := 0; k < len(newarray); k++ {
+ newarray[k] = last - beforefirst
+ }
+
+ if i == 0 {
+ copy(newarray, b.negativeASCII)
+ //TODO: this line needed?
+ b.negativeASCII = newarray
+ }
+
+ b.negativeUnicode[i] = newarray
+ }
+
+ if b.negativeUnicode[i][j] == last-beforefirst {
+ b.negativeUnicode[i][j] = last - examine
+ }
+ default:
+ // we can't do the filter because this algo doesn't support
+ // unicode chars >0xffff
+ return nil
+ }
+ }
+
+ return b
+}
+
+func (b *BmPrefix) String() string {
+ return string(b.pattern)
+}
+
+// Dump returns the contents of the filter as a human readable string
+func (b *BmPrefix) Dump(indent string) string {
+ buf := &bytes.Buffer{}
+
+ fmt.Fprintf(buf, "%sBM Pattern: %s\n%sPositive: ", indent, string(b.pattern), indent)
+ for i := 0; i < len(b.positive); i++ {
+ buf.WriteString(strconv.Itoa(b.positive[i]))
+ buf.WriteRune(' ')
+ }
+ buf.WriteRune('\n')
+
+ if b.negativeASCII != nil {
+ buf.WriteString(indent)
+ buf.WriteString("Negative table\n")
+ for i := 0; i < len(b.negativeASCII); i++ {
+ if b.negativeASCII[i] != len(b.pattern) {
+ fmt.Fprintf(buf, "%s %s %s\n", indent, Escape(string(rune(i))), strconv.Itoa(b.negativeASCII[i]))
+ }
+ }
+ }
+
+ return buf.String()
+}
+
+// Scan uses the Boyer-Moore algorithm to find the first occurrence
+// of the specified string within text, beginning at index, and
+// constrained within beglimit and endlimit.
+//
+// The direction and case-sensitivity of the match is determined
+// by the arguments to the RegexBoyerMoore constructor.
+func (b *BmPrefix) Scan(text []rune, index, beglimit, endlimit int) int {
+ var (
+ defadv, test, test2 int
+ match, startmatch, endmatch int
+ bump, advance int
+ chTest rune
+ unicodeLookup []int
+ )
+
+ if !b.rightToLeft {
+ defadv = len(b.pattern)
+ startmatch = len(b.pattern) - 1
+ endmatch = 0
+ test = index + defadv - 1
+ bump = 1
+ } else {
+ defadv = -len(b.pattern)
+ startmatch = 0
+ endmatch = -defadv - 1
+ test = index + defadv
+ bump = -1
+ }
+
+ chMatch := b.pattern[startmatch]
+
+ for {
+ if test >= endlimit || test < beglimit {
+ return -1
+ }
+
+ chTest = text[test]
+
+ if b.caseInsensitive {
+ chTest = unicode.ToLower(chTest)
+ }
+
+ if chTest != chMatch {
+ if chTest < 128 {
+ advance = b.negativeASCII[chTest]
+ } else if chTest < 0xffff && len(b.negativeUnicode) > 0 {
+ unicodeLookup = b.negativeUnicode[chTest>>8]
+ if len(unicodeLookup) > 0 {
+ advance = unicodeLookup[chTest&0xFF]
+ } else {
+ advance = defadv
+ }
+ } else {
+ advance = defadv
+ }
+
+ test += advance
+ } else { // if (chTest == chMatch)
+ test2 = test
+ match = startmatch
+
+ for {
+ if match == endmatch {
+ if b.rightToLeft {
+ return test2 + 1
+ } else {
+ return test2
+ }
+ }
+
+ match -= bump
+ test2 -= bump
+
+ chTest = text[test2]
+
+ if b.caseInsensitive {
+ chTest = unicode.ToLower(chTest)
+ }
+
+ if chTest != b.pattern[match] {
+ advance = b.positive[match]
+ if chTest < 128 {
+ test2 = (match - startmatch) + b.negativeASCII[chTest]
+ } else if chTest < 0xffff && len(b.negativeUnicode) > 0 {
+ unicodeLookup = b.negativeUnicode[chTest>>8]
+ if len(unicodeLookup) > 0 {
+ test2 = (match - startmatch) + unicodeLookup[chTest&0xFF]
+ } else {
+ test += advance
+ break
+ }
+ } else {
+ test += advance
+ break
+ }
+
+ if b.rightToLeft {
+ if test2 < advance {
+ advance = test2
+ }
+ } else if test2 > advance {
+ advance = test2
+ }
+
+ test += advance
+ break
+ }
+ }
+ }
+ }
+}
+
+// When a regex is anchored, we can do a quick IsMatch test instead of a Scan
+func (b *BmPrefix) IsMatch(text []rune, index, beglimit, endlimit int) bool {
+ if !b.rightToLeft {
+ if index < beglimit || endlimit-index < len(b.pattern) {
+ return false
+ }
+
+ return b.matchPattern(text, index)
+ } else {
+ if index > endlimit || index-beglimit < len(b.pattern) {
+ return false
+ }
+
+ return b.matchPattern(text, index-len(b.pattern))
+ }
+}
+
+func (b *BmPrefix) matchPattern(text []rune, index int) bool {
+ if len(text)-index < len(b.pattern) {
+ return false
+ }
+
+ if b.caseInsensitive {
+ for i := 0; i < len(b.pattern); i++ {
+ //Debug.Assert(textinfo.ToLower(_pattern[i]) == _pattern[i], "pattern should be converted to lower case in constructor!");
+ if unicode.ToLower(text[index+i]) != b.pattern[i] {
+ return false
+ }
+ }
+ return true
+ } else {
+ for i := 0; i < len(b.pattern); i++ {
+ if text[index+i] != b.pattern[i] {
+ return false
+ }
+ }
+ return true
+ }
+}
+
+type AnchorLoc int16
+
+// where the regex can be pegged
+const (
+ AnchorBeginning AnchorLoc = 0x0001
+ AnchorBol = 0x0002
+ AnchorStart = 0x0004
+ AnchorEol = 0x0008
+ AnchorEndZ = 0x0010
+ AnchorEnd = 0x0020
+ AnchorBoundary = 0x0040
+ AnchorECMABoundary = 0x0080
+)
+
+func getAnchors(tree *RegexTree) AnchorLoc {
+
+ var concatNode *regexNode
+ nextChild, result := 0, AnchorLoc(0)
+
+ curNode := tree.root
+
+ for {
+ switch curNode.t {
+ case ntConcatenate:
+ if len(curNode.children) > 0 {
+ concatNode = curNode
+ nextChild = 0
+ }
+
+ case ntGreedy, ntCapture:
+ curNode = curNode.children[0]
+ concatNode = nil
+ continue
+
+ case ntBol, ntEol, ntBoundary, ntECMABoundary, ntBeginning,
+ ntStart, ntEndZ, ntEnd:
+ return result | anchorFromType(curNode.t)
+
+ case ntEmpty, ntRequire, ntPrevent:
+
+ default:
+ return result
+ }
+
+ if concatNode == nil || nextChild >= len(concatNode.children) {
+ return result
+ }
+
+ curNode = concatNode.children[nextChild]
+ nextChild++
+ }
+}
+
+func anchorFromType(t nodeType) AnchorLoc {
+ switch t {
+ case ntBol:
+ return AnchorBol
+ case ntEol:
+ return AnchorEol
+ case ntBoundary:
+ return AnchorBoundary
+ case ntECMABoundary:
+ return AnchorECMABoundary
+ case ntBeginning:
+ return AnchorBeginning
+ case ntStart:
+ return AnchorStart
+ case ntEndZ:
+ return AnchorEndZ
+ case ntEnd:
+ return AnchorEnd
+ default:
+ return 0
+ }
+}
+
+// anchorDescription returns a human-readable description of the anchors
+func (anchors AnchorLoc) String() string {
+ buf := &bytes.Buffer{}
+
+ if 0 != (anchors & AnchorBeginning) {
+ buf.WriteString(", Beginning")
+ }
+ if 0 != (anchors & AnchorStart) {
+ buf.WriteString(", Start")
+ }
+ if 0 != (anchors & AnchorBol) {
+ buf.WriteString(", Bol")
+ }
+ if 0 != (anchors & AnchorBoundary) {
+ buf.WriteString(", Boundary")
+ }
+ if 0 != (anchors & AnchorECMABoundary) {
+ buf.WriteString(", ECMABoundary")
+ }
+ if 0 != (anchors & AnchorEol) {
+ buf.WriteString(", Eol")
+ }
+ if 0 != (anchors & AnchorEnd) {
+ buf.WriteString(", End")
+ }
+ if 0 != (anchors & AnchorEndZ) {
+ buf.WriteString(", EndZ")
+ }
+
+ // trim off comma
+ if buf.Len() >= 2 {
+ return buf.String()[2:]
+ }
+ return "None"
+}
diff --git a/vendor/github.com/dlclark/regexp2/syntax/replacerdata.go b/vendor/github.com/dlclark/regexp2/syntax/replacerdata.go
new file mode 100644
index 00000000..bcf4d3f2
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/syntax/replacerdata.go
@@ -0,0 +1,87 @@
+package syntax
+
+import (
+ "bytes"
+ "errors"
+)
+
+type ReplacerData struct {
+ Rep string
+ Strings []string
+ Rules []int
+}
+
+const (
+ replaceSpecials = 4
+ replaceLeftPortion = -1
+ replaceRightPortion = -2
+ replaceLastGroup = -3
+ replaceWholeString = -4
+)
+
+//ErrReplacementError is a general error during parsing the replacement text
+var ErrReplacementError = errors.New("Replacement pattern error.")
+
+// NewReplacerData will populate a reusable replacer data struct based on the given replacement string
+// and the capture group data from a regexp
+func NewReplacerData(rep string, caps map[int]int, capsize int, capnames map[string]int, op RegexOptions) (*ReplacerData, error) {
+ p := parser{
+ options: op,
+ caps: caps,
+ capsize: capsize,
+ capnames: capnames,
+ }
+ p.setPattern(rep)
+ concat, err := p.scanReplacement()
+ if err != nil {
+ return nil, err
+ }
+
+ if concat.t != ntConcatenate {
+ panic(ErrReplacementError)
+ }
+
+ sb := &bytes.Buffer{}
+ var (
+ strings []string
+ rules []int
+ )
+
+ for _, child := range concat.children {
+ switch child.t {
+ case ntMulti:
+ child.writeStrToBuf(sb)
+
+ case ntOne:
+ sb.WriteRune(child.ch)
+
+ case ntRef:
+ if sb.Len() > 0 {
+ rules = append(rules, len(strings))
+ strings = append(strings, sb.String())
+ sb.Reset()
+ }
+ slot := child.m
+
+ if len(caps) > 0 && slot >= 0 {
+ slot = caps[slot]
+ }
+
+ rules = append(rules, -replaceSpecials-1-slot)
+
+ default:
+ panic(ErrReplacementError)
+ }
+ }
+
+ if sb.Len() > 0 {
+ rules = append(rules, len(strings))
+ strings = append(strings, sb.String())
+ }
+
+ return &ReplacerData{
+ Rep: rep,
+ Strings: strings,
+ Rules: rules,
+ }, nil
+}
diff --git a/vendor/github.com/dlclark/regexp2/syntax/tree.go b/vendor/github.com/dlclark/regexp2/syntax/tree.go
new file mode 100644
index 00000000..ea288293
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/syntax/tree.go
@@ -0,0 +1,654 @@
+package syntax
+
+import (
+ "bytes"
+ "fmt"
+ "math"
+ "strconv"
+)
+
+type RegexTree struct {
+ root *regexNode
+ caps map[int]int
+ capnumlist []int
+ captop int
+ Capnames map[string]int
+ Caplist []string
+ options RegexOptions
+}
+
+// It is built into a parsed tree for a regular expression.
+
+// Implementation notes:
+//
+// Since the node tree is a temporary data structure only used
+// during compilation of the regexp to integer codes, it's
+// designed for clarity and convenience rather than
+// space efficiency.
+//
+// RegexNodes are built into a tree, linked by the n.children list.
+// Each node also has a n.parent and n.ichild member indicating
+// its parent and which child # it is in its parent's list.
+//
+// RegexNodes come in as many types as there are constructs in
+// a regular expression, for example, "concatenate", "alternate",
+// "one", "rept", "group". There are also node types for basic
+// peephole optimizations, e.g., "onerep", "notsetrep", etc.
+//
+// Because perl 5 allows "lookback" groups that scan backwards,
+// each node also gets a "direction". Normally the value of
+// boolean n.backward = false.
+//
+// During parsing, top-level nodes are also stacked onto a parse
+// stack (a stack of trees). For this purpose we have a n.next
+// pointer. [Note that to save a few bytes, we could overload the
+// n.parent pointer instead.]
+//
+// On the parse stack, each tree has a "role" - basically, the
+// nonterminal in the grammar that the parser has currently
+// assigned to the tree. That code is stored in n.role.
+//
+// Finally, some of the different kinds of nodes have data.
+// Two integers (for the looping constructs) are stored in
+// n.operands, an an object (either a string or a set)
+// is stored in n.data
+type regexNode struct {
+ t nodeType
+ children []*regexNode
+ str []rune
+ set *CharSet
+ ch rune
+ m int
+ n int
+ options RegexOptions
+ next *regexNode
+}
+
+type nodeType int32
+
+const (
+ // The following are leaves, and correspond to primitive operations
+
+ ntOnerep nodeType = 0 // lef,back char,min,max a {n}
+ ntNotonerep = 1 // lef,back char,min,max .{n}
+ ntSetrep = 2 // lef,back set,min,max [\d]{n}
+ ntOneloop = 3 // lef,back char,min,max a {,n}
+ ntNotoneloop = 4 // lef,back char,min,max .{,n}
+ ntSetloop = 5 // lef,back set,min,max [\d]{,n}
+ ntOnelazy = 6 // lef,back char,min,max a {,n}?
+ ntNotonelazy = 7 // lef,back char,min,max .{,n}?
+ ntSetlazy = 8 // lef,back set,min,max [\d]{,n}?
+ ntOne = 9 // lef char a
+ ntNotone = 10 // lef char [^a]
+ ntSet = 11 // lef set [a-z\s] \w \s \d
+ ntMulti = 12 // lef string abcd
+ ntRef = 13 // lef group \#
+ ntBol = 14 // ^
+ ntEol = 15 // $
+ ntBoundary = 16 // \b
+ ntNonboundary = 17 // \B
+ ntBeginning = 18 // \A
+ ntStart = 19 // \G
+ ntEndZ = 20 // \Z
+ ntEnd = 21 // \Z
+
+ // Interior nodes do not correspond to primitive operations, but
+ // control structures compositing other operations
+
+ // Concat and alternate take n children, and can run forward or backwards
+
+ ntNothing = 22 // []
+ ntEmpty = 23 // ()
+ ntAlternate = 24 // a|b
+ ntConcatenate = 25 // ab
+ ntLoop = 26 // m,x * + ? {,}
+ ntLazyloop = 27 // m,x *? +? ?? {,}?
+ ntCapture = 28 // n ()
+ ntGroup = 29 // (?:)
+ ntRequire = 30 // (?=) (?<=)
+ ntPrevent = 31 // (?!) (?) (?<)
+ ntTestref = 33 // (?(n) | )
+ ntTestgroup = 34 // (?(...) | )
+
+ ntECMABoundary = 41 // \b
+ ntNonECMABoundary = 42 // \B
+)
+
+func newRegexNode(t nodeType, opt RegexOptions) *regexNode {
+ return ®exNode{
+ t: t,
+ options: opt,
+ }
+}
+
+func newRegexNodeCh(t nodeType, opt RegexOptions, ch rune) *regexNode {
+ return ®exNode{
+ t: t,
+ options: opt,
+ ch: ch,
+ }
+}
+
+func newRegexNodeStr(t nodeType, opt RegexOptions, str []rune) *regexNode {
+ return ®exNode{
+ t: t,
+ options: opt,
+ str: str,
+ }
+}
+
+func newRegexNodeSet(t nodeType, opt RegexOptions, set *CharSet) *regexNode {
+ return ®exNode{
+ t: t,
+ options: opt,
+ set: set,
+ }
+}
+
+func newRegexNodeM(t nodeType, opt RegexOptions, m int) *regexNode {
+ return ®exNode{
+ t: t,
+ options: opt,
+ m: m,
+ }
+}
+func newRegexNodeMN(t nodeType, opt RegexOptions, m, n int) *regexNode {
+ return ®exNode{
+ t: t,
+ options: opt,
+ m: m,
+ n: n,
+ }
+}
+
+func (n *regexNode) writeStrToBuf(buf *bytes.Buffer) {
+ for i := 0; i < len(n.str); i++ {
+ buf.WriteRune(n.str[i])
+ }
+}
+
+func (n *regexNode) addChild(child *regexNode) {
+ reduced := child.reduce()
+ n.children = append(n.children, reduced)
+ reduced.next = n
+}
+
+func (n *regexNode) insertChildren(afterIndex int, nodes []*regexNode) {
+ newChildren := make([]*regexNode, 0, len(n.children)+len(nodes))
+ n.children = append(append(append(newChildren, n.children[:afterIndex]...), nodes...), n.children[afterIndex:]...)
+}
+
+// removes children including the start but not the end index
+func (n *regexNode) removeChildren(startIndex, endIndex int) {
+ n.children = append(n.children[:startIndex], n.children[endIndex:]...)
+}
+
+// Pass type as OneLazy or OneLoop
+func (n *regexNode) makeRep(t nodeType, min, max int) {
+ n.t += (t - ntOne)
+ n.m = min
+ n.n = max
+}
+
+func (n *regexNode) reduce() *regexNode {
+ switch n.t {
+ case ntAlternate:
+ return n.reduceAlternation()
+
+ case ntConcatenate:
+ return n.reduceConcatenation()
+
+ case ntLoop, ntLazyloop:
+ return n.reduceRep()
+
+ case ntGroup:
+ return n.reduceGroup()
+
+ case ntSet, ntSetloop:
+ return n.reduceSet()
+
+ default:
+ return n
+ }
+}
+
+// Basic optimization. Single-letter alternations can be replaced
+// by faster set specifications, and nested alternations with no
+// intervening operators can be flattened:
+//
+// a|b|c|def|g|h -> [a-c]|def|[gh]
+// apple|(?:orange|pear)|grape -> apple|orange|pear|grape
+func (n *regexNode) reduceAlternation() *regexNode {
+ if len(n.children) == 0 {
+ return newRegexNode(ntNothing, n.options)
+ }
+
+ wasLastSet := false
+ lastNodeCannotMerge := false
+ var optionsLast RegexOptions
+ var i, j int
+
+ for i, j = 0, 0; i < len(n.children); i, j = i+1, j+1 {
+ at := n.children[i]
+
+ if j < i {
+ n.children[j] = at
+ }
+
+ for {
+ if at.t == ntAlternate {
+ for k := 0; k < len(at.children); k++ {
+ at.children[k].next = n
+ }
+ n.insertChildren(i+1, at.children)
+
+ j--
+ } else if at.t == ntSet || at.t == ntOne {
+ // Cannot merge sets if L or I options differ, or if either are negated.
+ optionsAt := at.options & (RightToLeft | IgnoreCase)
+
+ if at.t == ntSet {
+ if !wasLastSet || optionsLast != optionsAt || lastNodeCannotMerge || !at.set.IsMergeable() {
+ wasLastSet = true
+ lastNodeCannotMerge = !at.set.IsMergeable()
+ optionsLast = optionsAt
+ break
+ }
+ } else if !wasLastSet || optionsLast != optionsAt || lastNodeCannotMerge {
+ wasLastSet = true
+ lastNodeCannotMerge = false
+ optionsLast = optionsAt
+ break
+ }
+
+ // The last node was a Set or a One, we're a Set or One and our options are the same.
+ // Merge the two nodes.
+ j--
+ prev := n.children[j]
+
+ var prevCharClass *CharSet
+ if prev.t == ntOne {
+ prevCharClass = &CharSet{}
+ prevCharClass.addChar(prev.ch)
+ } else {
+ prevCharClass = prev.set
+ }
+
+ if at.t == ntOne {
+ prevCharClass.addChar(at.ch)
+ } else {
+ prevCharClass.addSet(*at.set)
+ }
+
+ prev.t = ntSet
+ prev.set = prevCharClass
+ } else if at.t == ntNothing {
+ j--
+ } else {
+ wasLastSet = false
+ lastNodeCannotMerge = false
+ }
+ break
+ }
+ }
+
+ if j < i {
+ n.removeChildren(j, i)
+ }
+
+ return n.stripEnation(ntNothing)
+}
+
+// Basic optimization. Adjacent strings can be concatenated.
+//
+// (?:abc)(?:def) -> abcdef
+func (n *regexNode) reduceConcatenation() *regexNode {
+ // Eliminate empties and concat adjacent strings/chars
+
+ var optionsLast RegexOptions
+ var optionsAt RegexOptions
+ var i, j int
+
+ if len(n.children) == 0 {
+ return newRegexNode(ntEmpty, n.options)
+ }
+
+ wasLastString := false
+
+ for i, j = 0, 0; i < len(n.children); i, j = i+1, j+1 {
+ var at, prev *regexNode
+
+ at = n.children[i]
+
+ if j < i {
+ n.children[j] = at
+ }
+
+ if at.t == ntConcatenate &&
+ ((at.options & RightToLeft) == (n.options & RightToLeft)) {
+ for k := 0; k < len(at.children); k++ {
+ at.children[k].next = n
+ }
+
+ //insert at.children at i+1 index in n.children
+ n.insertChildren(i+1, at.children)
+
+ j--
+ } else if at.t == ntMulti || at.t == ntOne {
+ // Cannot merge strings if L or I options differ
+ optionsAt = at.options & (RightToLeft | IgnoreCase)
+
+ if !wasLastString || optionsLast != optionsAt {
+ wasLastString = true
+ optionsLast = optionsAt
+ continue
+ }
+
+ j--
+ prev = n.children[j]
+
+ if prev.t == ntOne {
+ prev.t = ntMulti
+ prev.str = []rune{prev.ch}
+ }
+
+ if (optionsAt & RightToLeft) == 0 {
+ if at.t == ntOne {
+ prev.str = append(prev.str, at.ch)
+ } else {
+ prev.str = append(prev.str, at.str...)
+ }
+ } else {
+ if at.t == ntOne {
+ // insert at the front by expanding our slice, copying the data over, and then setting the value
+ prev.str = append(prev.str, 0)
+ copy(prev.str[1:], prev.str)
+ prev.str[0] = at.ch
+ } else {
+ //insert at the front...this one we'll make a new slice and copy both into it
+ merge := make([]rune, len(prev.str)+len(at.str))
+ copy(merge, at.str)
+ copy(merge[len(at.str):], prev.str)
+ prev.str = merge
+ }
+ }
+ } else if at.t == ntEmpty {
+ j--
+ } else {
+ wasLastString = false
+ }
+ }
+
+ if j < i {
+ // remove indices j through i from the children
+ n.removeChildren(j, i)
+ }
+
+ return n.stripEnation(ntEmpty)
+}
+
+// Nested repeaters just get multiplied with each other if they're not
+// too lumpy
+func (n *regexNode) reduceRep() *regexNode {
+
+ u := n
+ t := n.t
+ min := n.m
+ max := n.n
+
+ for {
+ if len(u.children) == 0 {
+ break
+ }
+
+ child := u.children[0]
+
+ // multiply reps of the same type only
+ if child.t != t {
+ childType := child.t
+
+ if !(childType >= ntOneloop && childType <= ntSetloop && t == ntLoop ||
+ childType >= ntOnelazy && childType <= ntSetlazy && t == ntLazyloop) {
+ break
+ }
+ }
+
+ // child can be too lumpy to blur, e.g., (a {100,105}) {3} or (a {2,})?
+ // [but things like (a {2,})+ are not too lumpy...]
+ if u.m == 0 && child.m > 1 || child.n < child.m*2 {
+ break
+ }
+
+ u = child
+ if u.m > 0 {
+ if (math.MaxInt32-1)/u.m < min {
+ u.m = math.MaxInt32
+ } else {
+ u.m = u.m * min
+ }
+ }
+ if u.n > 0 {
+ if (math.MaxInt32-1)/u.n < max {
+ u.n = math.MaxInt32
+ } else {
+ u.n = u.n * max
+ }
+ }
+ }
+
+ if math.MaxInt32 == min {
+ return newRegexNode(ntNothing, n.options)
+ }
+ return u
+
+}
+
+// Simple optimization. If a concatenation or alternation has only
+// one child strip out the intermediate node. If it has zero children,
+// turn it into an empty.
+func (n *regexNode) stripEnation(emptyType nodeType) *regexNode {
+ switch len(n.children) {
+ case 0:
+ return newRegexNode(emptyType, n.options)
+ case 1:
+ return n.children[0]
+ default:
+ return n
+ }
+}
+
+func (n *regexNode) reduceGroup() *regexNode {
+ u := n
+
+ for u.t == ntGroup {
+ u = u.children[0]
+ }
+
+ return u
+}
+
+// Simple optimization. If a set is a singleton, an inverse singleton,
+// or empty, it's transformed accordingly.
+func (n *regexNode) reduceSet() *regexNode {
+ // Extract empty-set, one and not-one case as special
+
+ if n.set == nil {
+ n.t = ntNothing
+ } else if n.set.IsSingleton() {
+ n.ch = n.set.SingletonChar()
+ n.set = nil
+ n.t += (ntOne - ntSet)
+ } else if n.set.IsSingletonInverse() {
+ n.ch = n.set.SingletonChar()
+ n.set = nil
+ n.t += (ntNotone - ntSet)
+ }
+
+ return n
+}
+
+func (n *regexNode) reverseLeft() *regexNode {
+ if n.options&RightToLeft != 0 && n.t == ntConcatenate && len(n.children) > 0 {
+ //reverse children order
+ for left, right := 0, len(n.children)-1; left < right; left, right = left+1, right-1 {
+ n.children[left], n.children[right] = n.children[right], n.children[left]
+ }
+ }
+
+ return n
+}
+
+func (n *regexNode) makeQuantifier(lazy bool, min, max int) *regexNode {
+ if min == 0 && max == 0 {
+ return newRegexNode(ntEmpty, n.options)
+ }
+
+ if min == 1 && max == 1 {
+ return n
+ }
+
+ switch n.t {
+ case ntOne, ntNotone, ntSet:
+ if lazy {
+ n.makeRep(Onelazy, min, max)
+ } else {
+ n.makeRep(Oneloop, min, max)
+ }
+ return n
+
+ default:
+ var t nodeType
+ if lazy {
+ t = ntLazyloop
+ } else {
+ t = ntLoop
+ }
+ result := newRegexNodeMN(t, n.options, min, max)
+ result.addChild(n)
+ return result
+ }
+}
+
+// debug functions
+
+var typeStr = []string{
+ "Onerep", "Notonerep", "Setrep",
+ "Oneloop", "Notoneloop", "Setloop",
+ "Onelazy", "Notonelazy", "Setlazy",
+ "One", "Notone", "Set",
+ "Multi", "Ref",
+ "Bol", "Eol", "Boundary", "Nonboundary",
+ "Beginning", "Start", "EndZ", "End",
+ "Nothing", "Empty",
+ "Alternate", "Concatenate",
+ "Loop", "Lazyloop",
+ "Capture", "Group", "Require", "Prevent", "Greedy",
+ "Testref", "Testgroup",
+ "Unknown", "Unknown", "Unknown",
+ "Unknown", "Unknown", "Unknown",
+ "ECMABoundary", "NonECMABoundary",
+}
+
+func (n *regexNode) description() string {
+ buf := &bytes.Buffer{}
+
+ buf.WriteString(typeStr[n.t])
+
+ if (n.options & ExplicitCapture) != 0 {
+ buf.WriteString("-C")
+ }
+ if (n.options & IgnoreCase) != 0 {
+ buf.WriteString("-I")
+ }
+ if (n.options & RightToLeft) != 0 {
+ buf.WriteString("-L")
+ }
+ if (n.options & Multiline) != 0 {
+ buf.WriteString("-M")
+ }
+ if (n.options & Singleline) != 0 {
+ buf.WriteString("-S")
+ }
+ if (n.options & IgnorePatternWhitespace) != 0 {
+ buf.WriteString("-X")
+ }
+ if (n.options & ECMAScript) != 0 {
+ buf.WriteString("-E")
+ }
+
+ switch n.t {
+ case ntOneloop, ntNotoneloop, ntOnelazy, ntNotonelazy, ntOne, ntNotone:
+ buf.WriteString("(Ch = " + CharDescription(n.ch) + ")")
+ break
+ case ntCapture:
+ buf.WriteString("(index = " + strconv.Itoa(n.m) + ", unindex = " + strconv.Itoa(n.n) + ")")
+ break
+ case ntRef, ntTestref:
+ buf.WriteString("(index = " + strconv.Itoa(n.m) + ")")
+ break
+ case ntMulti:
+ fmt.Fprintf(buf, "(String = %s)", string(n.str))
+ break
+ case ntSet, ntSetloop, ntSetlazy:
+ buf.WriteString("(Set = " + n.set.String() + ")")
+ break
+ }
+
+ switch n.t {
+ case ntOneloop, ntNotoneloop, ntOnelazy, ntNotonelazy, ntSetloop, ntSetlazy, ntLoop, ntLazyloop:
+ buf.WriteString("(Min = ")
+ buf.WriteString(strconv.Itoa(n.m))
+ buf.WriteString(", Max = ")
+ if n.n == math.MaxInt32 {
+ buf.WriteString("inf")
+ } else {
+ buf.WriteString(strconv.Itoa(n.n))
+ }
+ buf.WriteString(")")
+
+ break
+ }
+
+ return buf.String()
+}
+
+var padSpace = []byte(" ")
+
+func (t *RegexTree) Dump() string {
+ return t.root.dump()
+}
+
+func (n *regexNode) dump() string {
+ var stack []int
+ CurNode := n
+ CurChild := 0
+
+ buf := bytes.NewBufferString(CurNode.description())
+ buf.WriteRune('\n')
+
+ for {
+ if CurNode.children != nil && CurChild < len(CurNode.children) {
+ stack = append(stack, CurChild+1)
+ CurNode = CurNode.children[CurChild]
+ CurChild = 0
+
+ Depth := len(stack)
+ if Depth > 32 {
+ Depth = 32
+ }
+ buf.Write(padSpace[:Depth])
+ buf.WriteString(CurNode.description())
+ buf.WriteRune('\n')
+ } else {
+ if len(stack) == 0 {
+ break
+ }
+
+ CurChild = stack[len(stack)-1]
+ stack = stack[:len(stack)-1]
+ CurNode = CurNode.next
+ }
+ }
+ return buf.String()
+}
diff --git a/vendor/github.com/dlclark/regexp2/syntax/writer.go b/vendor/github.com/dlclark/regexp2/syntax/writer.go
new file mode 100644
index 00000000..a5aa11ca
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/syntax/writer.go
@@ -0,0 +1,500 @@
+package syntax
+
+import (
+ "bytes"
+ "fmt"
+ "math"
+ "os"
+)
+
+func Write(tree *RegexTree) (*Code, error) {
+ w := writer{
+ intStack: make([]int, 0, 32),
+ emitted: make([]int, 2),
+ stringhash: make(map[string]int),
+ sethash: make(map[string]int),
+ }
+
+ code, err := w.codeFromTree(tree)
+
+ if tree.options&Debug > 0 && code != nil {
+ os.Stdout.WriteString(code.Dump())
+ os.Stdout.WriteString("\n")
+ }
+
+ return code, err
+}
+
+type writer struct {
+ emitted []int
+
+ intStack []int
+ curpos int
+ stringhash map[string]int
+ stringtable [][]rune
+ sethash map[string]int
+ settable []*CharSet
+ counting bool
+ count int
+ trackcount int
+ caps map[int]int
+}
+
+const (
+ beforeChild nodeType = 64
+ afterChild = 128
+ //MaxPrefixSize is the largest number of runes we'll use for a BoyerMoyer prefix
+ MaxPrefixSize = 50
+)
+
+// The top level RegexCode generator. It does a depth-first walk
+// through the tree and calls EmitFragment to emits code before
+// and after each child of an interior node, and at each leaf.
+//
+// It runs two passes, first to count the size of the generated
+// code, and second to generate the code.
+//
+// We should time it against the alternative, which is
+// to just generate the code and grow the array as we go.
+func (w *writer) codeFromTree(tree *RegexTree) (*Code, error) {
+ var (
+ curNode *regexNode
+ curChild int
+ capsize int
+ )
+ // construct sparse capnum mapping if some numbers are unused
+
+ if tree.capnumlist == nil || tree.captop == len(tree.capnumlist) {
+ capsize = tree.captop
+ w.caps = nil
+ } else {
+ capsize = len(tree.capnumlist)
+ w.caps = tree.caps
+ for i := 0; i < len(tree.capnumlist); i++ {
+ w.caps[tree.capnumlist[i]] = i
+ }
+ }
+
+ w.counting = true
+
+ for {
+ if !w.counting {
+ w.emitted = make([]int, w.count)
+ }
+
+ curNode = tree.root
+ curChild = 0
+
+ w.emit1(Lazybranch, 0)
+
+ for {
+ if len(curNode.children) == 0 {
+ w.emitFragment(curNode.t, curNode, 0)
+ } else if curChild < len(curNode.children) {
+ w.emitFragment(curNode.t|beforeChild, curNode, curChild)
+
+ curNode = curNode.children[curChild]
+
+ w.pushInt(curChild)
+ curChild = 0
+ continue
+ }
+
+ if w.emptyStack() {
+ break
+ }
+
+ curChild = w.popInt()
+ curNode = curNode.next
+
+ w.emitFragment(curNode.t|afterChild, curNode, curChild)
+ curChild++
+ }
+
+ w.patchJump(0, w.curPos())
+ w.emit(Stop)
+
+ if !w.counting {
+ break
+ }
+
+ w.counting = false
+ }
+
+ fcPrefix := getFirstCharsPrefix(tree)
+ prefix := getPrefix(tree)
+ rtl := (tree.options & RightToLeft) != 0
+
+ var bmPrefix *BmPrefix
+ //TODO: benchmark string prefixes
+ if prefix != nil && len(prefix.PrefixStr) > 0 && MaxPrefixSize > 0 {
+ if len(prefix.PrefixStr) > MaxPrefixSize {
+ // limit prefix changes to 10k
+ prefix.PrefixStr = prefix.PrefixStr[:MaxPrefixSize]
+ }
+ bmPrefix = newBmPrefix(prefix.PrefixStr, prefix.CaseInsensitive, rtl)
+ } else {
+ bmPrefix = nil
+ }
+
+ return &Code{
+ Codes: w.emitted,
+ Strings: w.stringtable,
+ Sets: w.settable,
+ TrackCount: w.trackcount,
+ Caps: w.caps,
+ Capsize: capsize,
+ FcPrefix: fcPrefix,
+ BmPrefix: bmPrefix,
+ Anchors: getAnchors(tree),
+ RightToLeft: rtl,
+ }, nil
+}
+
+// The main RegexCode generator. It does a depth-first walk
+// through the tree and calls EmitFragment to emits code before
+// and after each child of an interior node, and at each leaf.
+func (w *writer) emitFragment(nodetype nodeType, node *regexNode, curIndex int) error {
+ bits := InstOp(0)
+
+ if nodetype <= ntRef {
+ if (node.options & RightToLeft) != 0 {
+ bits |= Rtl
+ }
+ if (node.options & IgnoreCase) != 0 {
+ bits |= Ci
+ }
+ }
+ ntBits := nodeType(bits)
+
+ switch nodetype {
+ case ntConcatenate | beforeChild, ntConcatenate | afterChild, ntEmpty:
+ break
+
+ case ntAlternate | beforeChild:
+ if curIndex < len(node.children)-1 {
+ w.pushInt(w.curPos())
+ w.emit1(Lazybranch, 0)
+ }
+
+ case ntAlternate | afterChild:
+ if curIndex < len(node.children)-1 {
+ lbPos := w.popInt()
+ w.pushInt(w.curPos())
+ w.emit1(Goto, 0)
+ w.patchJump(lbPos, w.curPos())
+ } else {
+ for i := 0; i < curIndex; i++ {
+ w.patchJump(w.popInt(), w.curPos())
+ }
+ }
+ break
+
+ case ntTestref | beforeChild:
+ if curIndex == 0 {
+ w.emit(Setjump)
+ w.pushInt(w.curPos())
+ w.emit1(Lazybranch, 0)
+ w.emit1(Testref, w.mapCapnum(node.m))
+ w.emit(Forejump)
+ }
+
+ case ntTestref | afterChild:
+ if curIndex == 0 {
+ branchpos := w.popInt()
+ w.pushInt(w.curPos())
+ w.emit1(Goto, 0)
+ w.patchJump(branchpos, w.curPos())
+ w.emit(Forejump)
+ if len(node.children) <= 1 {
+ w.patchJump(w.popInt(), w.curPos())
+ }
+ } else if curIndex == 1 {
+ w.patchJump(w.popInt(), w.curPos())
+ }
+
+ case ntTestgroup | beforeChild:
+ if curIndex == 0 {
+ w.emit(Setjump)
+ w.emit(Setmark)
+ w.pushInt(w.curPos())
+ w.emit1(Lazybranch, 0)
+ }
+
+ case ntTestgroup | afterChild:
+ if curIndex == 0 {
+ w.emit(Getmark)
+ w.emit(Forejump)
+ } else if curIndex == 1 {
+ Branchpos := w.popInt()
+ w.pushInt(w.curPos())
+ w.emit1(Goto, 0)
+ w.patchJump(Branchpos, w.curPos())
+ w.emit(Getmark)
+ w.emit(Forejump)
+ if len(node.children) <= 2 {
+ w.patchJump(w.popInt(), w.curPos())
+ }
+ } else if curIndex == 2 {
+ w.patchJump(w.popInt(), w.curPos())
+ }
+
+ case ntLoop | beforeChild, ntLazyloop | beforeChild:
+
+ if node.n < math.MaxInt32 || node.m > 1 {
+ if node.m == 0 {
+ w.emit1(Nullcount, 0)
+ } else {
+ w.emit1(Setcount, 1-node.m)
+ }
+ } else if node.m == 0 {
+ w.emit(Nullmark)
+ } else {
+ w.emit(Setmark)
+ }
+
+ if node.m == 0 {
+ w.pushInt(w.curPos())
+ w.emit1(Goto, 0)
+ }
+ w.pushInt(w.curPos())
+
+ case ntLoop | afterChild, ntLazyloop | afterChild:
+
+ startJumpPos := w.curPos()
+ lazy := (nodetype - (ntLoop | afterChild))
+
+ if node.n < math.MaxInt32 || node.m > 1 {
+ if node.n == math.MaxInt32 {
+ w.emit2(InstOp(Branchcount+lazy), w.popInt(), math.MaxInt32)
+ } else {
+ w.emit2(InstOp(Branchcount+lazy), w.popInt(), node.n-node.m)
+ }
+ } else {
+ w.emit1(InstOp(Branchmark+lazy), w.popInt())
+ }
+
+ if node.m == 0 {
+ w.patchJump(w.popInt(), startJumpPos)
+ }
+
+ case ntGroup | beforeChild, ntGroup | afterChild:
+
+ case ntCapture | beforeChild:
+ w.emit(Setmark)
+
+ case ntCapture | afterChild:
+ w.emit2(Capturemark, w.mapCapnum(node.m), w.mapCapnum(node.n))
+
+ case ntRequire | beforeChild:
+ // NOTE: the following line causes lookahead/lookbehind to be
+ // NON-BACKTRACKING. It can be commented out with (*)
+ w.emit(Setjump)
+
+ w.emit(Setmark)
+
+ case ntRequire | afterChild:
+ w.emit(Getmark)
+
+ // NOTE: the following line causes lookahead/lookbehind to be
+ // NON-BACKTRACKING. It can be commented out with (*)
+ w.emit(Forejump)
+
+ case ntPrevent | beforeChild:
+ w.emit(Setjump)
+ w.pushInt(w.curPos())
+ w.emit1(Lazybranch, 0)
+
+ case ntPrevent | afterChild:
+ w.emit(Backjump)
+ w.patchJump(w.popInt(), w.curPos())
+ w.emit(Forejump)
+
+ case ntGreedy | beforeChild:
+ w.emit(Setjump)
+
+ case ntGreedy | afterChild:
+ w.emit(Forejump)
+
+ case ntOne, ntNotone:
+ w.emit1(InstOp(node.t|ntBits), int(node.ch))
+
+ case ntNotoneloop, ntNotonelazy, ntOneloop, ntOnelazy:
+ if node.m > 0 {
+ if node.t == ntOneloop || node.t == ntOnelazy {
+ w.emit2(Onerep|bits, int(node.ch), node.m)
+ } else {
+ w.emit2(Notonerep|bits, int(node.ch), node.m)
+ }
+ }
+ if node.n > node.m {
+ if node.n == math.MaxInt32 {
+ w.emit2(InstOp(node.t|ntBits), int(node.ch), math.MaxInt32)
+ } else {
+ w.emit2(InstOp(node.t|ntBits), int(node.ch), node.n-node.m)
+ }
+ }
+
+ case ntSetloop, ntSetlazy:
+ if node.m > 0 {
+ w.emit2(Setrep|bits, w.setCode(node.set), node.m)
+ }
+ if node.n > node.m {
+ if node.n == math.MaxInt32 {
+ w.emit2(InstOp(node.t|ntBits), w.setCode(node.set), math.MaxInt32)
+ } else {
+ w.emit2(InstOp(node.t|ntBits), w.setCode(node.set), node.n-node.m)
+ }
+ }
+
+ case ntMulti:
+ w.emit1(InstOp(node.t|ntBits), w.stringCode(node.str))
+
+ case ntSet:
+ w.emit1(InstOp(node.t|ntBits), w.setCode(node.set))
+
+ case ntRef:
+ w.emit1(InstOp(node.t|ntBits), w.mapCapnum(node.m))
+
+ case ntNothing, ntBol, ntEol, ntBoundary, ntNonboundary, ntECMABoundary, ntNonECMABoundary, ntBeginning, ntStart, ntEndZ, ntEnd:
+ w.emit(InstOp(node.t))
+
+ default:
+ return fmt.Errorf("unexpected opcode in regular expression generation: %v", nodetype)
+ }
+
+ return nil
+}
+
+// To avoid recursion, we use a simple integer stack.
+// This is the push.
+func (w *writer) pushInt(i int) {
+ w.intStack = append(w.intStack, i)
+}
+
+// Returns true if the stack is empty.
+func (w *writer) emptyStack() bool {
+ return len(w.intStack) == 0
+}
+
+// This is the pop.
+func (w *writer) popInt() int {
+ //get our item
+ idx := len(w.intStack) - 1
+ i := w.intStack[idx]
+ //trim our slice
+ w.intStack = w.intStack[:idx]
+ return i
+}
+
+// Returns the current position in the emitted code.
+func (w *writer) curPos() int {
+ return w.curpos
+}
+
+// Fixes up a jump instruction at the specified offset
+// so that it jumps to the specified jumpDest.
+func (w *writer) patchJump(offset, jumpDest int) {
+ w.emitted[offset+1] = jumpDest
+}
+
+// Returns an index in the set table for a charset
+// uses a map to eliminate duplicates.
+func (w *writer) setCode(set *CharSet) int {
+ if w.counting {
+ return 0
+ }
+
+ buf := &bytes.Buffer{}
+
+ set.mapHashFill(buf)
+ hash := buf.String()
+ i, ok := w.sethash[hash]
+ if !ok {
+ i = len(w.sethash)
+ w.sethash[hash] = i
+ w.settable = append(w.settable, set)
+ }
+ return i
+}
+
+// Returns an index in the string table for a string.
+// uses a map to eliminate duplicates.
+func (w *writer) stringCode(str []rune) int {
+ if w.counting {
+ return 0
+ }
+
+ hash := string(str)
+ i, ok := w.stringhash[hash]
+ if !ok {
+ i = len(w.stringhash)
+ w.stringhash[hash] = i
+ w.stringtable = append(w.stringtable, str)
+ }
+
+ return i
+}
+
+// When generating code on a regex that uses a sparse set
+// of capture slots, we hash them to a dense set of indices
+// for an array of capture slots. Instead of doing the hash
+// at match time, it's done at compile time, here.
+func (w *writer) mapCapnum(capnum int) int {
+ if capnum == -1 {
+ return -1
+ }
+
+ if w.caps != nil {
+ return w.caps[capnum]
+ }
+
+ return capnum
+}
+
+// Emits a zero-argument operation. Note that the emit
+// functions all run in two modes: they can emit code, or
+// they can just count the size of the code.
+func (w *writer) emit(op InstOp) {
+ if w.counting {
+ w.count++
+ if opcodeBacktracks(op) {
+ w.trackcount++
+ }
+ return
+ }
+ w.emitted[w.curpos] = int(op)
+ w.curpos++
+}
+
+// Emits a one-argument operation.
+func (w *writer) emit1(op InstOp, opd1 int) {
+ if w.counting {
+ w.count += 2
+ if opcodeBacktracks(op) {
+ w.trackcount++
+ }
+ return
+ }
+ w.emitted[w.curpos] = int(op)
+ w.curpos++
+ w.emitted[w.curpos] = opd1
+ w.curpos++
+}
+
+// Emits a two-argument operation.
+func (w *writer) emit2(op InstOp, opd1, opd2 int) {
+ if w.counting {
+ w.count += 3
+ if opcodeBacktracks(op) {
+ w.trackcount++
+ }
+ return
+ }
+ w.emitted[w.curpos] = int(op)
+ w.curpos++
+ w.emitted[w.curpos] = opd1
+ w.curpos++
+ w.emitted[w.curpos] = opd2
+ w.curpos++
+}
diff --git a/vendor/github.com/dlclark/regexp2/testoutput1 b/vendor/github.com/dlclark/regexp2/testoutput1
new file mode 100644
index 00000000..fbf63fdf
--- /dev/null
+++ b/vendor/github.com/dlclark/regexp2/testoutput1
@@ -0,0 +1,7061 @@
+# This set of tests is for features that are compatible with all versions of
+# Perl >= 5.10, in non-UTF mode. It should run clean for the 8-bit, 16-bit, and
+# 32-bit PCRE libraries, and also using the perltest.pl script.
+
+#forbid_utf
+#newline_default lf any anycrlf
+#perltest
+
+/the quick brown fox/
+ the quick brown fox
+ 0: the quick brown fox
+ What do you know about the quick brown fox?
+ 0: the quick brown fox
+\= Expect no match
+ The quick brown FOX
+No match
+ What do you know about THE QUICK BROWN FOX?
+No match
+
+/The quick brown fox/i
+ the quick brown fox
+ 0: the quick brown fox
+ The quick brown FOX
+ 0: The quick brown FOX
+ What do you know about the quick brown fox?
+ 0: the quick brown fox
+ What do you know about THE QUICK BROWN FOX?
+ 0: THE QUICK BROWN FOX
+
+/abcd\t\n\r\f\a\e\071\x3b\$\\\?caxyz/
+ abcd\t\n\r\f\a\e9;\$\\?caxyz
+ 0: abcd\x09\x0a\x0d\x0c\x07\x1b9;$\?caxyz
+
+/a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz/
+ abxyzpqrrrabbxyyyypqAzz
+ 0: abxyzpqrrrabbxyyyypqAzz
+ abxyzpqrrrabbxyyyypqAzz
+ 0: abxyzpqrrrabbxyyyypqAzz
+ aabxyzpqrrrabbxyyyypqAzz
+ 0: aabxyzpqrrrabbxyyyypqAzz
+ aaabxyzpqrrrabbxyyyypqAzz
+ 0: aaabxyzpqrrrabbxyyyypqAzz
+ aaaabxyzpqrrrabbxyyyypqAzz
+ 0: aaaabxyzpqrrrabbxyyyypqAzz
+ abcxyzpqrrrabbxyyyypqAzz
+ 0: abcxyzpqrrrabbxyyyypqAzz
+ aabcxyzpqrrrabbxyyyypqAzz
+ 0: aabcxyzpqrrrabbxyyyypqAzz
+ aaabcxyzpqrrrabbxyyyypAzz
+ 0: aaabcxyzpqrrrabbxyyyypAzz
+ aaabcxyzpqrrrabbxyyyypqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqAzz
+ aaabcxyzpqrrrabbxyyyypqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqqqqAzz
+ aaaabcxyzpqrrrabbxyyyypqAzz
+ 0: aaaabcxyzpqrrrabbxyyyypqAzz
+ abxyzzpqrrrabbxyyyypqAzz
+ 0: abxyzzpqrrrabbxyyyypqAzz
+ aabxyzzzpqrrrabbxyyyypqAzz
+ 0: aabxyzzzpqrrrabbxyyyypqAzz
+ aaabxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaabxyzzzzpqrrrabbxyyyypqAzz
+ aaaabxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaaabxyzzzzpqrrrabbxyyyypqAzz
+ abcxyzzpqrrrabbxyyyypqAzz
+ 0: abcxyzzpqrrrabbxyyyypqAzz
+ aabcxyzzzpqrrrabbxyyyypqAzz
+ 0: aabcxyzzzpqrrrabbxyyyypqAzz
+ aaabcxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaabcxyzzzzpqrrrabbxyyyypqAzz
+ aaaabcxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaaabcxyzzzzpqrrrabbxyyyypqAzz
+ aaaabcxyzzzzpqrrrabbbxyyyypqAzz
+ 0: aaaabcxyzzzzpqrrrabbbxyyyypqAzz
+ aaaabcxyzzzzpqrrrabbbxyyyyypqAzz
+ 0: aaaabcxyzzzzpqrrrabbbxyyyyypqAzz
+ aaabcxyzpqrrrabbxyyyypABzz
+ 0: aaabcxyzpqrrrabbxyyyypABzz
+ aaabcxyzpqrrrabbxyyyypABBzz
+ 0: aaabcxyzpqrrrabbxyyyypABBzz
+ >>>aaabxyzpqrrrabbxyyyypqAzz
+ 0: aaabxyzpqrrrabbxyyyypqAzz
+ >aaaabxyzpqrrrabbxyyyypqAzz
+ 0: aaaabxyzpqrrrabbxyyyypqAzz
+ >>>>abcxyzpqrrrabbxyyyypqAzz
+ 0: abcxyzpqrrrabbxyyyypqAzz
+\= Expect no match
+ abxyzpqrrabbxyyyypqAzz
+No match
+ abxyzpqrrrrabbxyyyypqAzz
+No match
+ abxyzpqrrrabxyyyypqAzz
+No match
+ aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz
+No match
+ aaaabcxyzzzzpqrrrabbbxyyypqAzz
+No match
+ aaabcxyzpqrrrabbxyyyypqqqqqqqAzz
+No match
+
+/^(abc){1,2}zz/
+ abczz
+ 0: abczz
+ 1: abc
+ abcabczz
+ 0: abcabczz
+ 1: abc
+\= Expect no match
+ zz
+No match
+ abcabcabczz
+No match
+ >>abczz
+No match
+
+/^(b+?|a){1,2}?c/
+ bc
+ 0: bc
+ 1: b
+ bbc
+ 0: bbc
+ 1: b
+ bbbc
+ 0: bbbc
+ 1: bb
+ bac
+ 0: bac
+ 1: a
+ bbac
+ 0: bbac
+ 1: a
+ aac
+ 0: aac
+ 1: a
+ abbbbbbbbbbbc
+ 0: abbbbbbbbbbbc
+ 1: bbbbbbbbbbb
+ bbbbbbbbbbbac
+ 0: bbbbbbbbbbbac
+ 1: a
+\= Expect no match
+ aaac
+No match
+ abbbbbbbbbbbac
+No match
+
+/^(b+|a){1,2}c/
+ bc
+ 0: bc
+ 1: b
+ bbc
+ 0: bbc
+ 1: bb
+ bbbc
+ 0: bbbc
+ 1: bbb
+ bac
+ 0: bac
+ 1: a
+ bbac
+ 0: bbac
+ 1: a
+ aac
+ 0: aac
+ 1: a
+ abbbbbbbbbbbc
+ 0: abbbbbbbbbbbc
+ 1: bbbbbbbbbbb
+ bbbbbbbbbbbac
+ 0: bbbbbbbbbbbac
+ 1: a
+\= Expect no match
+ aaac
+No match
+ abbbbbbbbbbbac
+No match
+
+/^(b+|a){1,2}?bc/
+ bbc
+ 0: bbc
+ 1: b
+
+/^(b*|ba){1,2}?bc/
+ babc
+ 0: babc
+ 1: ba
+ bbabc
+ 0: bbabc
+ 1: ba
+ bababc
+ 0: bababc
+ 1: ba
+\= Expect no match
+ bababbc
+No match
+ babababc
+No match
+
+/^(ba|b*){1,2}?bc/
+ babc
+ 0: babc
+ 1: ba
+ bbabc
+ 0: bbabc
+ 1: ba
+ bababc
+ 0: bababc
+ 1: ba
+\= Expect no match
+ bababbc
+No match
+ babababc
+No match
+
+#/^\ca\cA\c[;\c:/
+# \x01\x01\e;z
+# 0: \x01\x01\x1b;z
+
+/^[ab\]cde]/
+ athing
+ 0: a
+ bthing
+ 0: b
+ ]thing
+ 0: ]
+ cthing
+ 0: c
+ dthing
+ 0: d
+ ething
+ 0: e
+\= Expect no match
+ fthing
+No match
+ [thing
+No match
+ \\thing
+No match
+
+/^[]cde]/
+ ]thing
+ 0: ]
+ cthing
+ 0: c
+ dthing
+ 0: d
+ ething
+ 0: e
+\= Expect no match
+ athing
+No match
+ fthing
+No match
+
+/^[^ab\]cde]/
+ fthing
+ 0: f
+ [thing
+ 0: [
+ \\thing
+ 0: \
+\= Expect no match
+ athing
+No match
+ bthing
+No match
+ ]thing
+No match
+ cthing
+No match
+ dthing
+No match
+ ething
+No match
+
+/^[^]cde]/
+ athing
+ 0: a
+ fthing
+ 0: f
+\= Expect no match
+ ]thing
+No match
+ cthing
+No match
+ dthing
+No match
+ ething
+No match
+
+# DLC - I don't get this one
+#/^\/
+#
+# 0: \x81
+
+#updated to handle 16-bits utf8
+/^ÿ/
+ ÿ
+ 0: \xc3\xbf
+
+/^[0-9]+$/
+ 0
+ 0: 0
+ 1
+ 0: 1
+ 2
+ 0: 2
+ 3
+ 0: 3
+ 4
+ 0: 4
+ 5
+ 0: 5
+ 6
+ 0: 6
+ 7
+ 0: 7
+ 8
+ 0: 8
+ 9
+ 0: 9
+ 10
+ 0: 10
+ 100
+ 0: 100
+\= Expect no match
+ abc
+No match
+
+/^.*nter/
+ enter
+ 0: enter
+ inter
+ 0: inter
+ uponter
+ 0: uponter
+
+/^xxx[0-9]+$/
+ xxx0
+ 0: xxx0
+ xxx1234
+ 0: xxx1234
+\= Expect no match
+ xxx
+No match
+
+/^.+[0-9][0-9][0-9]$/
+ x123
+ 0: x123
+ x1234
+ 0: x1234
+ xx123
+ 0: xx123
+ 123456
+ 0: 123456
+\= Expect no match
+ 123
+No match
+
+/^.+?[0-9][0-9][0-9]$/
+ x123
+ 0: x123
+ x1234
+ 0: x1234
+ xx123
+ 0: xx123
+ 123456
+ 0: 123456
+\= Expect no match
+ 123
+No match
+
+/^([^!]+)!(.+)=apquxz\.ixr\.zzz\.ac\.uk$/
+ abc!pqr=apquxz.ixr.zzz.ac.uk
+ 0: abc!pqr=apquxz.ixr.zzz.ac.uk
+ 1: abc
+ 2: pqr
+\= Expect no match
+ !pqr=apquxz.ixr.zzz.ac.uk
+No match
+ abc!=apquxz.ixr.zzz.ac.uk
+No match
+ abc!pqr=apquxz:ixr.zzz.ac.uk
+No match
+ abc!pqr=apquxz.ixr.zzz.ac.ukk
+No match
+
+/:/
+ Well, we need a colon: somewhere
+ 0: :
+\= Expect no match
+ Fail without a colon
+No match
+
+/([\da-f:]+)$/i
+ 0abc
+ 0: 0abc
+ 1: 0abc
+ abc
+ 0: abc
+ 1: abc
+ fed
+ 0: fed
+ 1: fed
+ E
+ 0: E
+ 1: E
+ ::
+ 0: ::
+ 1: ::
+ 5f03:12C0::932e
+ 0: 5f03:12C0::932e
+ 1: 5f03:12C0::932e
+ fed def
+ 0: def
+ 1: def
+ Any old stuff
+ 0: ff
+ 1: ff
+\= Expect no match
+ 0zzz
+No match
+ gzzz
+No match
+ fed\x20
+No match
+ Any old rubbish
+No match
+
+/^.*\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
+ .1.2.3
+ 0: .1.2.3
+ 1: 1
+ 2: 2
+ 3: 3
+ A.12.123.0
+ 0: A.12.123.0
+ 1: 12
+ 2: 123
+ 3: 0
+\= Expect no match
+ .1.2.3333
+No match
+ 1.2.3
+No match
+ 1234.2.3
+No match
+
+/^(\d+)\s+IN\s+SOA\s+(\S+)\s+(\S+)\s*\(\s*$/
+ 1 IN SOA non-sp1 non-sp2(
+ 0: 1 IN SOA non-sp1 non-sp2(
+ 1: 1
+ 2: non-sp1
+ 3: non-sp2
+ 1 IN SOA non-sp1 non-sp2 (
+ 0: 1 IN SOA non-sp1 non-sp2 (
+ 1: 1
+ 2: non-sp1
+ 3: non-sp2
+\= Expect no match
+ 1IN SOA non-sp1 non-sp2(
+No match
+
+/^[a-zA-Z\d][a-zA-Z\d\-]*(\.[a-zA-Z\d][a-zA-z\d\-]*)*\.$/
+ a.
+ 0: a.
+ Z.
+ 0: Z.
+ 2.
+ 0: 2.
+ ab-c.pq-r.
+ 0: ab-c.pq-r.
+ 1: .pq-r
+ sxk.zzz.ac.uk.
+ 0: sxk.zzz.ac.uk.
+ 1: .uk
+ x-.y-.
+ 0: x-.y-.
+ 1: .y-
+\= Expect no match
+ -abc.peq.
+No match
+
+/^\*\.[a-z]([a-z\-\d]*[a-z\d]+)?(\.[a-z]([a-z\-\d]*[a-z\d]+)?)*$/
+ *.a
+ 0: *.a
+ *.b0-a
+ 0: *.b0-a
+ 1: 0-a
+ *.c3-b.c
+ 0: *.c3-b.c
+ 1: 3-b
+ 2: .c
+ *.c-a.b-c
+ 0: *.c-a.b-c
+ 1: -a
+ 2: .b-c
+ 3: -c
+\= Expect no match
+ *.0
+No match
+ *.a-
+No match
+ *.a-b.c-
+No match
+ *.c-a.0-c
+No match
+
+/^(?=ab(de))(abd)(e)/
+ abde
+ 0: abde
+ 1: de
+ 2: abd
+ 3: e
+
+/^(?!(ab)de|x)(abd)(f)/
+ abdf
+ 0: abdf
+ 1:
+ 2: abd
+ 3: f
+
+/^(?=(ab(cd)))(ab)/
+ abcd
+ 0: ab
+ 1: abcd
+ 2: cd
+ 3: ab
+
+/^[\da-f](\.[\da-f])*$/i
+ a.b.c.d
+ 0: a.b.c.d
+ 1: .d
+ A.B.C.D
+ 0: A.B.C.D
+ 1: .D
+ a.b.c.1.2.3.C
+ 0: a.b.c.1.2.3.C
+ 1: .C
+
+/^\".*\"\s*(;.*)?$/
+ \"1234\"
+ 0: "1234"
+ \"abcd\" ;
+ 0: "abcd" ;
+ 1: ;
+ \"\" ; rhubarb
+ 0: "" ; rhubarb
+ 1: ; rhubarb
+\= Expect no match
+ \"1234\" : things
+No match
+
+/^$/
+ \
+ 0:
+\= Expect no match
+ A non-empty line
+No match
+
+/ ^ a (?# begins with a) b\sc (?# then b c) $ (?# then end)/x
+ ab c
+ 0: ab c
+\= Expect no match
+ abc
+No match
+ ab cde
+No match
+
+/(?x) ^ a (?# begins with a) b\sc (?# then b c) $ (?# then end)/
+ ab c
+ 0: ab c
+\= Expect no match
+ abc
+No match
+ ab cde
+No match
+
+/^ a\ b[c ]d $/x
+ a bcd
+ 0: a bcd
+ a b d
+ 0: a b d
+\= Expect no match
+ abcd
+No match
+ ab d
+No match
+
+/^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$/
+ abcdefhijklm
+ 0: abcdefhijklm
+ 1: abc
+ 2: bc
+ 3: c
+ 4: def
+ 5: ef
+ 6: f
+ 7: hij
+ 8: ij
+ 9: j
+10: klm
+11: lm
+12: m
+
+/^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$/
+ abcdefhijklm
+ 0: abcdefhijklm
+ 1: bc
+ 2: c
+ 3: ef
+ 4: f
+ 5: ij
+ 6: j
+ 7: lm
+ 8: m
+
+#/^[\w][\W][\s][\S][\d][\D][\b][\n][\c]][\022]/
+# a+ Z0+\x08\n\x1d\x12
+# 0: a+ Z0+\x08\x0a\x1d\x12
+
+/^[.^$|()*+?{,}]+/
+ .^\$(*+)|{?,?}
+ 0: .^$(*+)|{?,?}
+
+/^a*\w/
+ z
+ 0: z
+ az
+ 0: az
+ aaaz
+ 0: aaaz
+ a
+ 0: a
+ aa
+ 0: aa
+ aaaa
+ 0: aaaa
+ a+
+ 0: a
+ aa+
+ 0: aa
+
+/^a*?\w/
+ z
+ 0: z
+ az
+ 0: a
+ aaaz
+ 0: a
+ a
+ 0: a
+ aa
+ 0: a
+ aaaa
+ 0: a
+ a+
+ 0: a
+ aa+
+ 0: a
+
+/^a+\w/
+ az
+ 0: az
+ aaaz
+ 0: aaaz
+ aa
+ 0: aa
+ aaaa
+ 0: aaaa
+ aa+
+ 0: aa
+
+/^a+?\w/
+ az
+ 0: az
+ aaaz
+ 0: aa
+ aa
+ 0: aa
+ aaaa
+ 0: aa
+ aa+
+ 0: aa
+
+/^\d{8}\w{2,}/
+ 1234567890
+ 0: 1234567890
+ 12345678ab
+ 0: 12345678ab
+ 12345678__
+ 0: 12345678__
+\= Expect no match
+ 1234567
+No match
+
+/^[aeiou\d]{4,5}$/
+ uoie
+ 0: uoie
+ 1234
+ 0: 1234
+ 12345
+ 0: 12345
+ aaaaa
+ 0: aaaaa
+\= Expect no match
+ 123456
+No match
+
+/^[aeiou\d]{4,5}?/
+ uoie
+ 0: uoie
+ 1234
+ 0: 1234
+ 12345
+ 0: 1234
+ aaaaa
+ 0: aaaa
+ 123456
+ 0: 1234
+
+/\A(abc|def)=(\1){2,3}\Z/
+ abc=abcabc
+ 0: abc=abcabc
+ 1: abc
+ 2: abc
+ def=defdefdef
+ 0: def=defdefdef
+ 1: def
+ 2: def
+\= Expect no match
+ abc=defdef
+No match
+
+/^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\11*(\3\4)\1(?#)2$/
+ abcdefghijkcda2
+ 0: abcdefghijkcda2
+ 1: a
+ 2: b
+ 3: c
+ 4: d
+ 5: e
+ 6: f
+ 7: g
+ 8: h
+ 9: i
+10: j
+11: k
+12: cd
+ abcdefghijkkkkcda2
+ 0: abcdefghijkkkkcda2
+ 1: a
+ 2: b
+ 3: c
+ 4: d
+ 5: e
+ 6: f
+ 7: g
+ 8: h
+ 9: i
+10: j
+11: k
+12: cd
+
+/(cat(a(ract|tonic)|erpillar)) \1()2(3)/
+ cataract cataract23
+ 0: cataract cataract23
+ 1: cataract
+ 2: aract
+ 3: ract
+ 4:
+ 5: 3
+ catatonic catatonic23
+ 0: catatonic catatonic23
+ 1: catatonic
+ 2: atonic
+ 3: tonic
+ 4:
+ 5: 3
+ caterpillar caterpillar23
+ 0: caterpillar caterpillar23
+ 1: caterpillar
+ 2: erpillar
+ 3:
+ 4:
+ 5: 3
+
+
+/^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]/
+ From abcd Mon Sep 01 12:33:02 1997
+ 0: From abcd Mon Sep 01 12:33
+ 1: abcd
+
+/^From\s+\S+\s+([a-zA-Z]{3}\s+){2}\d{1,2}\s+\d\d:\d\d/
+ From abcd Mon Sep 01 12:33:02 1997
+ 0: From abcd Mon Sep 01 12:33
+ 1: Sep
+ From abcd Mon Sep 1 12:33:02 1997
+ 0: From abcd Mon Sep 1 12:33
+ 1: Sep
+\= Expect no match
+ From abcd Sep 01 12:33:02 1997
+No match
+
+/^12.34/s
+ 12\n34
+ 0: 12\x0a34
+ 12\r34
+ 0: 12\x0d34
+
+/\w+(?=\t)/
+ the quick brown\t fox
+ 0: brown
+
+/foo(?!bar)(.*)/
+ foobar is foolish see?
+ 0: foolish see?
+ 1: lish see?
+
+/(?:(?!foo)...|^.{0,2})bar(.*)/
+ foobar crowbar etc
+ 0: rowbar etc
+ 1: etc
+ barrel
+ 0: barrel
+ 1: rel
+ 2barrel
+ 0: 2barrel
+ 1: rel
+ A barrel
+ 0: A barrel
+ 1: rel
+
+/^(\D*)(?=\d)(?!123)/
+ abc456
+ 0: abc
+ 1: abc
+\= Expect no match
+ abc123
+No match
+
+/^1234(?# test newlines
+ inside)/
+ 1234
+ 0: 1234
+
+/^1234 #comment in extended re
+ /x
+ 1234
+ 0: 1234
+
+/#rhubarb
+ abcd/x
+ abcd
+ 0: abcd
+
+/^abcd#rhubarb/x
+ abcd
+ 0: abcd
+
+/^(a)\1{2,3}(.)/
+ aaab
+ 0: aaab
+ 1: a
+ 2: b
+ aaaab
+ 0: aaaab
+ 1: a
+ 2: b
+ aaaaab
+ 0: aaaaa
+ 1: a
+ 2: a
+ aaaaaab
+ 0: aaaaa
+ 1: a
+ 2: a
+
+/(?!^)abc/
+ the abc
+ 0: abc
+\= Expect no match
+ abc
+No match
+
+/(?=^)abc/
+ abc
+ 0: abc
+\= Expect no match
+ the abc
+No match
+
+/^[ab]{1,3}(ab*|b)/
+ aabbbbb
+ 0: aabb
+ 1: b
+
+/^[ab]{1,3}?(ab*|b)/
+ aabbbbb
+ 0: aabbbbb
+ 1: abbbbb
+
+/^[ab]{1,3}?(ab*?|b)/
+ aabbbbb
+ 0: aa
+ 1: a
+
+/^[ab]{1,3}(ab*?|b)/
+ aabbbbb
+ 0: aabb
+ 1: b
+
+/ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* # optional leading comment
+(?: (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) # initial word
+(?: (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) )* # further okay, if led by a period
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] | # atom and space parts, or...
+\(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) | # comments, or...
+
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+# quoted strings
+)*
+< (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* # leading <
+(?: @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* , (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* )? # optional route
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) # initial word
+(?: (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) )* # further okay, if led by a period
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* > # trailing >
+# name and address
+) (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* # optional trailing comment
+/x
+ Alan Other
+ 0: Alan Other
+
+ 0: user@dom.ain
+ user\@dom.ain
+ 0: user@dom.ain
+ \"A. Other\" (a comment)
+ 0: "A. Other" (a comment)
+ A. Other (a comment)
+ 0: Other (a comment)
+ \"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"\@x400-re.lay
+ 0: "/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/"@x400-re.lay
+ A missing angle @,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+# leading word
+[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] * # "normal" atoms and or spaces
+(?:
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+|
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+) # "special" comment or quoted string
+[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] * # more "normal"
+)*
+<
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)
+/x
+ Alan Other
+ 0: Alan Other
+
+ 0: user@dom.ain
+ user\@dom.ain
+ 0: user@dom.ain
+ \"A. Other\" (a comment)
+ 0: "A. Other"
+ A. Other (a comment)
+ 0: Other
+ \"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"\@x400-re.lay
+ 0: "/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/"@x400-re.lay
+ A missing angle ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f
+
+/P[^*]TAIRE[^*]{1,6}?LL/
+ xxxxxxxxxxxPSTAIREISLLxxxxxxxxx
+ 0: PSTAIREISLL
+
+/P[^*]TAIRE[^*]{1,}?LL/
+ xxxxxxxxxxxPSTAIREISLLxxxxxxxxx
+ 0: PSTAIREISLL
+
+/(\.\d\d[1-9]?)\d+/
+ 1.230003938
+ 0: .230003938
+ 1: .23
+ 1.875000282
+ 0: .875000282
+ 1: .875
+ 1.235
+ 0: .235
+ 1: .23
+
+/(\.\d\d((?=0)|\d(?=\d)))/
+ 1.230003938
+ 0: .23
+ 1: .23
+ 2:
+ 1.875000282
+ 0: .875
+ 1: .875
+ 2: 5
+\= Expect no match
+ 1.235
+No match
+
+/\b(foo)\s+(\w+)/i
+ Food is on the foo table
+ 0: foo table
+ 1: foo
+ 2: table
+
+/foo(.*)bar/
+ The food is under the bar in the barn.
+ 0: food is under the bar in the bar
+ 1: d is under the bar in the
+
+/foo(.*?)bar/
+ The food is under the bar in the barn.
+ 0: food is under the bar
+ 1: d is under the
+
+/(.*)(\d*)/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers: 53147
+ 2:
+
+/(.*)(\d+)/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers: 5314
+ 2: 7
+
+/(.*?)(\d*)/
+ I have 2 numbers: 53147
+ 0:
+ 1:
+ 2:
+
+/(.*?)(\d+)/
+ I have 2 numbers: 53147
+ 0: I have 2
+ 1: I have
+ 2: 2
+
+/(.*)(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers: 5314
+ 2: 7
+
+/(.*?)(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers:
+ 2: 53147
+
+/(.*)\b(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers:
+ 2: 53147
+
+/(.*\D)(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers:
+ 2: 53147
+
+/^\D*(?!123)/
+ ABC123
+ 0: AB
+
+/^(\D*)(?=\d)(?!123)/
+ ABC445
+ 0: ABC
+ 1: ABC
+\= Expect no match
+ ABC123
+No match
+
+/^[W-]46]/
+ W46]789
+ 0: W46]
+ -46]789
+ 0: -46]
+\= Expect no match
+ Wall
+No match
+ Zebra
+No match
+ 42
+No match
+ [abcd]
+No match
+ ]abcd[
+No match
+
+/^[W-\]46]/
+ W46]789
+ 0: W
+ Wall
+ 0: W
+ Zebra
+ 0: Z
+ Xylophone
+ 0: X
+ 42
+ 0: 4
+ [abcd]
+ 0: [
+ ]abcd[
+ 0: ]
+ \\backslash
+ 0: \
+\= Expect no match
+ -46]789
+No match
+ well
+No match
+
+/\d\d\/\d\d\/\d\d\d\d/
+ 01/01/2000
+ 0: 01/01/2000
+
+/word (?:[a-zA-Z0-9]+ ){0,10}otherword/
+ word cat dog elephant mussel cow horse canary baboon snake shark otherword
+ 0: word cat dog elephant mussel cow horse canary baboon snake shark otherword
+\= Expect no match
+ word cat dog elephant mussel cow horse canary baboon snake shark
+No match
+
+/word (?:[a-zA-Z0-9]+ ){0,300}otherword/
+\= Expect no match
+ word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope
+No match
+
+/^(a){0,0}/
+ bcd
+ 0:
+ abc
+ 0:
+ aab
+ 0:
+
+/^(a){0,1}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: a
+ 1: a
+
+/^(a){0,2}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+
+/^(a){0,3}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+ aaa
+ 0: aaa
+ 1: a
+
+/^(a){0,}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+ aaa
+ 0: aaa
+ 1: a
+ aaaaaaaa
+ 0: aaaaaaaa
+ 1: a
+
+/^(a){1,1}/
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: a
+ 1: a
+\= Expect no match
+ bcd
+No match
+
+/^(a){1,2}/
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+\= Expect no match
+ bcd
+No match
+
+/^(a){1,3}/
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+ aaa
+ 0: aaa
+ 1: a
+\= Expect no match
+ bcd
+No match
+
+/^(a){1,}/
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+ aaa
+ 0: aaa
+ 1: a
+ aaaaaaaa
+ 0: aaaaaaaa
+ 1: a
+\= Expect no match
+ bcd
+No match
+
+/.*\.gif/
+ borfle\nbib.gif\nno
+ 0: bib.gif
+
+/.{0,}\.gif/
+ borfle\nbib.gif\nno
+ 0: bib.gif
+
+/.*\.gif/m
+ borfle\nbib.gif\nno
+ 0: bib.gif
+
+/.*\.gif/s
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif
+
+/.*\.gif/ms
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif
+
+/.*$/
+ borfle\nbib.gif\nno
+ 0: no
+
+/.*$/m
+ borfle\nbib.gif\nno
+ 0: borfle
+
+/.*$/s
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif\x0ano
+
+/.*$/ms
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif\x0ano
+
+/.*$/
+ borfle\nbib.gif\nno\n
+ 0: no
+
+/.*$/m
+ borfle\nbib.gif\nno\n
+ 0: borfle
+
+/.*$/s
+ borfle\nbib.gif\nno\n
+ 0: borfle\x0abib.gif\x0ano\x0a
+
+/.*$/ms
+ borfle\nbib.gif\nno\n
+ 0: borfle\x0abib.gif\x0ano\x0a
+
+/(.*X|^B)/
+ abcde\n1234Xyz
+ 0: 1234X
+ 1: 1234X
+ BarFoo
+ 0: B
+ 1: B
+\= Expect no match
+ abcde\nBar
+No match
+
+/(.*X|^B)/m
+ abcde\n1234Xyz
+ 0: 1234X
+ 1: 1234X
+ BarFoo
+ 0: B
+ 1: B
+ abcde\nBar
+ 0: B
+ 1: B
+
+/(.*X|^B)/s
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ 1: abcde\x0a1234X
+ BarFoo
+ 0: B
+ 1: B
+\= Expect no match
+ abcde\nBar
+No match
+
+/(.*X|^B)/ms
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ 1: abcde\x0a1234X
+ BarFoo
+ 0: B
+ 1: B
+ abcde\nBar
+ 0: B
+ 1: B
+
+/(?s)(.*X|^B)/
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ 1: abcde\x0a1234X
+ BarFoo
+ 0: B
+ 1: B
+\= Expect no match
+ abcde\nBar
+No match
+
+/(?s:.*X|^B)/
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ BarFoo
+ 0: B
+\= Expect no match
+ abcde\nBar
+No match
+
+/^.*B/
+\= Expect no match
+ abc\nB
+No match
+
+/(?s)^.*B/
+ abc\nB
+ 0: abc\x0aB
+
+/(?m)^.*B/
+ abc\nB
+ 0: B
+
+/(?ms)^.*B/
+ abc\nB
+ 0: abc\x0aB
+
+/(?ms)^B/
+ abc\nB
+ 0: B
+
+/(?s)B$/
+ B\n
+ 0: B
+
+/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/
+ 123456654321
+ 0: 123456654321
+
+/^\d\d\d\d\d\d\d\d\d\d\d\d/
+ 123456654321
+ 0: 123456654321
+
+/^[\d][\d][\d][\d][\d][\d][\d][\d][\d][\d][\d][\d]/
+ 123456654321
+ 0: 123456654321
+
+/^[abc]{12}/
+ abcabcabcabc
+ 0: abcabcabcabc
+
+/^[a-c]{12}/
+ abcabcabcabc
+ 0: abcabcabcabc
+
+/^(a|b|c){12}/
+ abcabcabcabc
+ 0: abcabcabcabc
+ 1: c
+
+/^[abcdefghijklmnopqrstuvwxy0123456789]/
+ n
+ 0: n
+\= Expect no match
+ z
+No match
+
+/abcde{0,0}/
+ abcd
+ 0: abcd
+\= Expect no match
+ abce
+No match
+
+/ab[cd]{0,0}e/
+ abe
+ 0: abe
+\= Expect no match
+ abcde
+No match
+
+/ab(c){0,0}d/
+ abd
+ 0: abd
+\= Expect no match
+ abcd
+No match
+
+/a(b*)/
+ a
+ 0: a
+ 1:
+ ab
+ 0: ab
+ 1: b
+ abbbb
+ 0: abbbb
+ 1: bbbb
+\= Expect no match
+ bbbbb
+No match
+
+/ab\d{0}e/
+ abe
+ 0: abe
+\= Expect no match
+ ab1e
+No match
+
+/"([^\\"]+|\\.)*"/
+ the \"quick\" brown fox
+ 0: "quick"
+ 1: quick
+ \"the \\\"quick\\\" brown fox\"
+ 0: "the \"quick\" brown fox"
+ 1: brown fox
+
+/]{0,})>]{0,})>([\d]{0,}\.)(.*)((
([\w\W\s\d][^<>]{0,})|[\s]{0,}))<\/a><\/TD> ]{0,})>([\w\W\s\d][^<>]{0,})<\/TD> ]{0,})>([\w\W\s\d][^<>]{0,})<\/TD><\/TR>/is
+ 43.Word Processor
(N-1286) Lega lstaff.com CA - Statewide
+ 0: 43.Word Processor
(N-1286) Lega lstaff.com CA - Statewide
+ 1: BGCOLOR='#DBE9E9'
+ 2: align=left valign=top
+ 3: 43.
+ 4: Word Processor
(N-1286)
+ 5:
+ 6:
+ 7:
+ 8: align=left valign=top
+ 9: Lega lstaff.com
+10: align=left valign=top
+11: CA - Statewide
+
+/a[^a]b/
+ acb
+ 0: acb
+ a\nb
+ 0: a\x0ab
+
+/a.b/
+ acb
+ 0: acb
+\= Expect no match
+ a\nb
+No match
+
+/a[^a]b/s
+ acb
+ 0: acb
+ a\nb
+ 0: a\x0ab
+
+/a.b/s
+ acb
+ 0: acb
+ a\nb
+ 0: a\x0ab
+
+/^(b+?|a){1,2}?c/
+ bac
+ 0: bac
+ 1: a
+ bbac
+ 0: bbac
+ 1: a
+ bbbac
+ 0: bbbac
+ 1: a
+ bbbbac
+ 0: bbbbac
+ 1: a
+ bbbbbac
+ 0: bbbbbac
+ 1: a
+
+/^(b+|a){1,2}?c/
+ bac
+ 0: bac
+ 1: a
+ bbac
+ 0: bbac
+ 1: a
+ bbbac
+ 0: bbbac
+ 1: a
+ bbbbac
+ 0: bbbbac
+ 1: a
+ bbbbbac
+ 0: bbbbbac
+ 1: a
+
+/(?!\A)x/m
+ a\bx\n
+ 0: x
+ a\nx\n
+ 0: x
+\= Expect no match
+ x\nb\n
+No match
+
+/(A|B)*?CD/
+ CD
+ 0: CD
+
+/(A|B)*CD/
+ CD
+ 0: CD
+
+/(AB)*?\1/
+ ABABAB
+ 0: ABAB
+ 1: AB
+
+/(AB)*\1/
+ ABABAB
+ 0: ABABAB
+ 1: AB
+
+/(?.*/)foo"
+ /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/and/foo
+ 0: /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/and/foo
+\= Expect no match
+ /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/it/you/see/
+No match
+
+/(?>(\.\d\d[1-9]?))\d+/
+ 1.230003938
+ 0: .230003938
+ 1: .23
+ 1.875000282
+ 0: .875000282
+ 1: .875
+\= Expect no match
+ 1.235
+No match
+
+/^((?>\w+)|(?>\s+))*$/
+ now is the time for all good men to come to the aid of the party
+ 0: now is the time for all good men to come to the aid of the party
+ 1: party
+\= Expect no match
+ this is not a line with only words and spaces!
+No match
+
+/(\d+)(\w)/
+ 12345a
+ 0: 12345a
+ 1: 12345
+ 2: a
+ 12345+
+ 0: 12345
+ 1: 1234
+ 2: 5
+
+/((?>\d+))(\w)/
+ 12345a
+ 0: 12345a
+ 1: 12345
+ 2: a
+\= Expect no match
+ 12345+
+No match
+
+/(?>a+)b/
+ aaab
+ 0: aaab
+
+/((?>a+)b)/
+ aaab
+ 0: aaab
+ 1: aaab
+
+/(?>(a+))b/
+ aaab
+ 0: aaab
+ 1: aaa
+
+/(?>b)+/
+ aaabbbccc
+ 0: bbb
+
+/(?>a+|b+|c+)*c/
+ aaabbbbccccd
+ 0: aaabbbbc
+
+/((?>[^()]+)|\([^()]*\))+/
+ ((abc(ade)ufh()()x
+ 0: abc(ade)ufh()()x
+ 1: x
+
+/\(((?>[^()]+)|\([^()]+\))+\)/
+ (abc)
+ 0: (abc)
+ 1: abc
+ (abc(def)xyz)
+ 0: (abc(def)xyz)
+ 1: xyz
+\= Expect no match
+ ((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+
+/a(?-i)b/i
+ ab
+ 0: ab
+ Ab
+ 0: Ab
+\= Expect no match
+ aB
+No match
+ AB
+No match
+
+/(a (?x)b c)d e/
+ a bcd e
+ 0: a bcd e
+ 1: a bc
+\= Expect no match
+ a b cd e
+No match
+ abcd e
+No match
+ a bcde
+No match
+
+/(a b(?x)c d (?-x)e f)/
+ a bcde f
+ 0: a bcde f
+ 1: a bcde f
+\= Expect no match
+ abcdef
+No match
+
+/(a(?i)b)c/
+ abc
+ 0: abc
+ 1: ab
+ aBc
+ 0: aBc
+ 1: aB
+\= Expect no match
+ abC
+No match
+ aBC
+No match
+ Abc
+No match
+ ABc
+No match
+ ABC
+No match
+ AbC
+No match
+
+/a(?i:b)c/
+ abc
+ 0: abc
+ aBc
+ 0: aBc
+\= Expect no match
+ ABC
+No match
+ abC
+No match
+ aBC
+No match
+
+/a(?i:b)*c/
+ aBc
+ 0: aBc
+ aBBc
+ 0: aBBc
+\= Expect no match
+ aBC
+No match
+ aBBC
+No match
+
+/a(?=b(?i)c)\w\wd/
+ abcd
+ 0: abcd
+ abCd
+ 0: abCd
+\= Expect no match
+ aBCd
+No match
+ abcD
+No match
+
+/(?s-i:more.*than).*million/i
+ more than million
+ 0: more than million
+ more than MILLION
+ 0: more than MILLION
+ more \n than Million
+ 0: more \x0a than Million
+\= Expect no match
+ MORE THAN MILLION
+No match
+ more \n than \n million
+No match
+
+/(?:(?s-i)more.*than).*million/i
+ more than million
+ 0: more than million
+ more than MILLION
+ 0: more than MILLION
+ more \n than Million
+ 0: more \x0a than Million
+\= Expect no match
+ MORE THAN MILLION
+No match
+ more \n than \n million
+No match
+
+/(?>a(?i)b+)+c/
+ abc
+ 0: abc
+ aBbc
+ 0: aBbc
+ aBBc
+ 0: aBBc
+\= Expect no match
+ Abc
+No match
+ abAb
+No match
+ abbC
+No match
+
+/(?=a(?i)b)\w\wc/
+ abc
+ 0: abc
+ aBc
+ 0: aBc
+\= Expect no match
+ Ab
+No match
+ abC
+No match
+ aBC
+No match
+
+/(?<=a(?i)b)(\w\w)c/
+ abxxc
+ 0: xxc
+ 1: xx
+ aBxxc
+ 0: xxc
+ 1: xx
+\= Expect no match
+ Abxxc
+No match
+ ABxxc
+No match
+ abxxC
+No match
+
+/(?:(a)|b)(?(1)A|B)/
+ aA
+ 0: aA
+ 1: a
+ bB
+ 0: bB
+\= Expect no match
+ aB
+No match
+ bA
+No match
+
+/^(a)?(?(1)a|b)+$/
+ aa
+ 0: aa
+ 1: a
+ b
+ 0: b
+ bb
+ 0: bb
+\= Expect no match
+ ab
+No match
+
+# Perl gets this next one wrong if the pattern ends with $; in that case it
+# fails to match "12".
+
+/^(?(?=abc)\w{3}:|\d\d)/
+ abc:
+ 0: abc:
+ 12
+ 0: 12
+ 123
+ 0: 12
+\= Expect no match
+ xyz
+No match
+
+/^(?(?!abc)\d\d|\w{3}:)$/
+ abc:
+ 0: abc:
+ 12
+ 0: 12
+\= Expect no match
+ 123
+No match
+ xyz
+No match
+
+/(?(?<=foo)bar|cat)/
+ foobar
+ 0: bar
+ cat
+ 0: cat
+ fcat
+ 0: cat
+ focat
+ 0: cat
+\= Expect no match
+ foocat
+No match
+
+/(?(?a*)*/
+ a
+ 0: a
+ aa
+ 0: aa
+ aaaa
+ 0: aaaa
+
+/(abc|)+/
+ abc
+ 0: abc
+ 1:
+ abcabc
+ 0: abcabc
+ 1:
+ abcabcabc
+ 0: abcabcabc
+ 1:
+ xyz
+ 0:
+ 1:
+
+/([a]*)*/
+ a
+ 0: a
+ 1:
+ aaaaa
+ 0: aaaaa
+ 1:
+
+/([ab]*)*/
+ a
+ 0: a
+ 1:
+ b
+ 0: b
+ 1:
+ ababab
+ 0: ababab
+ 1:
+ aaaabcde
+ 0: aaaab
+ 1:
+ bbbb
+ 0: bbbb
+ 1:
+
+/([^a]*)*/
+ b
+ 0: b
+ 1:
+ bbbb
+ 0: bbbb
+ 1:
+ aaa
+ 0:
+ 1:
+
+/([^ab]*)*/
+ cccc
+ 0: cccc
+ 1:
+ abab
+ 0:
+ 1:
+
+/([a]*?)*/
+ a
+ 0:
+ 1:
+ aaaa
+ 0:
+ 1:
+
+/([ab]*?)*/
+ a
+ 0:
+ 1:
+ b
+ 0:
+ 1:
+ abab
+ 0:
+ 1:
+ baba
+ 0:
+ 1:
+
+/([^a]*?)*/
+ b
+ 0:
+ 1:
+ bbbb
+ 0:
+ 1:
+ aaa
+ 0:
+ 1:
+
+/([^ab]*?)*/
+ c
+ 0:
+ 1:
+ cccc
+ 0:
+ 1:
+ baba
+ 0:
+ 1:
+
+/(?>a*)*/
+ a
+ 0: a
+ aaabcde
+ 0: aaa
+
+/((?>a*))*/
+ aaaaa
+ 0: aaaaa
+ 1:
+ aabbaa
+ 0: aa
+ 1:
+
+/((?>a*?))*/
+ aaaaa
+ 0:
+ 1:
+ aabbaa
+ 0:
+ 1:
+
+/(?(?=[^a-z]+[a-z]) \d{2}-[a-z]{3}-\d{2} | \d{2}-\d{2}-\d{2} ) /x
+ 12-sep-98
+ 0: 12-sep-98
+ 12-09-98
+ 0: 12-09-98
+\= Expect no match
+ sep-12-98
+No match
+
+/(?<=(foo))bar\1/
+ foobarfoo
+ 0: barfoo
+ 1: foo
+ foobarfootling
+ 0: barfoo
+ 1: foo
+\= Expect no match
+ foobar
+No match
+ barfoo
+No match
+
+/(?i:saturday|sunday)/
+ saturday
+ 0: saturday
+ sunday
+ 0: sunday
+ Saturday
+ 0: Saturday
+ Sunday
+ 0: Sunday
+ SATURDAY
+ 0: SATURDAY
+ SUNDAY
+ 0: SUNDAY
+ SunDay
+ 0: SunDay
+
+/(a(?i)bc|BB)x/
+ abcx
+ 0: abcx
+ 1: abc
+ aBCx
+ 0: aBCx
+ 1: aBC
+ bbx
+ 0: bbx
+ 1: bb
+ BBx
+ 0: BBx
+ 1: BB
+\= Expect no match
+ abcX
+No match
+ aBCX
+No match
+ bbX
+No match
+ BBX
+No match
+
+/^([ab](?i)[cd]|[ef])/
+ ac
+ 0: ac
+ 1: ac
+ aC
+ 0: aC
+ 1: aC
+ bD
+ 0: bD
+ 1: bD
+ elephant
+ 0: e
+ 1: e
+ Europe
+ 0: E
+ 1: E
+ frog
+ 0: f
+ 1: f
+ France
+ 0: F
+ 1: F
+\= Expect no match
+ Africa
+No match
+
+/^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)/
+ ab
+ 0: ab
+ 1: ab
+ aBd
+ 0: aBd
+ 1: aBd
+ xy
+ 0: xy
+ 1: xy
+ xY
+ 0: xY
+ 1: xY
+ zebra
+ 0: z
+ 1: z
+ Zambesi
+ 0: Z
+ 1: Z
+\= Expect no match
+ aCD
+No match
+ XY
+No match
+
+/(?<=foo\n)^bar/m
+ foo\nbar
+ 0: bar
+\= Expect no match
+ bar
+No match
+ baz\nbar
+No match
+
+/(?<=(?]&/
+ <&OUT
+ 0: <&
+
+/^(a\1?){4}$/
+ aaaaaaaaaa
+ 0: aaaaaaaaaa
+ 1: aaaa
+\= Expect no match
+ AB
+No match
+ aaaaaaaaa
+No match
+ aaaaaaaaaaa
+No match
+
+/^(a(?(1)\1)){4}$/
+ aaaaaaaaaa
+ 0: aaaaaaaaaa
+ 1: aaaa
+\= Expect no match
+ aaaaaaaaa
+No match
+ aaaaaaaaaaa
+No match
+
+/(?:(f)(o)(o)|(b)(a)(r))*/
+ foobar
+ 0: foobar
+ 1: f
+ 2: o
+ 3: o
+ 4: b
+ 5: a
+ 6: r
+
+/(?<=a)b/
+ ab
+ 0: b
+\= Expect no match
+ cb
+No match
+ b
+No match
+
+/(?
+ 2: abcd
+ xy:z:::abcd
+ 0: xy:z:::abcd
+ 1: xy:z:::
+ 2: abcd
+
+/^[^bcd]*(c+)/
+ aexycd
+ 0: aexyc
+ 1: c
+
+/(a*)b+/
+ caab
+ 0: aab
+ 1: aa
+
+/([\w:]+::)?(\w+)$/
+ abcd
+ 0: abcd
+ 1:
+ 2: abcd
+ xy:z:::abcd
+ 0: xy:z:::abcd
+ 1: xy:z:::
+ 2: abcd
+\= Expect no match
+ abcd:
+No match
+ abcd:
+No match
+
+/^[^bcd]*(c+)/
+ aexycd
+ 0: aexyc
+ 1: c
+
+/(>a+)ab/
+
+/(?>a+)b/
+ aaab
+ 0: aaab
+
+/([[:]+)/
+ a:[b]:
+ 0: :[
+ 1: :[
+
+/([[=]+)/
+ a=[b]=
+ 0: =[
+ 1: =[
+
+/([[.]+)/
+ a.[b].
+ 0: .[
+ 1: .[
+
+/((?>a+)b)/
+ aaab
+ 0: aaab
+ 1: aaab
+
+/(?>(a+))b/
+ aaab
+ 0: aaab
+ 1: aaa
+
+/((?>[^()]+)|\([^()]*\))+/
+ ((abc(ade)ufh()()x
+ 0: abc(ade)ufh()()x
+ 1: x
+
+/a\Z/
+\= Expect no match
+ aaab
+No match
+ a\nb\n
+No match
+
+/b\Z/
+ a\nb\n
+ 0: b
+
+/b\z/
+
+/b\Z/
+ a\nb
+ 0: b
+
+/b\z/
+ a\nb
+ 0: b
+
+/^(?>(?(1)\.|())[^\W_](?>[a-z0-9-]*[^\W_])?)+$/
+ a
+ 0: a
+ 1:
+ abc
+ 0: abc
+ 1:
+ a-b
+ 0: a-b
+ 1:
+ 0-9
+ 0: 0-9
+ 1:
+ a.b
+ 0: a.b
+ 1:
+ 5.6.7
+ 0: 5.6.7
+ 1:
+ the.quick.brown.fox
+ 0: the.quick.brown.fox
+ 1:
+ a100.b200.300c
+ 0: a100.b200.300c
+ 1:
+ 12-ab.1245
+ 0: 12-ab.1245
+ 1:
+\= Expect no match
+ \
+No match
+ .a
+No match
+ -a
+No match
+ a-
+No match
+ a.
+No match
+ a_b
+No match
+ a.-
+No match
+ a..
+No match
+ ab..bc
+No match
+ the.quick.brown.fox-
+No match
+ the.quick.brown.fox.
+No match
+ the.quick.brown.fox_
+No match
+ the.quick.brown.fox+
+No match
+
+/(?>.*)(?<=(abcd|wxyz))/
+ alphabetabcd
+ 0: alphabetabcd
+ 1: abcd
+ endingwxyz
+ 0: endingwxyz
+ 1: wxyz
+\= Expect no match
+ a rather long string that doesn't end with one of them
+No match
+
+/word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword/
+ word cat dog elephant mussel cow horse canary baboon snake shark otherword
+ 0: word cat dog elephant mussel cow horse canary baboon snake shark otherword
+\= Expect no match
+ word cat dog elephant mussel cow horse canary baboon snake shark
+No match
+
+/word (?>[a-zA-Z0-9]+ ){0,30}otherword/
+\= Expect no match
+ word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope
+No match
+
+/(?<=\d{3}(?!999))foo/
+ 999foo
+ 0: foo
+ 123999foo
+ 0: foo
+\= Expect no match
+ 123abcfoo
+No match
+
+/(?<=(?!...999)\d{3})foo/
+ 999foo
+ 0: foo
+ 123999foo
+ 0: foo
+\= Expect no match
+ 123abcfoo
+No match
+
+/(?<=\d{3}(?!999)...)foo/
+ 123abcfoo
+ 0: foo
+ 123456foo
+ 0: foo
+\= Expect no match
+ 123999foo
+No match
+
+/(?<=\d{3}...)(?
+ 2:
+ 3: abcd
+
+ 2:
+ 3: abcd
+ \s*)=(?>\s*) # find
+ 2:
+ 3: abcd
+ Z)+|A)*/
+ ZABCDEFG
+ 0: ZA
+ 1: A
+
+/((?>)+|A)*/
+ ZABCDEFG
+ 0:
+ 1:
+
+/^[\d-a]/
+ abcde
+ 0: a
+ -things
+ 0: -
+ 0digit
+ 0: 0
+\= Expect no match
+ bcdef
+No match
+
+/[\s]+/
+ > \x09\x0a\x0c\x0d\x0b<
+ 0: \x09\x0a\x0c\x0d\x0b
+
+/\s+/
+ > \x09\x0a\x0c\x0d\x0b<
+ 0: \x09\x0a\x0c\x0d\x0b
+
+/ab/x
+ ab
+ 0: ab
+
+/(?!\A)x/m
+ a\nxb\n
+ 0: x
+
+/(?!^)x/m
+\= Expect no match
+ a\nxb\n
+No match
+
+#/abc\Qabc\Eabc/
+# abcabcabc
+# 0: abcabcabc
+
+#/abc\Q(*+|\Eabc/
+# abc(*+|abc
+# 0: abc(*+|abc
+
+#/ abc\Q abc\Eabc/x
+# abc abcabc
+# 0: abc abcabc
+#\= Expect no match
+# abcabcabc
+#No match
+
+#/abc#comment
+# \Q#not comment
+# literal\E/x
+# abc#not comment\n literal
+# 0: abc#not comment\x0a literal
+
+#/abc#comment
+# \Q#not comment
+# literal/x
+# abc#not comment\n literal
+# 0: abc#not comment\x0a literal
+
+#/abc#comment
+# \Q#not comment
+# literal\E #more comment
+# /x
+# abc#not comment\n literal
+# 0: abc#not comment\x0a literal
+
+#/abc#comment
+# \Q#not comment
+# literal\E #more comment/x
+# abc#not comment\n literal
+# 0: abc#not comment\x0a literal
+
+#/\Qabc\$xyz\E/
+# abc\\\$xyz
+# 0: abc\$xyz
+
+#/\Qabc\E\$\Qxyz\E/
+# abc\$xyz
+# 0: abc$xyz
+
+/\Gabc/
+ abc
+ 0: abc
+\= Expect no match
+ xyzabc
+No match
+
+/a(?x: b c )d/
+ XabcdY
+ 0: abcd
+\= Expect no match
+ Xa b c d Y
+No match
+
+/((?x)x y z | a b c)/
+ XabcY
+ 0: abc
+ 1: abc
+ AxyzB
+ 0: xyz
+ 1: xyz
+
+/(?i)AB(?-i)C/
+ XabCY
+ 0: abC
+\= Expect no match
+ XabcY
+No match
+
+/((?i)AB(?-i)C|D)E/
+ abCE
+ 0: abCE
+ 1: abC
+ DE
+ 0: DE
+ 1: D
+\= Expect no match
+ abcE
+No match
+ abCe
+No match
+ dE
+No match
+ De
+No match
+
+/(.*)\d+\1/
+ abc123abc
+ 0: abc123abc
+ 1: abc
+ abc123bc
+ 0: bc123bc
+ 1: bc
+
+/(.*)\d+\1/s
+ abc123abc
+ 0: abc123abc
+ 1: abc
+ abc123bc
+ 0: bc123bc
+ 1: bc
+
+/((.*))\d+\1/
+ abc123abc
+ 0: abc123abc
+ 1: abc
+ 2: abc
+ abc123bc
+ 0: bc123bc
+ 1: bc
+ 2: bc
+
+# This tests for an IPv6 address in the form where it can have up to
+# eight components, one and only one of which is empty. This must be
+# an internal component.
+
+/^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ /ix
+ a123::a123
+ 0: a123::a123
+ 1:
+ a123:b342::abcd
+ 0: a123:b342::abcd
+ 1:
+ a123:b342::324e:abcd
+ 0: a123:b342::324e:abcd
+ 1:
+ a123:ddde:b342::324e:abcd
+ 0: a123:ddde:b342::324e:abcd
+ 1:
+ a123:ddde:b342::324e:dcba:abcd
+ 0: a123:ddde:b342::324e:dcba:abcd
+ 1:
+ a123:ddde:9999:b342::324e:dcba:abcd
+ 0: a123:ddde:9999:b342::324e:dcba:abcd
+ 1:
+\= Expect no match
+ 1:2:3:4:5:6:7:8
+No match
+ a123:bce:ddde:9999:b342::324e:dcba:abcd
+No match
+ a123::9999:b342::324e:dcba:abcd
+No match
+ abcde:2:3:4:5:6:7:8
+No match
+ ::1
+No match
+ abcd:fee0:123::
+No match
+ :1
+No match
+ 1:
+No match
+
+#/[z\Qa-d]\E]/
+# z
+# 0: z
+# a
+# 0: a
+# -
+# 0: -
+# d
+# 0: d
+# ]
+# 0: ]
+#\= Expect no match
+# b
+#No match
+
+#TODO: PCRE has an optimization to make this workable, .NET does not
+#/(a+)*b/
+#\= Expect no match
+# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+#No match
+
+# All these had to be updated because we understand unicode
+# and this looks like it's expecting single byte matches
+
+# .NET generates \xe4...not sure what's up, might just be different code pages
+/(?i)reg(?:ul(?:[aä]|ae)r|ex)/
+ REGular
+ 0: REGular
+ regulaer
+ 0: regulaer
+ Regex
+ 0: Regex
+ regulär
+ 0: regul\xc3\xa4r
+
+#/Åæåä[à-ÿÀ-ß]+/
+# Åæåäà
+# 0: \xc5\xe6\xe5\xe4\xe0
+# Åæåäÿ
+# 0: \xc5\xe6\xe5\xe4\xff
+# ÅæåäÀ
+# 0: \xc5\xe6\xe5\xe4\xc0
+# Åæåäß
+# 0: \xc5\xe6\xe5\xe4\xdf
+
+/(?<=Z)X./
+ \x84XAZXB
+ 0: XB
+
+/ab cd (?x) de fg/
+ ab cd defg
+ 0: ab cd defg
+
+/ab cd(?x) de fg/
+ ab cddefg
+ 0: ab cddefg
+\= Expect no match
+ abcddefg
+No match
+
+/(?
+ 2:
+ D
+ 0: D
+ 1:
+ 2:
+
+# this is really long with debug -- removing for now
+#/(a|)*\d/
+# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+# 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+# 1:
+#\= Expect no match
+# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+#No match
+
+/(?>a|)*\d/
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+\= Expect no match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+
+/(?:a|)*\d/
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+\= Expect no match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+
+/^(?s)(?>.*)(?
+ 2: a
+
+/(?>(a))b|(a)c/
+ ac
+ 0: ac
+ 1:
+ 2: a
+
+/(?=(a))ab|(a)c/
+ ac
+ 0: ac
+ 1:
+ 2: a
+
+/((?>(a))b|(a)c)/
+ ac
+ 0: ac
+ 1: ac
+ 2:
+ 3: a
+
+/(?=(?>(a))b|(a)c)(..)/
+ ac
+ 0: ac
+ 1:
+ 2: a
+ 3: ac
+
+/(?>(?>(a))b|(a)c)/
+ ac
+ 0: ac
+ 1:
+ 2: a
+
+/((?>(a+)b)+(aabab))/
+ aaaabaaabaabab
+ 0: aaaabaaabaabab
+ 1: aaaabaaabaabab
+ 2: aaa
+ 3: aabab
+
+/(?>a+|ab)+?c/
+\= Expect no match
+ aabc
+No match
+
+/(?>a+|ab)+c/
+\= Expect no match
+ aabc
+No match
+
+/(?:a+|ab)+c/
+ aabc
+ 0: aabc
+
+/^(?:a|ab)+c/
+ aaaabc
+ 0: aaaabc
+
+/(?=abc){0}xyz/
+ xyz
+ 0: xyz
+
+/(?=abc){1}xyz/
+\= Expect no match
+ xyz
+No match
+
+/(?=(a))?./
+ ab
+ 0: a
+ 1: a
+ bc
+ 0: b
+
+/(?=(a))??./
+ ab
+ 0: a
+ bc
+ 0: b
+
+/^(?!a){0}\w+/
+ aaaaa
+ 0: aaaaa
+
+/(?<=(abc))?xyz/
+ abcxyz
+ 0: xyz
+ 1: abc
+ pqrxyz
+ 0: xyz
+
+/^[g]+/
+ ggg<<>>
+ 0: ggg<<>>
+\= Expect no match
+ \\ga
+No match
+
+/^[ga]+/
+ gggagagaxyz
+ 0: gggagaga
+
+/[:a]xxx[b:]/
+ :xxx:
+ 0: :xxx:
+
+/(?<=a{2})b/i
+ xaabc
+ 0: b
+\= Expect no match
+ xabc
+No match
+
+/(?
+# 4:
+# 5: c
+# 6: d
+# 7: Y
+
+#/^X(?7)(a)(?|(b|(?|(r)|(t))(s))|(q))(c)(d)(Y)/
+# XYabcdY
+# 0: XYabcdY
+# 1: a
+# 2: b
+# 3:
+# 4:
+# 5: c
+# 6: d
+# 7: Y
+
+/(?'abc'\w+):\k{2}/
+ a:aaxyz
+ 0: a:aa
+ 1: a
+ ab:ababxyz
+ 0: ab:abab
+ 1: ab
+\= Expect no match
+ a:axyz
+No match
+ ab:abxyz
+No match
+
+/^(?a)? (?(ab)b|c) (?(ab)d|e)/x
+ abd
+ 0: abd
+ 1: a
+ ce
+ 0: ce
+
+# .NET has more consistent grouping numbers with these dupe groups for the two options
+/(?:a(? (?')|(?")) |b(? (?')|(?")) ) (?(quote)[a-z]+|[0-9]+)/x,dupnames
+ a\"aaaaa
+ 0: a"aaaaa
+ 1: "
+ 2:
+ 3: "
+ b\"aaaaa
+ 0: b"aaaaa
+ 1: "
+ 2:
+ 3: "
+\= Expect no match
+ b\"11111
+No match
+
+#/(?P(?P0)(?P>L1)|(?P>L2))/
+# 0
+# 0: 0
+# 1: 0
+# 00
+# 0: 00
+# 1: 00
+# 2: 0
+# 0000
+# 0: 0000
+# 1: 0000
+# 2: 0
+
+#/(?P(?P0)|(?P>L2)(?P>L1))/
+# 0
+# 0: 0
+# 1: 0
+# 2: 0
+# 00
+# 0: 0
+# 1: 0
+# 2: 0
+# 0000
+# 0: 0
+# 1: 0
+# 2: 0
+
+# Check the use of names for failure
+
+# Check opening parens in comment when seeking forward reference.
+
+#/(?P(?P=abn)xxx|)+/
+# xxx
+# 0:
+# 1:
+
+#Posses
+/^(a)?(\w)/
+ aaaaX
+ 0: aa
+ 1: a
+ 2: a
+ YZ
+ 0: Y
+ 1:
+ 2: Y
+
+#Posses
+/^(?:a)?(\w)/
+ aaaaX
+ 0: aa
+ 1: a
+ YZ
+ 0: Y
+ 1: Y
+
+/\A.*?(a|bc)/
+ ba
+ 0: ba
+ 1: a
+
+/\A.*?(?:a|bc|d)/
+ ba
+ 0: ba
+
+# --------------------------
+
+/(another)?(\1?)test/
+ hello world test
+ 0: test
+ 1:
+ 2:
+
+/(another)?(\1+)test/
+\= Expect no match
+ hello world test
+No match
+
+/((?:a?)*)*c/
+ aac
+ 0: aac
+ 1:
+
+/((?>a?)*)*c/
+ aac
+ 0: aac
+ 1:
+
+/(?>.*?a)(?<=ba)/
+ aba
+ 0: ba
+
+/(?:.*?a)(?<=ba)/
+ aba
+ 0: aba
+
+/(?>.*?a)b/s
+ aab
+ 0: ab
+
+/(?>.*?a)b/
+ aab
+ 0: ab
+
+/(?>^a)b/s
+\= Expect no match
+ aab
+No match
+
+/(?>.*?)(?<=(abcd)|(wxyz))/
+ alphabetabcd
+ 0:
+ 1: abcd
+ endingwxyz
+ 0:
+ 1:
+ 2: wxyz
+
+/(?>.*)(?<=(abcd)|(wxyz))/
+ alphabetabcd
+ 0: alphabetabcd
+ 1: abcd
+ endingwxyz
+ 0: endingwxyz
+ 1:
+ 2: wxyz
+
+"(?>.*)foo"
+\= Expect no match
+ abcdfooxyz
+No match
+
+"(?>.*?)foo"
+ abcdfooxyz
+ 0: foo
+
+# Tests that try to figure out how Perl works. My hypothesis is that the first
+# verb that is backtracked onto is the one that acts. This seems to be the case
+# almost all the time, but there is one exception that is perhaps a bug.
+
+/a(?=bc).|abd/
+ abd
+ 0: abd
+ abc
+ 0: ab
+
+/a(?>bc)d|abd/
+ abceabd
+ 0: abd
+
+# These tests were formerly in test 2, but changes in PCRE and Perl have
+# made them compatible.
+
+/^(a)?(?(1)a|b)+$/
+\= Expect no match
+ a
+No match
+
+# ----
+
+/^\d*\w{4}/
+ 1234
+ 0: 1234
+\= Expect no match
+ 123
+No match
+
+/^[^b]*\w{4}/
+ aaaa
+ 0: aaaa
+\= Expect no match
+ aaa
+No match
+
+/^[^b]*\w{4}/i
+ aaaa
+ 0: aaaa
+\= Expect no match
+ aaa
+No match
+
+/^a*\w{4}/
+ aaaa
+ 0: aaaa
+\= Expect no match
+ aaa
+No match
+
+/^a*\w{4}/i
+ aaaa
+ 0: aaaa
+\= Expect no match
+ aaa
+No match
+
+/(?:(?foo)|(?bar))\k/dupnames
+ foofoo
+ 0: foofoo
+ 1: foo
+ barbar
+ 0: barbar
+ 1: bar
+
+# A notable difference between PCRE and .NET. According to
+# the PCRE docs:
+# If you make a subroutine call to a non-unique named
+# subpattern, the one that corresponds to the first
+# occurrence of the name is used. In the absence of
+# duplicate numbers (see the previous section) this is
+# the one with the lowest number.
+# .NET takes the most recently captured number according to MSDN:
+# A backreference refers to the most recent definition of
+# a group (the definition most immediately to the left,
+# when matching left to right). When a group makes multiple
+# captures, a backreference refers to the most recent capture.
+
+#/(?A)(?:(?foo)|(?bar))\k/dupnames
+# AfooA
+# 0: AfooA
+# 1: A
+# 2: foo
+# AbarA
+# 0: AbarA
+# 1: A
+# 2:
+# 3: bar
+#\= Expect no match
+# Afoofoo
+#No match
+# Abarbar
+#No match
+
+/^(\d+)\s+IN\s+SOA\s+(\S+)\s+(\S+)\s*\(\s*$/
+ 1 IN SOA non-sp1 non-sp2(
+ 0: 1 IN SOA non-sp1 non-sp2(
+ 1: 1
+ 2: non-sp1
+ 3: non-sp2
+
+# TODO: .NET's group number ordering here in the second example is a bit odd
+/^ (?:(?A)|(?'B'B)(?A)) (?(A)x) (?(B)y)$/x,dupnames
+ Ax
+ 0: Ax
+ 1: A
+ BAxy
+ 0: BAxy
+ 1: A
+ 2: B
+
+/ ^ a + b $ /x
+ aaaab
+ 0: aaaab
+
+/ ^ a + #comment
+ b $ /x
+ aaaab
+ 0: aaaab
+
+/ ^ a + #comment
+ #comment
+ b $ /x
+ aaaab
+ 0: aaaab
+
+/ ^ (?> a + ) b $ /x
+ aaaab
+ 0: aaaab
+
+/ ^ ( a + ) + \w $ /x
+ aaaab
+ 0: aaaab
+ 1: aaaa
+
+/(?:x|(?:(xx|yy)+|x|x|x|x|x)|a|a|a)bc/
+\= Expect no match
+ acb
+No match
+
+#Posses
+#/\A(?:[^\"]+|\"(?:[^\"]*|\"\")*\")+/
+# NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED
+# 0: NON QUOTED "QUOT""ED" AFTER
+
+#Posses
+#/\A(?:[^\"]+|\"(?:[^\"]+|\"\")*\")+/
+# NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED
+# 0: NON QUOTED "QUOT""ED" AFTER
+
+#Posses
+#/\A(?:[^\"]+|\"(?:[^\"]+|\"\")+\")+/
+# NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED
+# 0: NON QUOTED "QUOT""ED" AFTER
+
+#Posses
+#/\A([^\"1]+|[\"2]([^\"3]*|[\"4][\"5])*[\"6])+/
+# NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED
+# 0: NON QUOTED "QUOT""ED" AFTER
+# 1: AFTER
+# 2:
+
+/^\w+(?>\s*)(?<=\w)/
+ test test
+ 0: tes
+
+#/(?Pa)?(?Pb)?(?()c|d)*l/
+# acl
+# 0: acl
+# 1: a
+# bdl
+# 0: bdl
+# 1:
+# 2: b
+# adl
+# 0: dl
+# bcl
+# 0: l
+
+/\sabc/
+ \x0babc
+ 0: \x0babc
+
+#/[\Qa]\E]+/
+# aa]]
+# 0: aa]]
+
+#/[\Q]a\E]+/
+# aa]]
+# 0: aa]]
+
+/A((((((((a))))))))\8B/
+ AaaB
+ 0: AaaB
+ 1: a
+ 2: a
+ 3: a
+ 4: a
+ 5: a
+ 6: a
+ 7: a
+ 8: a
+
+/A(((((((((a)))))))))\9B/
+ AaaB
+ 0: AaaB
+ 1: a
+ 2: a
+ 3: a
+ 4: a
+ 5: a
+ 6: a
+ 7: a
+ 8: a
+ 9: a
+
+/(|ab)*?d/
+ abd
+ 0: abd
+ 1: ab
+ xyd
+ 0: d
+
+/(\2|a)(\1)/
+ aaa
+ 0: aa
+ 1: a
+ 2: a
+
+/(\2)(\1)/
+
+"Z*(|d*){216}"
+
+/((((((((((((x))))))))))))\12/
+ xx
+ 0: xx
+ 1: x
+ 2: x
+ 3: x
+ 4: x
+ 5: x
+ 6: x
+ 7: x
+ 8: x
+ 9: x
+10: x
+11: x
+12: x
+
+#"(?|(\k'Pm')|(?'Pm'))"
+# abcd
+# 0:
+# 1:
+
+#/(?|(aaa)|(b))\g{1}/
+# aaaaaa
+# 0: aaaaaa
+# 1: aaa
+# bb
+# 0: bb
+# 1: b
+
+#/(?|(aaa)|(b))(?1)/
+# aaaaaa
+# 0: aaaaaa
+# 1: aaa
+# baaa
+# 0: baaa
+# 1: b
+#\= Expect no match
+# bb
+#No match
+
+#/(?|(aaa)|(b))/
+# xaaa
+# 0: aaa
+# 1: aaa
+# xbc
+# 0: b
+# 1: b
+
+#/(?|(?'a'aaa)|(?'a'b))\k'a'/
+# aaaaaa
+# 0: aaaaaa
+# 1: aaa
+# bb
+# 0: bb
+# 1: b
+
+#/(?|(?'a'aaa)|(?'a'b))(?'a'cccc)\k'a'/dupnames
+# aaaccccaaa
+# 0: aaaccccaaa
+# 1: aaa
+# 2: cccc
+# bccccb
+# 0: bccccb
+# 1: b
+# 2: cccc
+
+# End of testinput1
diff --git a/vendor/github.com/fatih/color/README.md b/vendor/github.com/fatih/color/README.md
index be82827c..d135bfe0 100644
--- a/vendor/github.com/fatih/color/README.md
+++ b/vendor/github.com/fatih/color/README.md
@@ -9,7 +9,7 @@ suits you.
## Install
-```bash
+```
go get github.com/fatih/color
```
@@ -30,6 +30,18 @@ color.Magenta("And many others ..")
```
+### RGB colors
+
+If your terminal supports 24-bit colors, you can use RGB color codes.
+
+```go
+color.RGB(255, 128, 0).Println("foreground orange")
+color.RGB(230, 42, 42).Println("foreground red")
+
+color.BgRGB(255, 128, 0).Println("background orange")
+color.BgRGB(230, 42, 42).Println("background red")
+```
+
### Mix and reuse colors
```go
@@ -49,6 +61,11 @@ boldRed.Println("This will print text in bold red.")
whiteBackground := red.Add(color.BgWhite)
whiteBackground.Println("Red text with white background.")
+
+// Mix with RGB color codes
+color.RGB(255, 128, 0).AddBgRGB(0, 0, 0).Println("orange with black background")
+
+color.BgRGB(255, 128, 0).AddRGB(255, 255, 255).Println("orange background with white foreground")
```
### Use your own output (io.Writer)
@@ -161,10 +178,6 @@ c.Println("This prints again cyan...")
To output color in GitHub Actions (or other CI systems that support ANSI colors), make sure to set `color.NoColor = false` so that it bypasses the check for non-tty output streams.
-## Todo
-
-* Save/Return previous values
-* Evaluate fmt.Formatter interface
## Credits
diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go
index 889f9e77..ee39b408 100644
--- a/vendor/github.com/fatih/color/color.go
+++ b/vendor/github.com/fatih/color/color.go
@@ -65,6 +65,29 @@ const (
CrossedOut
)
+const (
+ ResetBold Attribute = iota + 22
+ ResetItalic
+ ResetUnderline
+ ResetBlinking
+ _
+ ResetReversed
+ ResetConcealed
+ ResetCrossedOut
+)
+
+var mapResetAttributes map[Attribute]Attribute = map[Attribute]Attribute{
+ Bold: ResetBold,
+ Faint: ResetBold,
+ Italic: ResetItalic,
+ Underline: ResetUnderline,
+ BlinkSlow: ResetBlinking,
+ BlinkRapid: ResetBlinking,
+ ReverseVideo: ResetReversed,
+ Concealed: ResetConcealed,
+ CrossedOut: ResetCrossedOut,
+}
+
// Foreground text colors
const (
FgBlack Attribute = iota + 30
@@ -75,6 +98,9 @@ const (
FgMagenta
FgCyan
FgWhite
+
+ // used internally for 256 and 24-bit coloring
+ foreground
)
// Foreground Hi-Intensity text colors
@@ -99,6 +125,9 @@ const (
BgMagenta
BgCyan
BgWhite
+
+ // used internally for 256 and 24-bit coloring
+ background
)
// Background Hi-Intensity text colors
@@ -127,6 +156,30 @@ func New(value ...Attribute) *Color {
return c
}
+// RGB returns a new foreground color in 24-bit RGB.
+func RGB(r, g, b int) *Color {
+ return New(foreground, 2, Attribute(r), Attribute(g), Attribute(b))
+}
+
+// BgRGB returns a new background color in 24-bit RGB.
+func BgRGB(r, g, b int) *Color {
+ return New(background, 2, Attribute(r), Attribute(g), Attribute(b))
+}
+
+// AddRGB is used to chain foreground RGB SGR parameters. Use as many as parameters to combine
+// and create custom color objects. Example: .Add(34, 0, 12).Add(255, 128, 0).
+func (c *Color) AddRGB(r, g, b int) *Color {
+ c.params = append(c.params, foreground, 2, Attribute(r), Attribute(g), Attribute(b))
+ return c
+}
+
+// AddRGB is used to chain background RGB SGR parameters. Use as many as parameters to combine
+// and create custom color objects. Example: .Add(34, 0, 12).Add(255, 128, 0).
+func (c *Color) AddBgRGB(r, g, b int) *Color {
+ c.params = append(c.params, background, 2, Attribute(r), Attribute(g), Attribute(b))
+ return c
+}
+
// Set sets the given parameters immediately. It will change the color of
// output with the given SGR parameters until color.Unset() is called.
func Set(p ...Attribute) *Color {
@@ -246,10 +299,7 @@ func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
// On Windows, users should wrap w with colorable.NewColorable() if w is of
// type *os.File.
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
- c.SetWriter(w)
- defer c.UnsetWriter(w)
-
- return fmt.Fprintln(w, a...)
+ return fmt.Fprintln(w, c.wrap(sprintln(a...)))
}
// Println formats using the default formats for its operands and writes to
@@ -258,10 +308,7 @@ func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
// encountered. This is the standard fmt.Print() method wrapped with the given
// color.
func (c *Color) Println(a ...interface{}) (n int, err error) {
- c.Set()
- defer c.unset()
-
- return fmt.Fprintln(Output, a...)
+ return fmt.Fprintln(Output, c.wrap(sprintln(a...)))
}
// Sprint is just like Print, but returns a string instead of printing it.
@@ -271,7 +318,7 @@ func (c *Color) Sprint(a ...interface{}) string {
// Sprintln is just like Println, but returns a string instead of printing it.
func (c *Color) Sprintln(a ...interface{}) string {
- return c.wrap(fmt.Sprintln(a...))
+ return c.wrap(sprintln(a...)) + "\n"
}
// Sprintf is just like Printf, but returns a string instead of printing it.
@@ -353,7 +400,7 @@ func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
// string. Windows users should use this in conjunction with color.Output.
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
return func(a ...interface{}) string {
- return c.wrap(fmt.Sprintln(a...))
+ return c.wrap(sprintln(a...)) + "\n"
}
}
@@ -383,7 +430,18 @@ func (c *Color) format() string {
}
func (c *Color) unformat() string {
- return fmt.Sprintf("%s[%dm", escape, Reset)
+ //return fmt.Sprintf("%s[%dm", escape, Reset)
+ //for each element in sequence let's use the specific reset escape, or the generic one if not found
+ format := make([]string, len(c.params))
+ for i, v := range c.params {
+ format[i] = strconv.Itoa(int(Reset))
+ ra, ok := mapResetAttributes[v]
+ if ok {
+ format[i] = strconv.Itoa(int(ra))
+ }
+ }
+
+ return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";"))
}
// DisableColor disables the color output. Useful to not change any existing
@@ -411,6 +469,12 @@ func (c *Color) isNoColorSet() bool {
// Equals returns a boolean value indicating whether two colors are equal.
func (c *Color) Equals(c2 *Color) bool {
+ if c == nil && c2 == nil {
+ return true
+ }
+ if c == nil || c2 == nil {
+ return false
+ }
if len(c.params) != len(c2.params) {
return false
}
@@ -614,3 +678,8 @@ func HiCyanString(format string, a ...interface{}) string { return colorString(f
func HiWhiteString(format string, a ...interface{}) string {
return colorString(format, FgHiWhite, a...)
}
+
+// sprintln is a helper function to format a string with fmt.Sprintln and trim the trailing newline.
+func sprintln(a ...interface{}) string {
+ return strings.TrimSuffix(fmt.Sprintln(a...), "\n")
+}
diff --git a/vendor/github.com/gorilla/css/LICENSE b/vendor/github.com/gorilla/css/LICENSE
new file mode 100644
index 00000000..ee0d53ce
--- /dev/null
+++ b/vendor/github.com/gorilla/css/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2023 The Gorilla Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/vendor/github.com/gorilla/css/scanner/doc.go b/vendor/github.com/gorilla/css/scanner/doc.go
new file mode 100644
index 00000000..f19850e1
--- /dev/null
+++ b/vendor/github.com/gorilla/css/scanner/doc.go
@@ -0,0 +1,33 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package gorilla/css/scanner generates tokens for a CSS3 input.
+
+It follows the CSS3 specification located at:
+
+ http://www.w3.org/TR/css3-syntax/
+
+To use it, create a new scanner for a given CSS string and call Next() until
+the token returned has type TokenEOF or TokenError:
+
+ s := scanner.New(myCSS)
+ for {
+ token := s.Next()
+ if token.Type == scanner.TokenEOF || token.Type == scanner.TokenError {
+ break
+ }
+ // Do something with the token...
+ }
+
+Following the CSS3 specification, an error can only occur when the scanner
+finds an unclosed quote or unclosed comment. In these cases the text becomes
+"untokenizable". Everything else is tokenizable and it is up to a parser
+to make sense of the token stream (or ignore nonsensical token sequences).
+
+Note: the scanner doesn't perform lexical analysis or, in other words, it
+doesn't care about the token context. It is intended to be used by a
+lexer or parser.
+*/
+package scanner
diff --git a/vendor/github.com/gorilla/css/scanner/scanner.go b/vendor/github.com/gorilla/css/scanner/scanner.go
new file mode 100644
index 00000000..25a7c657
--- /dev/null
+++ b/vendor/github.com/gorilla/css/scanner/scanner.go
@@ -0,0 +1,360 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package scanner
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+// tokenType identifies the type of lexical tokens.
+type tokenType int
+
+// String returns a string representation of the token type.
+func (t tokenType) String() string {
+ return tokenNames[t]
+}
+
+// Token represents a token and the corresponding string.
+type Token struct {
+ Type tokenType
+ Value string
+ Line int
+ Column int
+}
+
+// String returns a string representation of the token.
+func (t *Token) String() string {
+ if len(t.Value) > 10 {
+ return fmt.Sprintf("%s (line: %d, column: %d): %.10q...",
+ t.Type, t.Line, t.Column, t.Value)
+ }
+ return fmt.Sprintf("%s (line: %d, column: %d): %q",
+ t.Type, t.Line, t.Column, t.Value)
+}
+
+// All tokens -----------------------------------------------------------------
+
+// The complete list of tokens in CSS3.
+const (
+ // Scanner flags.
+ TokenError tokenType = iota
+ TokenEOF
+ // From now on, only tokens from the CSS specification.
+ TokenIdent
+ TokenAtKeyword
+ TokenString
+ TokenHash
+ TokenNumber
+ TokenPercentage
+ TokenDimension
+ TokenURI
+ TokenUnicodeRange
+ TokenCDO
+ TokenCDC
+ TokenS
+ TokenComment
+ TokenFunction
+ TokenIncludes
+ TokenDashMatch
+ TokenPrefixMatch
+ TokenSuffixMatch
+ TokenSubstringMatch
+ TokenChar
+ TokenBOM
+)
+
+// tokenNames maps tokenType's to their names. Used for conversion to string.
+var tokenNames = map[tokenType]string{
+ TokenError: "error",
+ TokenEOF: "EOF",
+ TokenIdent: "IDENT",
+ TokenAtKeyword: "ATKEYWORD",
+ TokenString: "STRING",
+ TokenHash: "HASH",
+ TokenNumber: "NUMBER",
+ TokenPercentage: "PERCENTAGE",
+ TokenDimension: "DIMENSION",
+ TokenURI: "URI",
+ TokenUnicodeRange: "UNICODE-RANGE",
+ TokenCDO: "CDO",
+ TokenCDC: "CDC",
+ TokenS: "S",
+ TokenComment: "COMMENT",
+ TokenFunction: "FUNCTION",
+ TokenIncludes: "INCLUDES",
+ TokenDashMatch: "DASHMATCH",
+ TokenPrefixMatch: "PREFIXMATCH",
+ TokenSuffixMatch: "SUFFIXMATCH",
+ TokenSubstringMatch: "SUBSTRINGMATCH",
+ TokenChar: "CHAR",
+ TokenBOM: "BOM",
+}
+
+// Macros and productions -----------------------------------------------------
+// http://www.w3.org/TR/css3-syntax/#tokenization
+
+var macroRegexp = regexp.MustCompile(`\{[a-z]+\}`)
+
+// macros maps macro names to patterns to be expanded.
+var macros = map[string]string{
+ // must be escaped: `\.+*?()|[]{}^$`
+ "ident": `-?{nmstart}{nmchar}*`,
+ "name": `{nmchar}+`,
+ "nmstart": `[a-zA-Z_]|{nonascii}|{escape}`,
+ "nonascii": "[\u0080-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]",
+ "unicode": `\\[0-9a-fA-F]{1,6}{wc}?`,
+ "escape": "{unicode}|\\\\[\u0020-\u007E\u0080-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]",
+ "nmchar": `[a-zA-Z0-9_-]|{nonascii}|{escape}`,
+ "num": `[0-9]*\.[0-9]+|[0-9]+`,
+ "string": `"(?:{stringchar}|')*"|'(?:{stringchar}|")*'`,
+ "stringchar": `{urlchar}|[ ]|\\{nl}`,
+ "nl": `[\n\r\f]|\r\n`,
+ "w": `{wc}*`,
+ "wc": `[\t\n\f\r ]`,
+
+ // urlchar should accept [(ascii characters minus those that need escaping)|{nonascii}|{escape}]
+ // ASCII characters range = `[\u0020-\u007e]`
+ // Skip space \u0020 = `[\u0021-\u007e]`
+ // Skip quotation mark \0022 = `[\u0021\u0023-\u007e]`
+ // Skip apostrophe \u0027 = `[\u0021\u0023-\u0026\u0028-\u007e]`
+ // Skip reverse solidus \u005c = `[\u0021\u0023-\u0026\u0028-\u005b\u005d\u007e]`
+ // Finally, the left square bracket (\u005b) and right (\u005d) needs escaping themselves
+ "urlchar": "[\u0021\u0023-\u0026\u0028-\\\u005b\\\u005d-\u007E]|{nonascii}|{escape}",
+}
+
+// productions maps the list of tokens to patterns to be expanded.
+var productions = map[tokenType]string{
+ // Unused regexps (matched using other methods) are commented out.
+ TokenIdent: `{ident}`,
+ TokenAtKeyword: `@{ident}`,
+ TokenString: `{string}`,
+ TokenHash: `#{name}`,
+ TokenNumber: `{num}`,
+ TokenPercentage: `{num}%`,
+ TokenDimension: `{num}{ident}`,
+ TokenURI: `url\({w}(?:{string}|{urlchar}*?){w}\)`,
+ TokenUnicodeRange: `U\+[0-9A-F\?]{1,6}(?:-[0-9A-F]{1,6})?`,
+ //TokenCDO: ``,
+ TokenS: `{wc}+`,
+ TokenComment: `/\*[^\*]*[\*]+(?:[^/][^\*]*[\*]+)*/`,
+ TokenFunction: `{ident}\(`,
+ //TokenIncludes: `~=`,
+ //TokenDashMatch: `\|=`,
+ //TokenPrefixMatch: `\^=`,
+ //TokenSuffixMatch: `\$=`,
+ //TokenSubstringMatch: `\*=`,
+ //TokenChar: `[^"']`,
+ //TokenBOM: "\uFEFF",
+}
+
+// matchers maps the list of tokens to compiled regular expressions.
+//
+// The map is filled on init() using the macros and productions defined in
+// the CSS specification.
+var matchers = map[tokenType]*regexp.Regexp{}
+
+// matchOrder is the order to test regexps when first-char shortcuts
+// can't be used.
+var matchOrder = []tokenType{
+ TokenURI,
+ TokenFunction,
+ TokenUnicodeRange,
+ TokenIdent,
+ TokenDimension,
+ TokenPercentage,
+ TokenNumber,
+ TokenCDC,
+}
+
+func init() {
+ // replace macros and compile regexps for productions.
+ replaceMacro := func(s string) string {
+ return "(?:" + macros[s[1:len(s)-1]] + ")"
+ }
+ for t, s := range productions {
+ for macroRegexp.MatchString(s) {
+ s = macroRegexp.ReplaceAllStringFunc(s, replaceMacro)
+ }
+ matchers[t] = regexp.MustCompile("^(?:" + s + ")")
+ }
+}
+
+// Scanner --------------------------------------------------------------------
+
+// New returns a new CSS scanner for the given input.
+func New(input string) *Scanner {
+ // Normalize newlines.
+ // https://www.w3.org/TR/css-syntax-3/#input-preprocessing
+ input = strings.Replace(input, "\r\n", "\n", -1)
+ input = strings.Replace(input, "\r", "\n", -1)
+ input = strings.Replace(input, "\f", "\n", -1)
+ input = strings.Replace(input, "\u0000", "\ufffd", -1)
+ return &Scanner{
+ input: input,
+ row: 1,
+ col: 1,
+ }
+}
+
+// Scanner scans an input and emits tokens following the CSS3 specification.
+type Scanner struct {
+ input string
+ pos int
+ row int
+ col int
+ err *Token
+}
+
+// Next returns the next token from the input.
+//
+// At the end of the input the token type is TokenEOF.
+//
+// If the input can't be tokenized the token type is TokenError. This occurs
+// in case of unclosed quotation marks or comments.
+func (s *Scanner) Next() *Token {
+ if s.err != nil {
+ return s.err
+ }
+ if s.pos >= len(s.input) {
+ s.err = &Token{TokenEOF, "", s.row, s.col}
+ return s.err
+ }
+ if s.pos == 0 {
+ // Test BOM only once, at the beginning of the file.
+ if strings.HasPrefix(s.input, "\uFEFF") {
+ return s.emitSimple(TokenBOM, "\uFEFF")
+ }
+ }
+ // There's a lot we can guess based on the first byte so we'll take a
+ // shortcut before testing multiple regexps.
+ input := s.input[s.pos:]
+ switch input[0] {
+ case '\t', '\n', ' ':
+ // Whitespace.
+ return s.emitToken(TokenS, matchers[TokenS].FindString(input))
+ case '.':
+ // Dot is too common to not have a quick check.
+ // We'll test if this is a Char; if it is followed by a number it is a
+ // dimension/percentage/number, and this will be matched later.
+ if len(input) > 1 && !unicode.IsDigit(rune(input[1])) {
+ return s.emitSimple(TokenChar, ".")
+ }
+ case '#':
+ // Another common one: Hash or Char.
+ if match := matchers[TokenHash].FindString(input); match != "" {
+ return s.emitToken(TokenHash, match)
+ }
+ return s.emitSimple(TokenChar, "#")
+ case '@':
+ // Another common one: AtKeyword or Char.
+ if match := matchers[TokenAtKeyword].FindString(input); match != "" {
+ return s.emitSimple(TokenAtKeyword, match)
+ }
+ return s.emitSimple(TokenChar, "@")
+ case ':', ',', ';', '%', '&', '+', '=', '>', '(', ')', '[', ']', '{', '}':
+ // More common chars.
+ return s.emitSimple(TokenChar, string(input[0]))
+ case '"', '\'':
+ // String or error.
+ match := matchers[TokenString].FindString(input)
+ if match != "" {
+ return s.emitToken(TokenString, match)
+ }
+
+ s.err = &Token{TokenError, "unclosed quotation mark", s.row, s.col}
+ return s.err
+ case '/':
+ // Comment, error or Char.
+ if len(input) > 1 && input[1] == '*' {
+ match := matchers[TokenComment].FindString(input)
+ if match != "" {
+ return s.emitToken(TokenComment, match)
+ } else {
+ s.err = &Token{TokenError, "unclosed comment", s.row, s.col}
+ return s.err
+ }
+ }
+ return s.emitSimple(TokenChar, "/")
+ case '~':
+ // Includes or Char.
+ return s.emitPrefixOrChar(TokenIncludes, "~=")
+ case '|':
+ // DashMatch or Char.
+ return s.emitPrefixOrChar(TokenDashMatch, "|=")
+ case '^':
+ // PrefixMatch or Char.
+ return s.emitPrefixOrChar(TokenPrefixMatch, "^=")
+ case '$':
+ // SuffixMatch or Char.
+ return s.emitPrefixOrChar(TokenSuffixMatch, "$=")
+ case '*':
+ // SubstringMatch or Char.
+ return s.emitPrefixOrChar(TokenSubstringMatch, "*=")
+ case '<':
+ // CDO or Char.
+ return s.emitPrefixOrChar(TokenCDO, " which includes the use of that to permit
+// conditionals as per https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/ms537512(v=vs.85)?redirectedfrom=MSDN
+//
+// What is not permitted are CDATA XML comments, as the x/net/html package we depend
+// on does not handle this fully and we are not choosing to take on that work:
+// https://pkg.go.dev/golang.org/x/net/html#Tokenizer.AllowCDATA . If the x/net/html
+// package changes this then these will be considered, otherwise if you AllowComments
+// but provide a CDATA comment, then as per the documentation in x/net/html this will
+// be treated as a plain HTML comment.
+func (p *Policy) AllowComments() {
+ p.allowComments = true
+}
+
+// AllowNoAttrs says that attributes on element are optional.
+//
+// The attribute policy is only added to the core policy when OnElements(...)
+// are called.
+func (p *Policy) AllowNoAttrs() *attrPolicyBuilder {
+
+ p.init()
+
+ abp := attrPolicyBuilder{
+ p: p,
+ allowEmpty: true,
+ }
+ return &abp
+}
+
+// AllowNoAttrs says that attributes on element are optional.
+//
+// The attribute policy is only added to the core policy when OnElements(...)
+// are called.
+func (abp *attrPolicyBuilder) AllowNoAttrs() *attrPolicyBuilder {
+
+ abp.allowEmpty = true
+
+ return abp
+}
+
+// Matching allows a regular expression to be applied to a nascent attribute
+// policy, and returns the attribute policy.
+func (abp *attrPolicyBuilder) Matching(regex *regexp.Regexp) *attrPolicyBuilder {
+
+ abp.regexp = regex
+
+ return abp
+}
+
+// OnElements will bind an attribute policy to a given range of HTML elements
+// and return the updated policy
+func (abp *attrPolicyBuilder) OnElements(elements ...string) *Policy {
+
+ for _, element := range elements {
+ element = strings.ToLower(element)
+
+ for _, attr := range abp.attrNames {
+
+ if _, ok := abp.p.elsAndAttrs[element]; !ok {
+ abp.p.elsAndAttrs[element] = make(map[string][]attrPolicy)
+ }
+
+ ap := attrPolicy{}
+ if abp.regexp != nil {
+ ap.regexp = abp.regexp
+ }
+
+ abp.p.elsAndAttrs[element][attr] = append(abp.p.elsAndAttrs[element][attr], ap)
+ }
+
+ if abp.allowEmpty {
+ abp.p.setOfElementsAllowedWithoutAttrs[element] = struct{}{}
+
+ if _, ok := abp.p.elsAndAttrs[element]; !ok {
+ abp.p.elsAndAttrs[element] = make(map[string][]attrPolicy)
+ }
+ }
+ }
+
+ return abp.p
+}
+
+// OnElementsMatching will bind an attribute policy to all elements matching a given regex
+// and return the updated policy
+func (abp *attrPolicyBuilder) OnElementsMatching(regex *regexp.Regexp) *Policy {
+ for _, attr := range abp.attrNames {
+ if _, ok := abp.p.elsMatchingAndAttrs[regex]; !ok {
+ abp.p.elsMatchingAndAttrs[regex] = make(map[string][]attrPolicy)
+ }
+ ap := attrPolicy{}
+ if abp.regexp != nil {
+ ap.regexp = abp.regexp
+ }
+ abp.p.elsMatchingAndAttrs[regex][attr] = append(abp.p.elsMatchingAndAttrs[regex][attr], ap)
+ }
+
+ if abp.allowEmpty {
+ abp.p.setOfElementsMatchingAllowedWithoutAttrs = append(abp.p.setOfElementsMatchingAllowedWithoutAttrs, regex)
+ if _, ok := abp.p.elsMatchingAndAttrs[regex]; !ok {
+ abp.p.elsMatchingAndAttrs[regex] = make(map[string][]attrPolicy)
+ }
+ }
+
+ return abp.p
+}
+
+// Globally will bind an attribute policy to all HTML elements and return the
+// updated policy
+func (abp *attrPolicyBuilder) Globally() *Policy {
+
+ for _, attr := range abp.attrNames {
+ if _, ok := abp.p.globalAttrs[attr]; !ok {
+ abp.p.globalAttrs[attr] = []attrPolicy{}
+ }
+
+ ap := attrPolicy{}
+ if abp.regexp != nil {
+ ap.regexp = abp.regexp
+ }
+
+ abp.p.globalAttrs[attr] = append(abp.p.globalAttrs[attr], ap)
+ }
+
+ return abp.p
+}
+
+// AllowStyles takes a range of CSS property names and returns a
+// style policy builder that allows you to specify the pattern and scope of
+// the allowed property.
+//
+// The style policy is only added to the core policy when either Globally()
+// or OnElements(...) are called.
+func (p *Policy) AllowStyles(propertyNames ...string) *stylePolicyBuilder {
+
+ p.init()
+
+ abp := stylePolicyBuilder{
+ p: p,
+ }
+
+ for _, propertyName := range propertyNames {
+ abp.propertyNames = append(abp.propertyNames, strings.ToLower(propertyName))
+ }
+
+ return &abp
+}
+
+// Matching allows a regular expression to be applied to a nascent style
+// policy, and returns the style policy.
+func (spb *stylePolicyBuilder) Matching(regex *regexp.Regexp) *stylePolicyBuilder {
+
+ spb.regexp = regex
+
+ return spb
+}
+
+// MatchingEnum allows a list of allowed values to be applied to a nascent style
+// policy, and returns the style policy.
+func (spb *stylePolicyBuilder) MatchingEnum(enum ...string) *stylePolicyBuilder {
+
+ spb.enum = enum
+
+ return spb
+}
+
+// MatchingHandler allows a handler to be applied to a nascent style
+// policy, and returns the style policy.
+func (spb *stylePolicyBuilder) MatchingHandler(handler func(string) bool) *stylePolicyBuilder {
+
+ spb.handler = handler
+
+ return spb
+}
+
+// OnElements will bind a style policy to a given range of HTML elements
+// and return the updated policy
+func (spb *stylePolicyBuilder) OnElements(elements ...string) *Policy {
+
+ for _, element := range elements {
+ element = strings.ToLower(element)
+
+ for _, attr := range spb.propertyNames {
+
+ if _, ok := spb.p.elsAndStyles[element]; !ok {
+ spb.p.elsAndStyles[element] = make(map[string][]stylePolicy)
+ }
+
+ sp := stylePolicy{}
+ if spb.handler != nil {
+ sp.handler = spb.handler
+ } else if len(spb.enum) > 0 {
+ sp.enum = spb.enum
+ } else if spb.regexp != nil {
+ sp.regexp = spb.regexp
+ } else {
+ sp.handler = css.GetDefaultHandler(attr)
+ }
+ spb.p.elsAndStyles[element][attr] = append(spb.p.elsAndStyles[element][attr], sp)
+ }
+ }
+
+ return spb.p
+}
+
+// OnElementsMatching will bind a style policy to any HTML elements matching the pattern
+// and return the updated policy
+func (spb *stylePolicyBuilder) OnElementsMatching(regex *regexp.Regexp) *Policy {
+
+ for _, attr := range spb.propertyNames {
+
+ if _, ok := spb.p.elsMatchingAndStyles[regex]; !ok {
+ spb.p.elsMatchingAndStyles[regex] = make(map[string][]stylePolicy)
+ }
+
+ sp := stylePolicy{}
+ if spb.handler != nil {
+ sp.handler = spb.handler
+ } else if len(spb.enum) > 0 {
+ sp.enum = spb.enum
+ } else if spb.regexp != nil {
+ sp.regexp = spb.regexp
+ } else {
+ sp.handler = css.GetDefaultHandler(attr)
+ }
+ spb.p.elsMatchingAndStyles[regex][attr] = append(spb.p.elsMatchingAndStyles[regex][attr], sp)
+ }
+
+ return spb.p
+}
+
+// Globally will bind a style policy to all HTML elements and return the
+// updated policy
+func (spb *stylePolicyBuilder) Globally() *Policy {
+
+ for _, attr := range spb.propertyNames {
+ if _, ok := spb.p.globalStyles[attr]; !ok {
+ spb.p.globalStyles[attr] = []stylePolicy{}
+ }
+
+ // Use only one strategy for validating styles, fallback to default
+ sp := stylePolicy{}
+ if spb.handler != nil {
+ sp.handler = spb.handler
+ } else if len(spb.enum) > 0 {
+ sp.enum = spb.enum
+ } else if spb.regexp != nil {
+ sp.regexp = spb.regexp
+ } else {
+ sp.handler = css.GetDefaultHandler(attr)
+ }
+ spb.p.globalStyles[attr] = append(spb.p.globalStyles[attr], sp)
+ }
+
+ return spb.p
+}
+
+// AllowElements will append HTML elements to the allowlist without applying an
+// attribute policy to those elements (the elements are permitted
+// sans-attributes)
+func (p *Policy) AllowElements(names ...string) *Policy {
+ p.init()
+
+ for _, element := range names {
+ element = strings.ToLower(element)
+
+ if _, ok := p.elsAndAttrs[element]; !ok {
+ p.elsAndAttrs[element] = make(map[string][]attrPolicy)
+ }
+ }
+
+ return p
+}
+
+// AllowElementsMatching will append HTML elements to the allowlist if they
+// match a regexp.
+func (p *Policy) AllowElementsMatching(regex *regexp.Regexp) *Policy {
+ p.init()
+ if _, ok := p.elsMatchingAndAttrs[regex]; !ok {
+ p.elsMatchingAndAttrs[regex] = make(map[string][]attrPolicy)
+ }
+ return p
+}
+
+// AllowURLSchemesMatching will append URL schemes to the allowlist if they
+// match a regexp.
+func (p *Policy) AllowURLSchemesMatching(r *regexp.Regexp) *Policy {
+ p.allowURLSchemeRegexps = append(p.allowURLSchemeRegexps, r)
+ return p
+}
+
+// RewriteSrc will rewrite the src attribute of a resource downloading tag
+// (e.g.
, tag.
+func (p *Policy) addDefaultSkipElementContent() {
+ p.init()
+
+ p.setOfElementsToSkipContent["frame"] = struct{}{}
+ p.setOfElementsToSkipContent["frameset"] = struct{}{}
+ p.setOfElementsToSkipContent["iframe"] = struct{}{}
+ p.setOfElementsToSkipContent["noembed"] = struct{}{}
+ p.setOfElementsToSkipContent["noframes"] = struct{}{}
+ p.setOfElementsToSkipContent["noscript"] = struct{}{}
+ p.setOfElementsToSkipContent["nostyle"] = struct{}{}
+ p.setOfElementsToSkipContent["object"] = struct{}{}
+ p.setOfElementsToSkipContent["script"] = struct{}{}
+ p.setOfElementsToSkipContent["style"] = struct{}{}
+ p.setOfElementsToSkipContent["title"] = struct{}{}
+}
diff --git a/vendor/github.com/microcosm-cc/bluemonday/sanitize.go b/vendor/github.com/microcosm-cc/bluemonday/sanitize.go
new file mode 100644
index 00000000..47c31f7d
--- /dev/null
+++ b/vendor/github.com/microcosm-cc/bluemonday/sanitize.go
@@ -0,0 +1,1096 @@
+// Copyright (c) 2014, David Kitchen
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// * Neither the name of the organisation (Microcosm) nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package bluemonday
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/url"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "golang.org/x/net/html"
+
+ "github.com/aymerick/douceur/parser"
+)
+
+var (
+ dataAttribute = regexp.MustCompile("^data-.+")
+ dataAttributeXMLPrefix = regexp.MustCompile("^xml.+")
+ dataAttributeInvalidChars = regexp.MustCompile("[A-Z;]+")
+ cssUnicodeChar = regexp.MustCompile(`\\[0-9a-f]{1,6} ?`)
+ dataURIbase64Prefix = regexp.MustCompile(`^data:[^,]*;base64,`)
+)
+
+// Sanitize takes a string that contains a HTML fragment or document and applies
+// the given policy allowlist.
+//
+// It returns a HTML string that has been sanitized by the policy or an empty
+// string if an error has occurred (most likely as a consequence of extremely
+// malformed input)
+func (p *Policy) Sanitize(s string) string {
+ if strings.TrimSpace(s) == "" {
+ return s
+ }
+
+ return p.sanitizeWithBuff(strings.NewReader(s)).String()
+}
+
+// SanitizeBytes takes a []byte that contains a HTML fragment or document and applies
+// the given policy allowlist.
+//
+// It returns a []byte containing the HTML that has been sanitized by the policy
+// or an empty []byte if an error has occurred (most likely as a consequence of
+// extremely malformed input)
+func (p *Policy) SanitizeBytes(b []byte) []byte {
+ if len(bytes.TrimSpace(b)) == 0 {
+ return b
+ }
+
+ return p.sanitizeWithBuff(bytes.NewReader(b)).Bytes()
+}
+
+// SanitizeReader takes an io.Reader that contains a HTML fragment or document
+// and applies the given policy allowlist.
+//
+// It returns a bytes.Buffer containing the HTML that has been sanitized by the
+// policy. Errors during sanitization will merely return an empty result.
+func (p *Policy) SanitizeReader(r io.Reader) *bytes.Buffer {
+ return p.sanitizeWithBuff(r)
+}
+
+// SanitizeReaderToWriter takes an io.Reader that contains a HTML fragment or document
+// and applies the given policy allowlist and writes to the provided writer returning
+// an error if there is one.
+func (p *Policy) SanitizeReaderToWriter(r io.Reader, w io.Writer) error {
+ return p.sanitize(r, w)
+}
+
+// Query represents a single part of the query string, a query param
+type Query struct {
+ Key string
+ Value string
+ HasValue bool
+}
+
+func parseQuery(query string) (values []Query, err error) {
+ // This is essentially a copy of parseQuery from
+ // https://golang.org/src/net/url/url.go but adjusted to build our values
+ // based on our type, which we need to preserve the ordering of the query
+ // string
+ for query != "" {
+ key := query
+ if i := strings.IndexAny(key, "&;"); i >= 0 {
+ key, query = key[:i], key[i+1:]
+ } else {
+ query = ""
+ }
+ if key == "" {
+ continue
+ }
+ value := ""
+ hasValue := false
+ if i := strings.Index(key, "="); i >= 0 {
+ key, value = key[:i], key[i+1:]
+ hasValue = true
+ }
+ key, err1 := url.QueryUnescape(key)
+ if err1 != nil {
+ if err == nil {
+ err = err1
+ }
+ continue
+ }
+ value, err1 = url.QueryUnescape(value)
+ if err1 != nil {
+ if err == nil {
+ err = err1
+ }
+ continue
+ }
+ values = append(values, Query{
+ Key: key,
+ Value: value,
+ HasValue: hasValue,
+ })
+ }
+ return values, err
+}
+
+func encodeQueries(queries []Query) string {
+ var buff bytes.Buffer
+ for i, query := range queries {
+ buff.WriteString(url.QueryEscape(query.Key))
+ if query.HasValue {
+ buff.WriteString("=")
+ buff.WriteString(url.QueryEscape(query.Value))
+ }
+ if i < len(queries)-1 {
+ buff.WriteString("&")
+ }
+ }
+ return buff.String()
+}
+
+func sanitizedURL(val string) (string, error) {
+ u, err := url.Parse(val)
+ if err != nil {
+ return "", err
+ }
+
+ // we use parseQuery but not u.Query to keep the order not change because
+ // url.Values is a map which has a random order.
+ queryValues, err := parseQuery(u.RawQuery)
+ if err != nil {
+ return "", err
+ }
+ // sanitize the url query params
+ for i, query := range queryValues {
+ queryValues[i].Key = html.EscapeString(query.Key)
+ }
+ u.RawQuery = encodeQueries(queryValues)
+ // u.String() will also sanitize host/scheme/user/pass
+ return u.String(), nil
+}
+
+// Performs the actual sanitization process.
+func (p *Policy) sanitizeWithBuff(r io.Reader) *bytes.Buffer {
+ var buff bytes.Buffer
+ if err := p.sanitize(r, &buff); err != nil {
+ return &bytes.Buffer{}
+ }
+ return &buff
+}
+
+type asStringWriter struct {
+ io.Writer
+}
+
+func (a *asStringWriter) WriteString(s string) (int, error) {
+ return a.Write([]byte(s))
+}
+
+func (p *Policy) sanitize(r io.Reader, w io.Writer) error {
+ // It is possible that the developer has created the policy via:
+ // p := bluemonday.Policy{}
+ // rather than:
+ // p := bluemonday.NewPolicy()
+ // If this is the case, and if they haven't yet triggered an action that
+ // would initialize the maps, then we need to do that.
+ p.init()
+
+ buff, ok := w.(stringWriterWriter)
+ if !ok {
+ buff = &asStringWriter{w}
+ }
+
+ var (
+ skipElementContent bool
+ skippingElementsCount int64
+ skipClosingTag bool
+ closingTagToSkipStack []string
+ mostRecentlyStartedToken string
+ )
+
+ tokenizer := html.NewTokenizer(r)
+ for {
+ if tokenizer.Next() == html.ErrorToken {
+ err := tokenizer.Err()
+ if err == io.EOF {
+ // End of input means end of processing
+ return nil
+ }
+
+ // Raw tokenizer error
+ return err
+ }
+
+ token := tokenizer.Token()
+ switch token.Type {
+ case html.DoctypeToken:
+
+ // DocType is not handled as there is no safe parsing mechanism
+ // provided by golang.org/x/net/html for the content, and this can
+ // be misused to insert HTML tags that are not then sanitized
+ //
+ // One might wish to recursively sanitize here using the same policy
+ // but I will need to do some further testing before considering
+ // this.
+
+ case html.CommentToken:
+
+ // Comments are ignored by default
+ if p.allowComments {
+ // But if allowed then write the comment out as-is
+ buff.WriteString(token.String())
+ }
+
+ case html.StartTagToken:
+
+ mostRecentlyStartedToken = normaliseElementName(token.Data)
+
+ switch normaliseElementName(token.Data) {
+ case `script`:
+ if !p.allowUnsafe {
+ continue
+ }
+ case `style`:
+ if !p.allowUnsafe {
+ continue
+ }
+ }
+
+ aps, ok := p.elsAndAttrs[token.Data]
+ if !ok {
+ aa, matched := p.matchRegex(token.Data)
+ if !matched {
+ if _, ok := p.setOfElementsToSkipContent[token.Data]; ok {
+ skipElementContent = true
+ skippingElementsCount++
+ }
+ if p.addSpaces {
+ if _, err := buff.WriteString(" "); err != nil {
+ return err
+ }
+ }
+ break
+ }
+ aps = aa
+ }
+ if len(token.Attr) != 0 {
+ token.Attr = p.sanitizeAttrs(token.Data, token.Attr, aps)
+ }
+
+ if len(token.Attr) == 0 {
+ if !p.allowNoAttrs(token.Data) {
+ skipClosingTag = true
+ closingTagToSkipStack = append(closingTagToSkipStack, token.Data)
+ if p.addSpaces {
+ if _, err := buff.WriteString(" "); err != nil {
+ return err
+ }
+ }
+ break
+ }
+ }
+
+ if !skipElementContent {
+ if _, err := buff.WriteString(token.String()); err != nil {
+ return err
+ }
+ }
+
+ case html.EndTagToken:
+
+ if mostRecentlyStartedToken == normaliseElementName(token.Data) {
+ mostRecentlyStartedToken = ""
+ }
+
+ switch normaliseElementName(token.Data) {
+ case `script`:
+ if !p.allowUnsafe {
+ continue
+ }
+ case `style`:
+ if !p.allowUnsafe {
+ continue
+ }
+ }
+
+ if skipClosingTag && closingTagToSkipStack[len(closingTagToSkipStack)-1] == token.Data {
+ closingTagToSkipStack = closingTagToSkipStack[:len(closingTagToSkipStack)-1]
+ if len(closingTagToSkipStack) == 0 {
+ skipClosingTag = false
+ }
+ if p.addSpaces {
+ if _, err := buff.WriteString(" "); err != nil {
+ return err
+ }
+ }
+ break
+ }
+ if _, ok := p.elsAndAttrs[token.Data]; !ok {
+ match := false
+ for regex := range p.elsMatchingAndAttrs {
+ if regex.MatchString(token.Data) {
+ skipElementContent = false
+ match = true
+ break
+ }
+ }
+ if _, ok := p.setOfElementsToSkipContent[token.Data]; ok && !match {
+ skippingElementsCount--
+ if skippingElementsCount == 0 {
+ skipElementContent = false
+ }
+ }
+ if !match {
+ if p.addSpaces {
+ if _, err := buff.WriteString(" "); err != nil {
+ return err
+ }
+ }
+ break
+ }
+ }
+
+ if !skipElementContent {
+ if _, err := buff.WriteString(token.String()); err != nil {
+ return err
+ }
+ }
+
+ case html.SelfClosingTagToken:
+
+ switch normaliseElementName(token.Data) {
+ case `script`:
+ if !p.allowUnsafe {
+ continue
+ }
+ case `style`:
+ if !p.allowUnsafe {
+ continue
+ }
+ }
+
+ aps, ok := p.elsAndAttrs[token.Data]
+ if !ok {
+ aa, matched := p.matchRegex(token.Data)
+ if !matched {
+ if p.addSpaces && !matched {
+ if _, err := buff.WriteString(" "); err != nil {
+ return err
+ }
+ }
+ break
+ }
+ aps = aa
+ }
+
+ if len(token.Attr) != 0 {
+ token.Attr = p.sanitizeAttrs(token.Data, token.Attr, aps)
+ }
+
+ if len(token.Attr) == 0 && !p.allowNoAttrs(token.Data) {
+ if p.addSpaces {
+ if _, err := buff.WriteString(" "); err != nil {
+ return err
+ }
+ }
+ break
+ }
+ if !skipElementContent {
+ if _, err := buff.WriteString(token.String()); err != nil {
+ return err
+ }
+ }
+
+ case html.TextToken:
+
+ if !skipElementContent {
+ switch mostRecentlyStartedToken {
+ case `script`:
+ // not encouraged, but if a policy allows JavaScript we
+ // should not HTML escape it as that would break the output
+ //
+ // requires p.AllowUnsafe()
+ if p.allowUnsafe {
+ if _, err := buff.WriteString(token.Data); err != nil {
+ return err
+ }
+ }
+ case "style":
+ // not encouraged, but if a policy allows CSS styles we
+ // should not HTML escape it as that would break the output
+ //
+ // requires p.AllowUnsafe()
+ if p.allowUnsafe {
+ if _, err := buff.WriteString(token.Data); err != nil {
+ return err
+ }
+ }
+ default:
+ // HTML escape the text
+ if _, err := buff.WriteString(token.String()); err != nil {
+ return err
+ }
+ }
+ }
+
+ default:
+ // A token that didn't exist in the html package when we wrote this
+ return fmt.Errorf("unknown token: %v", token)
+ }
+ }
+}
+
+// sanitizeAttrs takes a set of element attribute policies and the global
+// attribute policies and applies them to the []html.Attribute returning a set
+// of html.Attributes that match the policies
+func (p *Policy) sanitizeAttrs(
+ elementName string,
+ attrs []html.Attribute,
+ aps map[string][]attrPolicy,
+) []html.Attribute {
+
+ if len(attrs) == 0 {
+ return attrs
+ }
+
+ hasStylePolicies := false
+ sps, elementHasStylePolicies := p.elsAndStyles[elementName]
+ if len(p.globalStyles) > 0 || (elementHasStylePolicies && len(sps) > 0) {
+ hasStylePolicies = true
+ }
+ // no specific element policy found, look for a pattern match
+ if !hasStylePolicies {
+ for k, v := range p.elsMatchingAndStyles {
+ if k.MatchString(elementName) {
+ if len(v) > 0 {
+ hasStylePolicies = true
+ break
+ }
+ }
+ }
+ }
+
+ // Builds a new attribute slice based on the whether the attribute has been
+ // allowed explicitly or globally.
+ cleanAttrs := []html.Attribute{}
+attrsLoop:
+ for _, htmlAttr := range attrs {
+ if p.allowDataAttributes {
+ // If we see a data attribute, let it through.
+ if isDataAttribute(htmlAttr.Key) {
+ cleanAttrs = append(cleanAttrs, htmlAttr)
+ continue
+ }
+ }
+ // Is this a "style" attribute, and if so, do we need to sanitize it?
+ if htmlAttr.Key == "style" && hasStylePolicies {
+ htmlAttr = p.sanitizeStyles(htmlAttr, elementName)
+ if htmlAttr.Val == "" {
+ // We've sanitized away any and all styles; don't bother to
+ // output the style attribute (even if it's allowed)
+ continue
+ } else {
+ cleanAttrs = append(cleanAttrs, htmlAttr)
+ continue
+ }
+ }
+
+ // Is there an element specific attribute policy that applies?
+ if apl, ok := aps[htmlAttr.Key]; ok {
+ for _, ap := range apl {
+ if ap.regexp != nil {
+ if ap.regexp.MatchString(htmlAttr.Val) {
+ cleanAttrs = append(cleanAttrs, htmlAttr)
+ continue attrsLoop
+ }
+ } else {
+ cleanAttrs = append(cleanAttrs, htmlAttr)
+ continue attrsLoop
+ }
+ }
+ }
+
+ // Is there a global attribute policy that applies?
+ if apl, ok := p.globalAttrs[htmlAttr.Key]; ok {
+ for _, ap := range apl {
+ if ap.regexp != nil {
+ if ap.regexp.MatchString(htmlAttr.Val) {
+ cleanAttrs = append(cleanAttrs, htmlAttr)
+ continue attrsLoop
+ }
+ } else {
+ cleanAttrs = append(cleanAttrs, htmlAttr)
+ continue attrsLoop
+ }
+ }
+ }
+ }
+
+ if len(cleanAttrs) == 0 {
+ // If nothing was allowed, let's get out of here
+ return cleanAttrs
+ }
+ // cleanAttrs now contains the attributes that are permitted
+
+ if linkable(elementName) {
+ if p.requireParseableURLs {
+ // Ensure URLs are parseable:
+ // - a.href
+ // - area.href
+ // - link.href
+ // - blockquote.cite
+ // - q.cite
+ // - img.src
+ // - script.src
+ tmpAttrs := []html.Attribute{}
+ for _, htmlAttr := range cleanAttrs {
+ switch elementName {
+ case "a", "area", "base", "link":
+ if htmlAttr.Key == "href" {
+ if u, ok := p.validURL(htmlAttr.Val); ok {
+ htmlAttr.Val = u
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ }
+ break
+ }
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ case "blockquote", "del", "ins", "q":
+ if htmlAttr.Key == "cite" {
+ if u, ok := p.validURL(htmlAttr.Val); ok {
+ htmlAttr.Val = u
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ }
+ break
+ }
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ case "audio", "embed", "iframe", "img", "script", "source", "track", "video":
+ if htmlAttr.Key == "src" {
+ if u, ok := p.validURL(htmlAttr.Val); ok {
+ if p.srcRewriter != nil {
+ parsedURL, err := url.Parse(u)
+ if err != nil {
+ fmt.Println(err)
+ }
+ p.srcRewriter(parsedURL)
+ u = parsedURL.String()
+ }
+ htmlAttr.Val = u
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ }
+ break
+ }
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ default:
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ }
+ }
+ cleanAttrs = tmpAttrs
+ }
+
+ if (p.requireNoFollow ||
+ p.requireNoFollowFullyQualifiedLinks ||
+ p.requireNoReferrer ||
+ p.requireNoReferrerFullyQualifiedLinks ||
+ p.addTargetBlankToFullyQualifiedLinks) &&
+ len(cleanAttrs) > 0 {
+
+ // Add rel="nofollow" if a "href" exists
+ switch elementName {
+ case "a", "area", "base", "link":
+ var hrefFound bool
+ var externalLink bool
+ for _, htmlAttr := range cleanAttrs {
+ if htmlAttr.Key == "href" {
+ hrefFound = true
+
+ u, err := url.Parse(htmlAttr.Val)
+ if err != nil {
+ continue
+ }
+ if u.Host != "" {
+ externalLink = true
+ }
+
+ continue
+ }
+ }
+
+ if hrefFound {
+ var (
+ noFollowFound bool
+ noReferrerFound bool
+ targetBlankFound bool
+ )
+
+ addNoFollow := (p.requireNoFollow ||
+ externalLink && p.requireNoFollowFullyQualifiedLinks)
+
+ addNoReferrer := (p.requireNoReferrer ||
+ externalLink && p.requireNoReferrerFullyQualifiedLinks)
+
+ addTargetBlank := (externalLink &&
+ p.addTargetBlankToFullyQualifiedLinks)
+
+ tmpAttrs := []html.Attribute{}
+ for _, htmlAttr := range cleanAttrs {
+
+ var appended bool
+ if htmlAttr.Key == "rel" && (addNoFollow || addNoReferrer) {
+
+ if addNoFollow && !strings.Contains(htmlAttr.Val, "nofollow") {
+ htmlAttr.Val += " nofollow"
+ }
+ if addNoReferrer && !strings.Contains(htmlAttr.Val, "noreferrer") {
+ htmlAttr.Val += " noreferrer"
+ }
+ noFollowFound = addNoFollow
+ noReferrerFound = addNoReferrer
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ appended = true
+ }
+
+ if elementName == "a" && htmlAttr.Key == "target" {
+ if htmlAttr.Val == "_blank" {
+ targetBlankFound = true
+ }
+ if addTargetBlank && !targetBlankFound {
+ htmlAttr.Val = "_blank"
+ targetBlankFound = true
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ appended = true
+ }
+ }
+
+ if !appended {
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ }
+ }
+ if noFollowFound || noReferrerFound || targetBlankFound {
+ cleanAttrs = tmpAttrs
+ }
+
+ if (addNoFollow && !noFollowFound) || (addNoReferrer && !noReferrerFound) {
+ rel := html.Attribute{}
+ rel.Key = "rel"
+ if addNoFollow {
+ rel.Val = "nofollow"
+ }
+ if addNoReferrer {
+ if rel.Val != "" {
+ rel.Val += " "
+ }
+ rel.Val += "noreferrer"
+ }
+ cleanAttrs = append(cleanAttrs, rel)
+ }
+
+ if elementName == "a" && addTargetBlank && !targetBlankFound {
+ rel := html.Attribute{}
+ rel.Key = "target"
+ rel.Val = "_blank"
+ targetBlankFound = true
+ cleanAttrs = append(cleanAttrs, rel)
+ }
+
+ if targetBlankFound {
+ // target="_blank" has a security risk that allows the
+ // opened window/tab to issue JavaScript calls against
+ // window.opener, which in effect allow the destination
+ // of the link to control the source:
+ // https://dev.to/ben/the-targetblank-vulnerability-by-example
+ //
+ // To mitigate this risk, we need to add a specific rel
+ // attribute if it is not already present.
+ // rel="noopener"
+ //
+ // Unfortunately this is processing the rel twice (we
+ // already looked at it earlier ^^) as we cannot be sure
+ // of the ordering of the href and rel, and whether we
+ // have fully satisfied that we need to do this. This
+ // double processing only happens *if* target="_blank"
+ // is true.
+ var noOpenerAdded bool
+ tmpAttrs := []html.Attribute{}
+ for _, htmlAttr := range cleanAttrs {
+ var appended bool
+ if htmlAttr.Key == "rel" {
+ if strings.Contains(htmlAttr.Val, "noopener") {
+ noOpenerAdded = true
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ } else {
+ htmlAttr.Val += " noopener"
+ noOpenerAdded = true
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ }
+
+ appended = true
+ }
+ if !appended {
+ tmpAttrs = append(tmpAttrs, htmlAttr)
+ }
+ }
+ if noOpenerAdded {
+ cleanAttrs = tmpAttrs
+ } else {
+ // rel attr was not found, or else noopener would
+ // have been added already
+ rel := html.Attribute{}
+ rel.Key = "rel"
+ rel.Val = "noopener"
+ cleanAttrs = append(cleanAttrs, rel)
+ }
+
+ }
+ }
+ default:
+ }
+ }
+ }
+
+ if p.requireCrossOriginAnonymous && len(cleanAttrs) > 0 {
+ switch elementName {
+ case "audio", "img", "link", "script", "video":
+ var crossOriginFound bool
+ for i, htmlAttr := range cleanAttrs {
+ if htmlAttr.Key == "crossorigin" {
+ crossOriginFound = true
+ cleanAttrs[i].Val = "anonymous"
+ }
+ }
+
+ if !crossOriginFound {
+ crossOrigin := html.Attribute{}
+ crossOrigin.Key = "crossorigin"
+ crossOrigin.Val = "anonymous"
+ cleanAttrs = append(cleanAttrs, crossOrigin)
+ }
+ }
+ }
+
+ if p.requireSandboxOnIFrame != nil && elementName == "iframe" {
+ var sandboxFound bool
+ for i, htmlAttr := range cleanAttrs {
+ if htmlAttr.Key == "sandbox" {
+ sandboxFound = true
+ var cleanVals []string
+ cleanValsSet := make(map[string]bool)
+ for _, val := range strings.Fields(htmlAttr.Val) {
+ if p.requireSandboxOnIFrame[val] {
+ if !cleanValsSet[val] {
+ cleanVals = append(cleanVals, val)
+ cleanValsSet[val] = true
+ }
+ }
+ }
+ cleanAttrs[i].Val = strings.Join(cleanVals, " ")
+ }
+ }
+
+ if !sandboxFound {
+ sandbox := html.Attribute{}
+ sandbox.Key = "sandbox"
+ sandbox.Val = ""
+ cleanAttrs = append(cleanAttrs, sandbox)
+ }
+ }
+
+ return cleanAttrs
+}
+
+func (p *Policy) sanitizeStyles(attr html.Attribute, elementName string) html.Attribute {
+ sps := p.elsAndStyles[elementName]
+ if len(sps) == 0 {
+ sps = map[string][]stylePolicy{}
+ // check for any matching elements, if we don't already have a policy found
+ // if multiple matches are found they will be overwritten, it's best
+ // to not have overlapping matchers
+ for regex, policies := range p.elsMatchingAndStyles {
+ if regex.MatchString(elementName) {
+ for k, v := range policies {
+ sps[k] = append(sps[k], v...)
+ }
+ }
+ }
+ }
+
+ //Add semi-colon to end to fix parsing issue
+ attr.Val = strings.TrimRight(attr.Val, " ")
+ if len(attr.Val) > 0 && attr.Val[len(attr.Val)-1] != ';' {
+ attr.Val = attr.Val + ";"
+ }
+ decs, err := parser.ParseDeclarations(attr.Val)
+ if err != nil {
+ attr.Val = ""
+ return attr
+ }
+ clean := []string{}
+ prefixes := []string{"-webkit-", "-moz-", "-ms-", "-o-", "mso-", "-xv-", "-atsc-", "-wap-", "-khtml-", "prince-", "-ah-", "-hp-", "-ro-", "-rim-", "-tc-"}
+
+decLoop:
+ for _, dec := range decs {
+ tempProperty := strings.ToLower(dec.Property)
+ tempValue := removeUnicode(strings.ToLower(dec.Value))
+ for _, i := range prefixes {
+ tempProperty = strings.TrimPrefix(tempProperty, i)
+ }
+ if spl, ok := sps[tempProperty]; ok {
+ for _, sp := range spl {
+ if sp.handler != nil {
+ if sp.handler(tempValue) {
+ clean = append(clean, dec.Property+": "+dec.Value)
+ continue decLoop
+ }
+ } else if len(sp.enum) > 0 {
+ if stringInSlice(tempValue, sp.enum) {
+ clean = append(clean, dec.Property+": "+dec.Value)
+ continue decLoop
+ }
+ } else if sp.regexp != nil {
+ if sp.regexp.MatchString(tempValue) {
+ clean = append(clean, dec.Property+": "+dec.Value)
+ continue decLoop
+ }
+ }
+ }
+ }
+ if spl, ok := p.globalStyles[tempProperty]; ok {
+ for _, sp := range spl {
+ if sp.handler != nil {
+ if sp.handler(tempValue) {
+ clean = append(clean, dec.Property+": "+dec.Value)
+ continue decLoop
+ }
+ } else if len(sp.enum) > 0 {
+ if stringInSlice(tempValue, sp.enum) {
+ clean = append(clean, dec.Property+": "+dec.Value)
+ continue decLoop
+ }
+ } else if sp.regexp != nil {
+ if sp.regexp.MatchString(tempValue) {
+ clean = append(clean, dec.Property+": "+dec.Value)
+ continue decLoop
+ }
+ }
+ }
+ }
+ }
+ if len(clean) > 0 {
+ attr.Val = strings.Join(clean, "; ")
+ } else {
+ attr.Val = ""
+ }
+ return attr
+}
+
+func (p *Policy) allowNoAttrs(elementName string) bool {
+ _, ok := p.setOfElementsAllowedWithoutAttrs[elementName]
+ if !ok {
+ for _, r := range p.setOfElementsMatchingAllowedWithoutAttrs {
+ if r.MatchString(elementName) {
+ ok = true
+ break
+ }
+ }
+ }
+ return ok
+}
+
+func (p *Policy) validURL(rawurl string) (string, bool) {
+ if p.requireParseableURLs {
+ // URLs are valid if when space is trimmed the URL is valid
+ rawurl = strings.TrimSpace(rawurl)
+
+ // URLs cannot contain whitespace, unless it is a data-uri
+ if strings.Contains(rawurl, " ") ||
+ strings.Contains(rawurl, "\t") ||
+ strings.Contains(rawurl, "\n") {
+ if !strings.HasPrefix(rawurl, `data:`) {
+ return "", false
+ }
+
+ // Remove \r and \n from base64 encoded data to pass url.Parse.
+ matched := dataURIbase64Prefix.FindString(rawurl)
+ if matched != "" {
+ rawurl = matched + strings.Replace(
+ strings.Replace(
+ rawurl[len(matched):],
+ "\r",
+ "",
+ -1,
+ ),
+ "\n",
+ "",
+ -1,
+ )
+ }
+ }
+
+ // URLs are valid if they parse
+ u, err := url.Parse(rawurl)
+ if err != nil {
+ return "", false
+ }
+
+ if u.Scheme != "" {
+ urlPolicies, ok := p.allowURLSchemes[u.Scheme]
+ if !ok {
+ for _, r := range p.allowURLSchemeRegexps {
+ if r.MatchString(u.Scheme) {
+ return u.String(), true
+ }
+ }
+
+ return "", false
+ }
+
+ if len(urlPolicies) == 0 {
+ return u.String(), true
+ }
+
+ for _, urlPolicy := range urlPolicies {
+ if urlPolicy(u) {
+ return u.String(), true
+ }
+ }
+
+ return "", false
+ }
+
+ if p.allowRelativeURLs {
+ if u.String() != "" {
+ return u.String(), true
+ }
+ }
+
+ return "", false
+ }
+
+ return rawurl, true
+}
+
+func linkable(elementName string) bool {
+ switch elementName {
+ case "a", "area", "base", "link":
+ // elements that allow .href
+ return true
+ case "blockquote", "del", "ins", "q":
+ // elements that allow .cite
+ return true
+ case "audio", "embed", "iframe", "img", "input", "script", "track", "video":
+ // elements that allow .src
+ return true
+ default:
+ return false
+ }
+}
+
+// stringInSlice returns true if needle exists in haystack
+func stringInSlice(needle string, haystack []string) bool {
+ for _, straw := range haystack {
+ if strings.EqualFold(straw, needle) {
+ return true
+ }
+ }
+ return false
+}
+
+func isDataAttribute(val string) bool {
+ if !dataAttribute.MatchString(val) {
+ return false
+ }
+ rest := strings.Split(val, "data-")
+ if len(rest) == 1 {
+ return false
+ }
+ // data-xml* is invalid.
+ if dataAttributeXMLPrefix.MatchString(rest[1]) {
+ return false
+ }
+ // no uppercase or semi-colons allowed.
+ if dataAttributeInvalidChars.MatchString(rest[1]) {
+ return false
+ }
+ return true
+}
+
+func removeUnicode(value string) string {
+ substitutedValue := value
+ currentLoc := cssUnicodeChar.FindStringIndex(substitutedValue)
+ for currentLoc != nil {
+
+ character := substitutedValue[currentLoc[0]+1 : currentLoc[1]]
+ character = strings.TrimSpace(character)
+ if len(character) < 4 {
+ character = strings.Repeat("0", 4-len(character)) + character
+ } else {
+ for len(character) > 4 {
+ if character[0] != '0' {
+ character = ""
+ break
+ } else {
+ character = character[1:]
+ }
+ }
+ }
+ character = "\\u" + character
+ translatedChar, err := strconv.Unquote(`"` + character + `"`)
+ translatedChar = strings.TrimSpace(translatedChar)
+ if err != nil {
+ return ""
+ }
+ substitutedValue = substitutedValue[0:currentLoc[0]] + translatedChar + substitutedValue[currentLoc[1]:]
+ currentLoc = cssUnicodeChar.FindStringIndex(substitutedValue)
+ }
+ return substitutedValue
+}
+
+func (p *Policy) matchRegex(elementName string) (map[string][]attrPolicy, bool) {
+ aps := make(map[string][]attrPolicy, 0)
+ matched := false
+ for regex, attrs := range p.elsMatchingAndAttrs {
+ if regex.MatchString(elementName) {
+ matched = true
+ for k, v := range attrs {
+ aps[k] = append(aps[k], v...)
+ }
+ }
+ }
+ return aps, matched
+}
+
+// normaliseElementName takes a HTML element like " that closes the next token. If
+ // non-empty, the subsequent call to Next will return a raw or RCDATA text
+ // token: one that treats "" as text instead of an element.
+ // rawTag's contents are lower-cased.
+ rawTag string
+ // textIsRaw is whether the current text token's data is not escaped.
+ textIsRaw bool
+ // convertNUL is whether NUL bytes in the current token's data should
+ // be converted into \ufffd replacement characters.
+ convertNUL bool
+ // allowCDATA is whether CDATA sections are allowed in the current context.
+ allowCDATA bool
+}
+
+// AllowCDATA sets whether or not the tokenizer recognizes as
+// the text "foo". The default value is false, which means to recognize it as
+// a bogus comment "" instead.
+//
+// Strictly speaking, an HTML5 compliant tokenizer should allow CDATA if and
+// only if tokenizing foreign content, such as MathML and SVG. However,
+// tracking foreign-contentness is difficult to do purely in the tokenizer,
+// as opposed to the parser, due to HTML integration points: an
", where "foo" is z.rawTag and
+// is typically something like "script" or "textarea".
+func (z *Tokenizer) readRawOrRCDATA() {
+ if z.rawTag == "script" {
+ z.readScript()
+ z.textIsRaw = true
+ z.rawTag = ""
+ return
+ }
+loop:
+ for {
+ c := z.readByte()
+ if z.err != nil {
+ break loop
+ }
+ if c != '<' {
+ continue loop
+ }
+ c = z.readByte()
+ if z.err != nil {
+ break loop
+ }
+ if c != '/' {
+ z.raw.end--
+ continue loop
+ }
+ if z.readRawEndTag() || z.err != nil {
+ break loop
+ }
+ }
+ z.data.end = z.raw.end
+ // A textarea's or title's RCDATA can contain escaped entities.
+ z.textIsRaw = z.rawTag != "textarea" && z.rawTag != "title"
+ z.rawTag = ""
+}
+
+// readRawEndTag attempts to read a tag like " ", where "foo" is z.rawTag.
+// If it succeeds, it backs up the input position to reconsume the tag and
+// returns true. Otherwise it returns false. The opening "" has already been
+// consumed.
+func (z *Tokenizer) readRawEndTag() bool {
+ for i := 0; i < len(z.rawTag); i++ {
+ c := z.readByte()
+ if z.err != nil {
+ return false
+ }
+ if c != z.rawTag[i] && c != z.rawTag[i]-('a'-'A') {
+ z.raw.end--
+ return false
+ }
+ }
+ c := z.readByte()
+ if z.err != nil {
+ return false
+ }
+ switch c {
+ case ' ', '\n', '\r', '\t', '\f', '/', '>':
+ // The 3 is 2 for the leading "" plus 1 for the trailing character c.
+ z.raw.end -= 3 + len(z.rawTag)
+ return true
+ }
+ z.raw.end--
+ return false
+}
+
+// readScript reads until the next tag, following the byzantine
+// rules for escaping/hiding the closing tag.
+func (z *Tokenizer) readScript() {
+ defer func() {
+ z.data.end = z.raw.end
+ }()
+ var c byte
+
+scriptData:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ if c == '<' {
+ goto scriptDataLessThanSign
+ }
+ goto scriptData
+
+scriptDataLessThanSign:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ switch c {
+ case '/':
+ goto scriptDataEndTagOpen
+ case '!':
+ goto scriptDataEscapeStart
+ }
+ z.raw.end--
+ goto scriptData
+
+scriptDataEndTagOpen:
+ if z.readRawEndTag() || z.err != nil {
+ return
+ }
+ goto scriptData
+
+scriptDataEscapeStart:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ if c == '-' {
+ goto scriptDataEscapeStartDash
+ }
+ z.raw.end--
+ goto scriptData
+
+scriptDataEscapeStartDash:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ if c == '-' {
+ goto scriptDataEscapedDashDash
+ }
+ z.raw.end--
+ goto scriptData
+
+scriptDataEscaped:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ switch c {
+ case '-':
+ goto scriptDataEscapedDash
+ case '<':
+ goto scriptDataEscapedLessThanSign
+ }
+ goto scriptDataEscaped
+
+scriptDataEscapedDash:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ switch c {
+ case '-':
+ goto scriptDataEscapedDashDash
+ case '<':
+ goto scriptDataEscapedLessThanSign
+ }
+ goto scriptDataEscaped
+
+scriptDataEscapedDashDash:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ switch c {
+ case '-':
+ goto scriptDataEscapedDashDash
+ case '<':
+ goto scriptDataEscapedLessThanSign
+ case '>':
+ goto scriptData
+ }
+ goto scriptDataEscaped
+
+scriptDataEscapedLessThanSign:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ if c == '/' {
+ goto scriptDataEscapedEndTagOpen
+ }
+ if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' {
+ goto scriptDataDoubleEscapeStart
+ }
+ z.raw.end--
+ goto scriptData
+
+scriptDataEscapedEndTagOpen:
+ if z.readRawEndTag() || z.err != nil {
+ return
+ }
+ goto scriptDataEscaped
+
+scriptDataDoubleEscapeStart:
+ z.raw.end--
+ for i := 0; i < len("script"); i++ {
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ if c != "script"[i] && c != "SCRIPT"[i] {
+ z.raw.end--
+ goto scriptDataEscaped
+ }
+ }
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ switch c {
+ case ' ', '\n', '\r', '\t', '\f', '/', '>':
+ goto scriptDataDoubleEscaped
+ }
+ z.raw.end--
+ goto scriptDataEscaped
+
+scriptDataDoubleEscaped:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ switch c {
+ case '-':
+ goto scriptDataDoubleEscapedDash
+ case '<':
+ goto scriptDataDoubleEscapedLessThanSign
+ }
+ goto scriptDataDoubleEscaped
+
+scriptDataDoubleEscapedDash:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ switch c {
+ case '-':
+ goto scriptDataDoubleEscapedDashDash
+ case '<':
+ goto scriptDataDoubleEscapedLessThanSign
+ }
+ goto scriptDataDoubleEscaped
+
+scriptDataDoubleEscapedDashDash:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ switch c {
+ case '-':
+ goto scriptDataDoubleEscapedDashDash
+ case '<':
+ goto scriptDataDoubleEscapedLessThanSign
+ case '>':
+ goto scriptData
+ }
+ goto scriptDataDoubleEscaped
+
+scriptDataDoubleEscapedLessThanSign:
+ c = z.readByte()
+ if z.err != nil {
+ return
+ }
+ if c == '/' {
+ goto scriptDataDoubleEscapeEnd
+ }
+ z.raw.end--
+ goto scriptDataDoubleEscaped
+
+scriptDataDoubleEscapeEnd:
+ if z.readRawEndTag() {
+ z.raw.end += len("")
+ goto scriptDataEscaped
+ }
+ if z.err != nil {
+ return
+ }
+ goto scriptDataDoubleEscaped
+}
+
+// readComment reads the next comment token starting with ".
+ z.data.end = z.data.start
+ }
+ }()
+
+ var dashCount int
+ beginning := true
+ for {
+ c := z.readByte()
+ if z.err != nil {
+ z.data.end = z.calculateAbruptCommentDataEnd()
+ return
+ }
+ switch c {
+ case '-':
+ dashCount++
+ continue
+ case '>':
+ if dashCount >= 2 || beginning {
+ z.data.end = z.raw.end - len("-->")
+ return
+ }
+ case '!':
+ if dashCount >= 2 {
+ c = z.readByte()
+ if z.err != nil {
+ z.data.end = z.calculateAbruptCommentDataEnd()
+ return
+ } else if c == '>' {
+ z.data.end = z.raw.end - len("--!>")
+ return
+ } else if c == '-' {
+ dashCount = 1
+ beginning = false
+ continue
+ }
+ }
+ }
+ dashCount = 0
+ beginning = false
+ }
+}
+
+func (z *Tokenizer) calculateAbruptCommentDataEnd() int {
+ raw := z.Raw()
+ const prefixLen = len("", a "", a "" or
+// "':
+ if brackets >= 2 {
+ z.data.end = z.raw.end - len("]]>")
+ return true
+ }
+ brackets = 0
+ default:
+ brackets = 0
+ }
+ }
+}
+
+// startTagIn returns whether the start tag in z.buf[z.data.start:z.data.end]
+// case-insensitively matches any element of ss.
+func (z *Tokenizer) startTagIn(ss ...string) bool {
+loop:
+ for _, s := range ss {
+ if z.data.end-z.data.start != len(s) {
+ continue loop
+ }
+ for i := 0; i < len(s); i++ {
+ c := z.buf[z.data.start+i]
+ if 'A' <= c && c <= 'Z' {
+ c += 'a' - 'A'
+ }
+ if c != s[i] {
+ continue loop
+ }
+ }
+ return true
+ }
+ return false
+}
+
+// readStartTag reads the next start tag token. The opening ").
+ //
+ // Originally, we did this by just checking that the last character of the
+ // tag (ignoring the closing bracket) was a solidus (/) character, but this
+ // is not always accurate.
+ //
+ // We need to be careful that we don't misinterpret a non-self-closing tag
+ // as self-closing, as can happen if the tag contains unquoted attribute
+ // values (i.e. ).
+ //
+ // To avoid this, we check that the last non-bracket character of the tag
+ // (z.raw.end-2) isn't the same character as the last non-quote character of
+ // the last attribute of the tag (z.pendingAttr[1].end-1), if the tag has
+ // attributes.
+ nAttrs := len(z.attr)
+ if z.err == nil && z.buf[z.raw.end-2] == '/' && (nAttrs == 0 || z.raw.end-2 != z.attr[nAttrs-1][1].end-1) {
+ return SelfClosingTagToken
+ }
+ return StartTagToken
+}
+
+// readTag reads the next tag token and its attributes. If saveAttr, those
+// attributes are saved in z.attr, otherwise z.attr is set to an empty slice.
+// The opening "' {
+ break
+ }
+ z.raw.end--
+ z.readTagAttrKey()
+ z.readTagAttrVal()
+ // Save pendingAttr if saveAttr and that attribute has a non-empty key.
+ if saveAttr && z.pendingAttr[0].start != z.pendingAttr[0].end {
+ z.attr = append(z.attr, z.pendingAttr)
+ }
+ if z.skipWhiteSpace(); z.err != nil {
+ break
+ }
+ }
+}
+
+// readTagName sets z.data to the "div" in "". The reader (z.raw.end)
+// is positioned such that the first byte of the tag name (the "d" in "':
+ z.raw.end--
+ z.data.end = z.raw.end
+ return
+ }
+ }
+}
+
+// readTagAttrKey sets z.pendingAttr[0] to the "k" in "".
+// Precondition: z.err == nil.
+func (z *Tokenizer) readTagAttrKey() {
+ z.pendingAttr[0].start = z.raw.end
+ for {
+ c := z.readByte()
+ if z.err != nil {
+ z.pendingAttr[0].end = z.raw.end
+ return
+ }
+ switch c {
+ case '=':
+ if z.pendingAttr[0].start+1 == z.raw.end {
+ // WHATWG 13.2.5.32, if we see an equals sign before the attribute name
+ // begins, we treat it as a character in the attribute name and continue.
+ continue
+ }
+ fallthrough
+ case ' ', '\n', '\r', '\t', '\f', '/', '>':
+ // WHATWG 13.2.5.33 Attribute name state
+ // We need to reconsume the char in the after attribute name state to support the / character
+ z.raw.end--
+ z.pendingAttr[0].end = z.raw.end
+ return
+ }
+ }
+}
+
+// readTagAttrVal sets z.pendingAttr[1] to the "v" in "".
+func (z *Tokenizer) readTagAttrVal() {
+ z.pendingAttr[1].start = z.raw.end
+ z.pendingAttr[1].end = z.raw.end
+ if z.skipWhiteSpace(); z.err != nil {
+ return
+ }
+ c := z.readByte()
+ if z.err != nil {
+ return
+ }
+ if c == '/' {
+ // WHATWG 13.2.5.34 After attribute name state
+ // U+002F SOLIDUS (/) - Switch to the self-closing start tag state.
+ return
+ }
+ if c != '=' {
+ z.raw.end--
+ return
+ }
+ if z.skipWhiteSpace(); z.err != nil {
+ return
+ }
+ quote := z.readByte()
+ if z.err != nil {
+ return
+ }
+ switch quote {
+ case '>':
+ z.raw.end--
+ return
+
+ case '\'', '"':
+ z.pendingAttr[1].start = z.raw.end
+ for {
+ c := z.readByte()
+ if z.err != nil {
+ z.pendingAttr[1].end = z.raw.end
+ return
+ }
+ if c == quote {
+ z.pendingAttr[1].end = z.raw.end - 1
+ return
+ }
+ }
+
+ default:
+ z.pendingAttr[1].start = z.raw.end - 1
+ for {
+ c := z.readByte()
+ if z.err != nil {
+ z.pendingAttr[1].end = z.raw.end
+ return
+ }
+ switch c {
+ case ' ', '\n', '\r', '\t', '\f':
+ z.pendingAttr[1].end = z.raw.end - 1
+ return
+ case '>':
+ z.raw.end--
+ z.pendingAttr[1].end = z.raw.end
+ return
+ }
+ }
+ }
+}
+
+// Next scans the next token and returns its type.
+func (z *Tokenizer) Next() TokenType {
+ z.raw.start = z.raw.end
+ z.data.start = z.raw.end
+ z.data.end = z.raw.end
+ if z.err != nil {
+ z.tt = ErrorToken
+ return z.tt
+ }
+ if z.rawTag != "" {
+ if z.rawTag == "plaintext" {
+ // Read everything up to EOF.
+ for z.err == nil {
+ z.readByte()
+ }
+ z.data.end = z.raw.end
+ z.textIsRaw = true
+ } else {
+ z.readRawOrRCDATA()
+ }
+ if z.data.end > z.data.start {
+ z.tt = TextToken
+ z.convertNUL = true
+ return z.tt
+ }
+ }
+ z.textIsRaw = false
+ z.convertNUL = false
+
+loop:
+ for {
+ c := z.readByte()
+ if z.err != nil {
+ break loop
+ }
+ if c != '<' {
+ continue loop
+ }
+
+ // Check if the '<' we have just read is part of a tag, comment
+ // or doctype. If not, it's part of the accumulated text token.
+ c = z.readByte()
+ if z.err != nil {
+ break loop
+ }
+ var tokenType TokenType
+ switch {
+ case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
+ tokenType = StartTagToken
+ case c == '/':
+ tokenType = EndTagToken
+ case c == '!' || c == '?':
+ // We use CommentToken to mean any of "",
+ // "" and "".
+ tokenType = CommentToken
+ default:
+ // Reconsume the current character.
+ z.raw.end--
+ continue
+ }
+
+ // We have a non-text token, but we might have accumulated some text
+ // before that. If so, we return the text first, and return the non-
+ // text token on the subsequent call to Next.
+ if x := z.raw.end - len("' {
+ // ">" does not generate a token at all. Generate an empty comment
+ // to allow passthrough clients to pick up the data using Raw.
+ // Reset the tokenizer state and start again.
+ z.tt = CommentToken
+ return z.tt
+ }
+ if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' {
+ z.readTag(false)
+ if z.err != nil {
+ z.tt = ErrorToken
+ } else {
+ z.tt = EndTagToken
+ }
+ return z.tt
+ }
+ z.raw.end--
+ z.readUntilCloseAngle()
+ z.tt = CommentToken
+ return z.tt
+ case CommentToken:
+ if c == '!' {
+ z.tt = z.readMarkupDeclaration()
+ return z.tt
+ }
+ z.raw.end--
+ z.readUntilCloseAngle()
+ z.tt = CommentToken
+ return z.tt
+ }
+ }
+ if z.raw.start < z.raw.end {
+ z.data.end = z.raw.end
+ z.tt = TextToken
+ return z.tt
+ }
+ z.tt = ErrorToken
+ return z.tt
+}
+
+// Raw returns the unmodified text of the current token. Calling Next, Token,
+// Text, TagName or TagAttr may change the contents of the returned slice.
+//
+// The token stream's raw bytes partition the byte stream (up until an
+// ErrorToken). There are no overlaps or gaps between two consecutive token's
+// raw bytes. One implication is that the byte offset of the current token is
+// the sum of the lengths of all previous tokens' raw bytes.
+func (z *Tokenizer) Raw() []byte {
+ return z.buf[z.raw.start:z.raw.end]
+}
+
+// convertNewlines converts "\r" and "\r\n" in s to "\n".
+// The conversion happens in place, but the resulting slice may be shorter.
+func convertNewlines(s []byte) []byte {
+ for i, c := range s {
+ if c != '\r' {
+ continue
+ }
+
+ src := i + 1
+ if src >= len(s) || s[src] != '\n' {
+ s[i] = '\n'
+ continue
+ }
+
+ dst := i
+ for src < len(s) {
+ if s[src] == '\r' {
+ if src+1 < len(s) && s[src+1] == '\n' {
+ src++
+ }
+ s[dst] = '\n'
+ } else {
+ s[dst] = s[src]
+ }
+ src++
+ dst++
+ }
+ return s[:dst]
+ }
+ return s
+}
+
+var (
+ nul = []byte("\x00")
+ replacement = []byte("\ufffd")
+)
+
+// Text returns the unescaped text of a text, comment or doctype token. The
+// contents of the returned slice may change on the next call to Next.
+func (z *Tokenizer) Text() []byte {
+ switch z.tt {
+ case TextToken, CommentToken, DoctypeToken:
+ s := z.buf[z.data.start:z.data.end]
+ z.data.start = z.raw.end
+ z.data.end = z.raw.end
+ s = convertNewlines(s)
+ if (z.convertNUL || z.tt == CommentToken) && bytes.Contains(s, nul) {
+ s = bytes.Replace(s, nul, replacement, -1)
+ }
+ if !z.textIsRaw {
+ s = unescape(s, false)
+ }
+ return s
+ }
+ return nil
+}
+
+// TagName returns the lower-cased name of a tag token (the `img` out of
+// `
`) and whether the tag has attributes.
+// The contents of the returned slice may change on the next call to Next.
+func (z *Tokenizer) TagName() (name []byte, hasAttr bool) {
+ if z.data.start < z.data.end {
+ switch z.tt {
+ case StartTagToken, EndTagToken, SelfClosingTagToken:
+ s := z.buf[z.data.start:z.data.end]
+ z.data.start = z.raw.end
+ z.data.end = z.raw.end
+ return lower(s), z.nAttrReturned < len(z.attr)
+ }
+ }
+ return nil, false
+}
+
+// TagAttr returns the lower-cased key and unescaped value of the next unparsed
+// attribute for the current tag token and whether there are more attributes.
+// The contents of the returned slices may change on the next call to Next.
+func (z *Tokenizer) TagAttr() (key, val []byte, moreAttr bool) {
+ if z.nAttrReturned < len(z.attr) {
+ switch z.tt {
+ case StartTagToken, SelfClosingTagToken:
+ x := z.attr[z.nAttrReturned]
+ z.nAttrReturned++
+ key = z.buf[x[0].start:x[0].end]
+ val = z.buf[x[1].start:x[1].end]
+ return lower(key), unescape(convertNewlines(val), true), z.nAttrReturned < len(z.attr)
+ }
+ }
+ return nil, nil, false
+}
+
+// Token returns the current Token. The result's Data and Attr values remain
+// valid after subsequent Next calls.
+func (z *Tokenizer) Token() Token {
+ t := Token{Type: z.tt}
+ switch z.tt {
+ case TextToken, CommentToken, DoctypeToken:
+ t.Data = string(z.Text())
+ case StartTagToken, SelfClosingTagToken, EndTagToken:
+ name, moreAttr := z.TagName()
+ for moreAttr {
+ var key, val []byte
+ key, val, moreAttr = z.TagAttr()
+ t.Attr = append(t.Attr, Attribute{"", atom.String(key), string(val)})
+ }
+ if a := atom.Lookup(name); a != 0 {
+ t.DataAtom, t.Data = a, a.String()
+ } else {
+ t.DataAtom, t.Data = 0, string(name)
+ }
+ }
+ return t
+}
+
+// SetMaxBuf sets a limit on the amount of data buffered during tokenization.
+// A value of 0 means unlimited.
+func (z *Tokenizer) SetMaxBuf(n int) {
+ z.maxBuf = n
+}
+
+// NewTokenizer returns a new HTML Tokenizer for the given Reader.
+// The input is assumed to be UTF-8 encoded.
+func NewTokenizer(r io.Reader) *Tokenizer {
+ return NewTokenizerFragment(r, "")
+}
+
+// NewTokenizerFragment returns a new HTML Tokenizer for the given Reader, for
+// tokenizing an existing element's InnerHTML fragment. contextTag is that
+// element's tag, such as "div" or "iframe".
+//
+// For example, how the InnerHTML "a tag or a