mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2af6e11adc | ||
|
|
a3a98858fd |
No files matched your search
@@ -4,7 +4,9 @@ go 1.25.5
|
||||
|
||||
replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.46.3-0.20231214181515-27e49f3c3b69
|
||||
|
||||
replace github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d
|
||||
replace github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-20260128035102-9f373ad18989
|
||||
|
||||
replace github.com/reeflective/console => github.com/moloch--/console v0.0.0-20260128035037-680800942770
|
||||
|
||||
require (
|
||||
filippo.io/age v1.3.1
|
||||
@@ -99,7 +101,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/carapace-sh/carapace v1.11.0 // indirect
|
||||
github.com/carapace-sh/carapace v1.11.0
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1 // indirect
|
||||
github.com/catppuccin/go v0.3.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
|
||||
|
||||
@@ -91,12 +91,8 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/carapace-sh/carapace v1.9.0 h1:IuEP6YeeK4fhrH7MoOyL0I3GMhvROerkw6YGxkx8Fbw=
|
||||
github.com/carapace-sh/carapace v1.9.0/go.mod h1:Zs3DpsawpFm+8fEfRVYE9EzMBYH8u7S3Aw9yAAKX0JM=
|
||||
github.com/carapace-sh/carapace v1.11.0 h1:dTUFXvIrUTLd9BGLsfDt4wCgEyPl8bJeZOFDmN7fIfo=
|
||||
github.com/carapace-sh/carapace v1.11.0/go.mod h1:NVkuv1s8wyFvu7d98rdgYwqGKMB6GO1MEUZp5RcWri8=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0 h1:58QSJTUxghpa/rEn6+Z9UM/Hy0ipVHDUn9S1mp4i+ZE=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1 h1:ccmNeetAYZOk4IcV36youFDsXusT9uCNW2Njkw+QS+Q=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
|
||||
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||
@@ -354,12 +350,14 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/moloch--/asciicast v0.1.1 h1:btwwniCK9br3ys33XZ71IG/M3ZujSSCcBzsH/sieiXg=
|
||||
github.com/moloch--/asciicast v0.1.1/go.mod h1:OckO16UDLgxVLclrCnbocL1ix15Br/8Xv/caBoYq98o=
|
||||
github.com/moloch--/console v0.0.0-20260128035037-680800942770 h1:JtfE7Od0O5ISD4b9r9AX7FTlAf8sT6O4qr5HV6Ey5U0=
|
||||
github.com/moloch--/console v0.0.0-20260128035037-680800942770/go.mod h1:UdqyqqCSnPu3Zc8Q3kpDszrq1hcyBHsG17hlWlRJmWI=
|
||||
github.com/moloch--/go-keystone v0.0.2 h1:xThbYs45CSzoDUz5kBZxAyTPIpK3t5M7NB1x+NTTRL8=
|
||||
github.com/moloch--/go-keystone v0.0.2/go.mod h1:kpGRJpw9b7Eq3MluHrba+GPKZhSyDrvhLULSwH5nGGM=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
|
||||
github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d h1:9RbKockJl6ZdgoEh2QqU3An6Ij/NWZtRWS3J6yG0+qo=
|
||||
github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
|
||||
github.com/moloch--/readline v0.0.0-20260128035102-9f373ad18989 h1:g0EfxAWzDGYarN0RAPibsjz2M/f0pAqYNiEpCPWRDok=
|
||||
github.com/moloch--/readline v0.0.0-20260128035102-9f373ad18989/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
|
||||
github.com/moloch--/sgn v0.0.4 h1:90IC5rx3TmPEfrqPnENB0yyiqcbcNk9LqezXx7uruF0=
|
||||
github.com/moloch--/sgn v0.0.4/go.mod h1:FyWfCtdynywgLmVR71vTJztZ3WkGSkR91Y4EOijUutE=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
@@ -401,8 +399,6 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/reeflective/console v0.1.25 h1:do/r6rcKH6pCQDEzh5PhoVFcqHkqcjd0N6tNjpmYWWQ=
|
||||
github.com/reeflective/console v0.1.25/go.mod h1:It/IqEjo0PowaBuxBS9pEi5EYUgmtvQd0ytMtlS77kk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
@@ -423,11 +419,9 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
|
||||
+78
-2
@@ -1,11 +1,13 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/rivo/uniseg"
|
||||
|
||||
@@ -448,8 +450,82 @@ func (rl *Shell) selfInsert() {
|
||||
}
|
||||
|
||||
func (rl *Shell) bracketedPasteBegin() {
|
||||
// keys, _ := rl.Keys.PeekAllBytes()
|
||||
// fmt.Println(string(keys))
|
||||
rl.History.SkipSave()
|
||||
rl.completer.TrimSuffix()
|
||||
|
||||
const endSeq = "\x1b[201~"
|
||||
|
||||
data := rl.Keys.DrainBuffer()
|
||||
|
||||
for {
|
||||
if idx := bytes.Index(data, []byte(endSeq)); idx >= 0 {
|
||||
payload := data[:idx]
|
||||
rest := data[idx+len(endSeq):]
|
||||
if len(rest) > 0 {
|
||||
rl.Keys.PrependBuffer(rest)
|
||||
}
|
||||
|
||||
rl.insertPastedBytes(payload)
|
||||
return
|
||||
}
|
||||
|
||||
more, err := rl.Keys.ReadInput()
|
||||
if err != nil {
|
||||
rl.insertPastedBytes(data)
|
||||
return
|
||||
}
|
||||
|
||||
if len(more) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
data = append(data, more...)
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Shell) insertPastedBytes(data []byte) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
runes := normalizePasteBytes(data)
|
||||
if len(runes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
rl.cursor.InsertAt(runes...)
|
||||
rl.Keys.SetMatched(runes...)
|
||||
}
|
||||
|
||||
func normalizePasteBytes(data []byte) []rune {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make([]rune, 0, len(data))
|
||||
|
||||
for len(data) > 0 {
|
||||
if data[0] == '\r' {
|
||||
if len(data) > 1 && data[1] == '\n' {
|
||||
data = data[1:]
|
||||
}
|
||||
out = append(out, '\n')
|
||||
data = data[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRune(data)
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
out = append(out, rune(data[0]))
|
||||
data = data[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, r)
|
||||
data = data[size:]
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// Drag the character before point forward over the character
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ func Display(eng *Engine, maxRows int) {
|
||||
// little more time. The engine itself is responsible for
|
||||
// deleting those lists when it deems them useless.
|
||||
if eng.Matches() == 0 || eng.skipDisplay {
|
||||
fmt.Print(term.ClearLineAfter)
|
||||
term.WriteString(term.ClearLineAfter)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func Display(eng *Engine, maxRows int) {
|
||||
completions, eng.usedY = eng.cropCompletions(completions, maxRows)
|
||||
|
||||
if completions != "" {
|
||||
fmt.Print(completions)
|
||||
term.WriteString(completions)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+84
-1
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
keyScanBufSize = 1024
|
||||
keyScanBufSize = 8192
|
||||
)
|
||||
|
||||
// Stdin is used by the Keys struct to read and write keys.
|
||||
@@ -238,6 +238,89 @@ func FlushUsed(keys *Keys) {
|
||||
defer keys.mutex.Unlock()
|
||||
}
|
||||
|
||||
// Pending returns the number of buffered keys waiting to be processed.
|
||||
func (k *Keys) Pending() int {
|
||||
k.mutex.RLock()
|
||||
defer k.mutex.RUnlock()
|
||||
|
||||
return len(k.buf) + len(k.macroKeys)
|
||||
}
|
||||
|
||||
// BufferSize returns the number of raw input bytes buffered.
|
||||
func (k *Keys) BufferSize() int {
|
||||
k.mutex.RLock()
|
||||
defer k.mutex.RUnlock()
|
||||
|
||||
return len(k.buf)
|
||||
}
|
||||
|
||||
// PeekBuffer returns up to max bytes from the raw input buffer.
|
||||
func (k *Keys) PeekBuffer(max int) []byte {
|
||||
k.mutex.RLock()
|
||||
defer k.mutex.RUnlock()
|
||||
|
||||
if max <= 0 || len(k.buf) <= max {
|
||||
return append([]byte(nil), k.buf...)
|
||||
}
|
||||
|
||||
return append([]byte(nil), k.buf[:max]...)
|
||||
}
|
||||
|
||||
// DrainBuffer returns all buffered raw input bytes and clears the buffer.
|
||||
func (k *Keys) DrainBuffer() []byte {
|
||||
k.mutex.Lock()
|
||||
defer k.mutex.Unlock()
|
||||
|
||||
if len(k.buf) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := append([]byte(nil), k.buf...)
|
||||
k.buf = nil
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
// PrependBuffer pushes raw input bytes back to the front of the buffer.
|
||||
func (k *Keys) PrependBuffer(keys []byte) {
|
||||
if len(keys) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
k.mutex.Lock()
|
||||
k.buf = append(keys, k.buf...)
|
||||
k.mutex.Unlock()
|
||||
}
|
||||
|
||||
// AppendMatched appends keys to the matched buffer, so they can be recorded by macros.
|
||||
func (k *Keys) AppendMatched(keys ...rune) {
|
||||
if len(keys) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
k.mutex.Lock()
|
||||
k.matched = append(k.matched, keys...)
|
||||
k.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetMatched replaces the matched buffer with the provided keys.
|
||||
func (k *Keys) SetMatched(keys ...rune) {
|
||||
k.mutex.Lock()
|
||||
if len(keys) == 0 {
|
||||
k.matched = nil
|
||||
k.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
k.matched = append(k.matched[:0], keys...)
|
||||
k.mutex.Unlock()
|
||||
}
|
||||
|
||||
// ReadInput reads raw input bytes from stdin, passing through the input filter.
|
||||
func (k *Keys) ReadInput() ([]byte, error) {
|
||||
return k.readInputFiltered()
|
||||
}
|
||||
|
||||
// ReadKey reads keys from stdin like Read(), but immediately
|
||||
// returns them instead of storing them in the stack, along with
|
||||
// an indication on whether this key is an escape/abort one.
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
@@ -344,7 +343,7 @@ func DisplayLine(l *Line, indent int) {
|
||||
line += term.NewlineReturn
|
||||
}
|
||||
|
||||
fmt.Print(line)
|
||||
term.WriteString(line)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+56
-21
@@ -1,7 +1,7 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/reeflective/readline/inputrc"
|
||||
"github.com/reeflective/readline/internal/color"
|
||||
@@ -32,6 +32,10 @@ type Engine struct {
|
||||
hintRows int
|
||||
compRows int
|
||||
primaryPrinted bool
|
||||
cursorKnown bool
|
||||
cursorDirty bool
|
||||
commentBegin string
|
||||
commentRegexp *regexp.Regexp
|
||||
|
||||
// UI components
|
||||
keys *core.Keys
|
||||
@@ -69,7 +73,10 @@ func Init(e *Engine, highlighter func([]rune) string) {
|
||||
// Refresh recomputes and redisplays the entire readline interface, except
|
||||
// the first lines of the primary prompt when the latter is a multiline one.
|
||||
func (e *Engine) Refresh() {
|
||||
fmt.Print(term.HideCursor)
|
||||
term.BeginBuffer()
|
||||
defer term.EndBuffer()
|
||||
|
||||
term.WriteString(term.HideCursor)
|
||||
|
||||
// Go back to the first column, and if the primary prompt
|
||||
// was not printed yet, back up to the line's beginning row.
|
||||
@@ -82,6 +89,12 @@ func (e *Engine) Refresh() {
|
||||
// Print either all or the last line of the prompt.
|
||||
e.prompt.LastPrint()
|
||||
|
||||
// Flush prompt output before querying the cursor position.
|
||||
if term.ShouldQueryCursorPos() {
|
||||
term.EndBuffer()
|
||||
term.BeginBuffer()
|
||||
}
|
||||
|
||||
// Get all positions required for the redisplay to come:
|
||||
// prompt end (thus indentation), cursor positions, etc.
|
||||
e.computeCoordinates(true)
|
||||
@@ -99,7 +112,7 @@ func (e *Engine) Refresh() {
|
||||
} else {
|
||||
e.lineEndToCursorPos()
|
||||
}
|
||||
fmt.Print(term.ShowCursor)
|
||||
term.WriteString(term.ShowCursor)
|
||||
}
|
||||
|
||||
// PrintPrimaryPrompt redraws the primary prompt.
|
||||
@@ -108,12 +121,13 @@ func (e *Engine) Refresh() {
|
||||
func (e *Engine) PrintPrimaryPrompt() {
|
||||
e.prompt.PrimaryPrint()
|
||||
e.primaryPrinted = true
|
||||
e.MarkCursorDirty()
|
||||
}
|
||||
|
||||
// ClearHelpers clears the hint and completion sections below the line.
|
||||
func (e *Engine) ClearHelpers() {
|
||||
e.CursorBelowLine()
|
||||
fmt.Print(term.ClearScreenBelow)
|
||||
term.WriteString(term.ClearScreenBelow)
|
||||
|
||||
term.MoveCursorUp(1)
|
||||
term.MoveCursorUp(e.lineRows)
|
||||
@@ -140,14 +154,15 @@ func (e *Engine) AcceptLine() {
|
||||
term.MoveCursorBackwards(term.GetWidth())
|
||||
term.MoveCursorDown(e.lineRows)
|
||||
term.MoveCursorForwards(e.lineCol)
|
||||
fmt.Print(term.ClearScreenBelow)
|
||||
term.WriteString(term.ClearScreenBelow)
|
||||
|
||||
// Reprint the right-side prompt if it's not a tooltip one.
|
||||
e.prompt.RightPrint(e.lineCol, false)
|
||||
|
||||
// Go below this non-suggested line and clear everything.
|
||||
term.MoveCursorBackwards(term.GetWidth())
|
||||
fmt.Print(term.NewlineReturn)
|
||||
term.WriteString(term.NewlineReturn)
|
||||
e.MarkCursorDirty()
|
||||
}
|
||||
|
||||
// RefreshTransient goes back to the first line of the input buffer
|
||||
@@ -164,7 +179,8 @@ func (e *Engine) RefreshTransient() {
|
||||
// And redisplay the transient/primary/line.
|
||||
e.prompt.TransientPrint()
|
||||
e.displayLine()
|
||||
fmt.Print(term.NewlineReturn)
|
||||
term.WriteString(term.NewlineReturn)
|
||||
e.MarkCursorDirty()
|
||||
}
|
||||
|
||||
// CursorToLineStart moves the cursor just after the primary prompt.
|
||||
@@ -183,7 +199,7 @@ func (e *Engine) CursorToLineStart() {
|
||||
func (e *Engine) CursorBelowLine() {
|
||||
term.MoveCursorUp(e.cursorRow)
|
||||
term.MoveCursorDown(e.lineRows)
|
||||
fmt.Print(term.NewlineReturn)
|
||||
term.WriteString(term.NewlineReturn)
|
||||
}
|
||||
|
||||
// lineStartToCursorPos can be used if the cursor is currently
|
||||
@@ -202,6 +218,11 @@ func (e *Engine) cursorHintToLineStart() {
|
||||
e.CursorToLineStart()
|
||||
}
|
||||
|
||||
// MarkCursorDirty forces the next refresh to query the cursor position.
|
||||
func (e *Engine) MarkCursorDirty() {
|
||||
e.cursorDirty = true
|
||||
}
|
||||
|
||||
func (e *Engine) computeCoordinates(suggested bool) {
|
||||
// Get the new input line and auto-suggested one.
|
||||
e.line, e.cursor = e.completer.Line()
|
||||
@@ -211,17 +232,31 @@ func (e *Engine) computeCoordinates(suggested bool) {
|
||||
e.suggested = e.histories.Suggest(e.line)
|
||||
}
|
||||
|
||||
// Get the position of the line's beginning by querying
|
||||
// the terminal for the cursor position.
|
||||
e.startCols, e.startRows = e.keys.GetCursorPos()
|
||||
// Get the position of the line's beginning. When possible, reuse the
|
||||
// cached cursor row to avoid the expensive terminal query.
|
||||
if term.ShouldQueryCursorPos() && (!e.cursorKnown || e.cursorDirty) {
|
||||
e.startCols, e.startRows = e.keys.GetCursorPos()
|
||||
|
||||
if e.startCols > 0 {
|
||||
e.startCols--
|
||||
}
|
||||
if e.startCols > 0 {
|
||||
e.startCols--
|
||||
}
|
||||
|
||||
// Cursor position might be misleading if invalid (negative).
|
||||
if e.startCols == -1 {
|
||||
e.startCols = e.prompt.LastUsed()
|
||||
// Cursor position might be misleading if invalid (negative).
|
||||
if e.startCols == -1 {
|
||||
e.startCols = e.prompt.LastUsed()
|
||||
}
|
||||
|
||||
e.cursorKnown = true
|
||||
e.cursorDirty = false
|
||||
} else {
|
||||
e.startCols = e.prompt.LastCols()
|
||||
if e.startCols == 0 {
|
||||
e.startCols = e.prompt.LastUsed()
|
||||
}
|
||||
if !e.cursorKnown {
|
||||
e.startRows = 0
|
||||
e.cursorKnown = true
|
||||
}
|
||||
}
|
||||
|
||||
e.cursorCol, e.cursorRow = core.CoordinatesCursor(e.cursor, e.startCols)
|
||||
@@ -269,8 +304,8 @@ func (e *Engine) displayLine() {
|
||||
|
||||
// Adjust the cursor if the line fits exactly in the terminal width.
|
||||
if e.lineCol == 0 {
|
||||
fmt.Print(term.NewlineReturn)
|
||||
fmt.Print(term.ClearLineAfter)
|
||||
term.WriteString(term.NewlineReturn)
|
||||
term.WriteString(term.ClearLineAfter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +345,7 @@ func (e *Engine) displayHelpers() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
fmt.Print(term.NewlineReturn)
|
||||
term.WriteString(term.NewlineReturn)
|
||||
|
||||
prevHintRows := e.hintRows
|
||||
prevCompRows := e.compRows
|
||||
@@ -327,7 +362,7 @@ func (e *Engine) displayHelpers() bool {
|
||||
}
|
||||
|
||||
if e.hintRows+e.compRows < prevHintRows+prevCompRows {
|
||||
fmt.Print(term.ClearScreenBelow)
|
||||
term.WriteString(term.ClearScreenBelow)
|
||||
}
|
||||
|
||||
// Go back to the first line below the input line.
|
||||
|
||||
+28
-15
@@ -19,29 +19,41 @@ func (e *Engine) highlightLine(line []rune, selection core.Selection) string {
|
||||
sorted := sortHighlights(selection)
|
||||
colors := e.getHighlights(line, sorted)
|
||||
|
||||
var highlighted string
|
||||
var highlighted strings.Builder
|
||||
|
||||
// And apply highlighting before each rune.
|
||||
for i, r := range line {
|
||||
if highlight, found := colors[i]; found {
|
||||
highlighted += string(highlight)
|
||||
highlighted.WriteString(highlight)
|
||||
}
|
||||
|
||||
highlighted += string(r)
|
||||
highlighted.WriteRune(r)
|
||||
}
|
||||
|
||||
// Finally, highlight comments using a regex.
|
||||
result := highlighted.String()
|
||||
|
||||
// Finally, highlight comments using a cached regex.
|
||||
comment := strings.Trim(e.opts.GetString("comment-begin"), "\"")
|
||||
commentPattern := fmt.Sprintf(`(^|\s)%s.*`, comment)
|
||||
if comment != e.commentBegin {
|
||||
e.commentBegin = comment
|
||||
e.commentRegexp = nil
|
||||
|
||||
if commentsMatch, err := regexp.Compile(commentPattern); err == nil {
|
||||
commentColor := color.SGRStart + color.Fg + "244" + color.SGREnd
|
||||
highlighted = commentsMatch.ReplaceAllString(highlighted, fmt.Sprintf("%s${0}%s", commentColor, color.Reset))
|
||||
if comment != "" {
|
||||
commentPattern := fmt.Sprintf(`(^|\s)%s.*`, comment)
|
||||
if commentsMatch, err := regexp.Compile(commentPattern); err == nil {
|
||||
e.commentRegexp = commentsMatch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlighted += color.Reset
|
||||
if e.commentRegexp != nil {
|
||||
commentColor := color.SGRStart + color.Fg + "244" + color.SGREnd
|
||||
result = e.commentRegexp.ReplaceAllString(result, fmt.Sprintf("%s${0}%s", commentColor, color.Reset))
|
||||
}
|
||||
|
||||
return highlighted
|
||||
result += color.Reset
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func sortHighlights(vhl core.Selection) []core.Selection {
|
||||
@@ -95,15 +107,16 @@ func sortHighlights(vhl core.Selection) []core.Selection {
|
||||
return sorted
|
||||
}
|
||||
|
||||
func (e *Engine) getHighlights(line []rune, sorted []core.Selection) map[int][]rune {
|
||||
highlights := make(map[int][]rune)
|
||||
var colorSeqRegexp = regexp.MustCompile(`\x1b\[[0-9;]+m`)
|
||||
|
||||
func (e *Engine) getHighlights(line []rune, sorted []core.Selection) map[int]string {
|
||||
highlights := make(map[int]string)
|
||||
|
||||
// Find any highlighting already applied on the line,
|
||||
// and keep the indexes so that we can skip those.
|
||||
var colors [][]int
|
||||
|
||||
colorMatch := regexp.MustCompile(`\x1b\[[0-9;]+m`)
|
||||
colors = colorMatch.FindAllStringIndex(string(line), -1)
|
||||
colors = colorSeqRegexp.FindAllStringIndex(string(line), -1)
|
||||
|
||||
// marks that started highlighting, but not done yet.
|
||||
regions := make([]core.Selection, 0)
|
||||
@@ -149,7 +162,7 @@ func (e *Engine) getHighlights(line []rune, sorted []core.Selection) map[int][]r
|
||||
// Add to the line, with the raw index since
|
||||
// we must take into account embedded colors.
|
||||
if len(posHl) > 0 {
|
||||
highlights[rawIndex] = posHl
|
||||
highlights[rawIndex] = string(posHl)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-21
@@ -1,7 +1,6 @@
|
||||
package keymap
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/reeflective/readline/inputrc"
|
||||
@@ -180,32 +179,19 @@ func (m *Engine) matchBind(keys []byte, binds map[string]inputrc.Bind) (inputrc.
|
||||
var match inputrc.Bind
|
||||
var prefixed []inputrc.Bind
|
||||
|
||||
// Make a sorted list with all keys in the binds map.
|
||||
var sequences []string
|
||||
for sequence := range binds {
|
||||
sequences = append(sequences, sequence)
|
||||
}
|
||||
keysStr := string(keys)
|
||||
|
||||
// Sort the list of sequences by alphabetical order and length.
|
||||
sort.Slice(sequences, func(i, j int) bool {
|
||||
if len(sequences[i]) == len(sequences[j]) {
|
||||
return sequences[i] < sequences[j]
|
||||
}
|
||||
|
||||
return len(sequences[i]) < len(sequences[j])
|
||||
})
|
||||
|
||||
// Iterate over the sorted list of sequences and find all binds
|
||||
// Iterate over the list of sequences and find all binds
|
||||
// that match the sequence either by prefix or exactly.
|
||||
for _, sequence := range sequences {
|
||||
for sequence, bind := range binds {
|
||||
seq := strutil.ConvertMeta([]rune(sequence))
|
||||
|
||||
if len(string(keys)) < len(seq) && strings.HasPrefix(seq, string(keys)) {
|
||||
prefixed = append(prefixed, binds[sequence])
|
||||
if len(keysStr) < len(seq) && strings.HasPrefix(seq, keysStr) {
|
||||
prefixed = append(prefixed, bind)
|
||||
}
|
||||
|
||||
if string(keys) == seq {
|
||||
match = binds[sequence]
|
||||
if keysStr == seq {
|
||||
match = bind
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -11,11 +11,13 @@ const (
|
||||
ClearScreen = "\x1b[2J" // Clears screen, preserving scroll buffer
|
||||
ClearDisplay = "\x1b[3J" // Clears screen fully, wipes the scroll buffer
|
||||
|
||||
CursorTopLeft = "\x1b[H"
|
||||
SaveCursorPos = "\x1b7"
|
||||
RestoreCursorPos = "\x1b8"
|
||||
HideCursor = "\x1b[?25l"
|
||||
ShowCursor = "\x1b[?25h"
|
||||
CursorTopLeft = "\x1b[H"
|
||||
SaveCursorPos = "\x1b7"
|
||||
RestoreCursorPos = "\x1b8"
|
||||
HideCursor = "\x1b[?25l"
|
||||
ShowCursor = "\x1b[?25h"
|
||||
BracketedPasteEnable = "\x1b[?2004h"
|
||||
BracketedPasteDisable = "\x1b[?2004l"
|
||||
)
|
||||
|
||||
// Some core keys needed by some stuff.
|
||||
|
||||
+80
-4
@@ -1,8 +1,11 @@
|
||||
package term
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Those variables are very important to realine low-level code: all virtual terminal
|
||||
@@ -16,13 +19,19 @@ var (
|
||||
|
||||
func init() {
|
||||
stdoutTerm = os.Stdout
|
||||
stdoutTerm = os.Stderr
|
||||
stderrTerm = os.Stdin
|
||||
stdinTerm = os.Stdin
|
||||
stderrTerm = os.Stderr
|
||||
}
|
||||
|
||||
// fallback terminal width when we can't get it through query.
|
||||
var defaultTermWidth = 80
|
||||
|
||||
var (
|
||||
outputMu sync.Mutex
|
||||
outputDepth int
|
||||
outputBuf *bufio.Writer
|
||||
)
|
||||
|
||||
// GetWidth returns the width of Stdout or 80 if the width cannot be established.
|
||||
func GetWidth() (termWidth int) {
|
||||
var err error
|
||||
@@ -50,6 +59,73 @@ func GetLength() int {
|
||||
}
|
||||
|
||||
func printf(format string, a ...interface{}) {
|
||||
s := fmt.Sprintf(format, a...)
|
||||
fmt.Print(s)
|
||||
Printf(format, a...)
|
||||
}
|
||||
|
||||
// BeginBuffer enables buffered terminal writes until EndBuffer is called.
|
||||
func BeginBuffer() {
|
||||
outputMu.Lock()
|
||||
defer outputMu.Unlock()
|
||||
|
||||
outputDepth++
|
||||
if outputDepth == 1 {
|
||||
outputBuf = bufio.NewWriterSize(stdoutTerm, 64*1024)
|
||||
}
|
||||
}
|
||||
|
||||
// EndBuffer flushes buffered terminal writes when leaving the outermost buffer.
|
||||
func EndBuffer() {
|
||||
outputMu.Lock()
|
||||
defer outputMu.Unlock()
|
||||
|
||||
if outputDepth == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
outputDepth--
|
||||
if outputDepth == 0 && outputBuf != nil {
|
||||
_ = outputBuf.Flush()
|
||||
outputBuf = nil
|
||||
}
|
||||
}
|
||||
|
||||
// WriteString writes to the terminal, using the buffer when enabled.
|
||||
func WriteString(s string) {
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
outputMu.Lock()
|
||||
defer outputMu.Unlock()
|
||||
|
||||
if outputDepth > 0 && outputBuf != nil {
|
||||
_, _ = outputBuf.WriteString(s)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = stdoutTerm.WriteString(s)
|
||||
}
|
||||
|
||||
// Printf formats and writes to the terminal, using the buffer when enabled.
|
||||
func Printf(format string, a ...interface{}) {
|
||||
WriteString(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// ShouldQueryCursorPos reports whether querying the terminal cursor position is allowed.
|
||||
// Some terminals respond slowly or cause visible flicker when queried frequently.
|
||||
func ShouldQueryCursorPos() bool {
|
||||
if v := strings.TrimSpace(os.Getenv("READLINE_CURSOR_POS")); v != "" {
|
||||
switch strings.ToLower(v) {
|
||||
case "0", "false", "no", "off":
|
||||
return false
|
||||
case "1", "true", "yes", "on":
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if os.Getenv("TERM_PROGRAM") == "iTerm.app" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/reeflective/readline/internal/color"
|
||||
@@ -78,7 +77,7 @@ func DisplayHint(hint *Hint) {
|
||||
|
||||
if len(hint.text) == 0 && len(hint.persistent) == 0 {
|
||||
if hint.cleanup {
|
||||
fmt.Print(term.ClearLineAfter)
|
||||
term.WriteString(term.ClearLineAfter)
|
||||
}
|
||||
|
||||
hint.cleanup = false
|
||||
@@ -95,7 +94,7 @@ func DisplayHint(hint *Hint) {
|
||||
text += term.ClearLineAfter + color.Reset
|
||||
|
||||
if len(text) > 0 {
|
||||
fmt.Print(text)
|
||||
term.WriteString(text)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+43
-18
@@ -20,9 +20,10 @@ const (
|
||||
// Prompt stores all prompt rendering/generation functions and is
|
||||
// in charge of displaying them, as well as computing their offsets.
|
||||
type Prompt struct {
|
||||
primaryF func() string
|
||||
primaryRows int
|
||||
primaryCols int
|
||||
primaryF func() string
|
||||
primaryRows int
|
||||
primaryCols int
|
||||
primaryColsRaw int
|
||||
|
||||
secondaryF func() string
|
||||
transientF func() string
|
||||
@@ -108,14 +109,15 @@ func (p *Prompt) PrimaryPrint() {
|
||||
|
||||
// Print the various lines.
|
||||
if prompt != "" {
|
||||
fmt.Print(prompt)
|
||||
term.WriteString(prompt)
|
||||
}
|
||||
|
||||
fmt.Print(lastPrompt)
|
||||
term.WriteString(lastPrompt)
|
||||
|
||||
// And compute coordinates
|
||||
p.primaryRows = strings.Count(prompt, "\n")
|
||||
p.primaryCols = strutil.RealLength(lastPrompt)
|
||||
p.primaryColsRaw = strutil.RealLength(lastPrompt)
|
||||
p.primaryCols = p.primaryColsRaw
|
||||
|
||||
if p.primaryCols > 0 {
|
||||
p.primaryCols--
|
||||
@@ -149,9 +151,10 @@ func (p *Prompt) LastPrint() {
|
||||
|
||||
prompt := p.formatLastPrompt(lines[len(lines)-1])
|
||||
|
||||
fmt.Print(prompt)
|
||||
term.WriteString(prompt)
|
||||
|
||||
p.primaryCols = strutil.RealLength(prompt)
|
||||
p.primaryColsRaw = strutil.RealLength(prompt)
|
||||
p.primaryCols = p.primaryColsRaw
|
||||
if p.primaryCols > 0 {
|
||||
p.primaryCols--
|
||||
}
|
||||
@@ -175,7 +178,8 @@ func (p *Prompt) LastUsed() int {
|
||||
}
|
||||
|
||||
prompt := p.formatLastPrompt(lines[len(lines)-1])
|
||||
p.primaryCols = strutil.RealLength(prompt)
|
||||
p.primaryColsRaw = strutil.RealLength(prompt)
|
||||
p.primaryCols = p.primaryColsRaw
|
||||
|
||||
if p.primaryCols > 0 {
|
||||
p.primaryCols--
|
||||
@@ -184,15 +188,36 @@ func (p *Prompt) LastUsed() int {
|
||||
return p.primaryCols
|
||||
}
|
||||
|
||||
// LastCols returns the number of terminal columns used by the last prompt line.
|
||||
func (p *Prompt) LastCols() int {
|
||||
if p.primaryF == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
if p.primaryColsRaw != 0 {
|
||||
return p.primaryColsRaw
|
||||
}
|
||||
|
||||
lines := strings.Split(p.primaryF(), "\n")
|
||||
if len(lines) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
prompt := p.formatLastPrompt(lines[len(lines)-1])
|
||||
p.primaryColsRaw = strutil.RealLength(prompt)
|
||||
|
||||
return p.primaryColsRaw
|
||||
}
|
||||
|
||||
// SecondaryPrint prints the last cursor in secondary prompt mode,
|
||||
// which is always activated when the current input line is a multiline one.
|
||||
func (p *Prompt) SecondaryPrint() {
|
||||
if p.secondaryF != nil {
|
||||
fmt.Print(p.secondaryF())
|
||||
term.WriteString(p.secondaryF())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Print(secondaryPromptDefault)
|
||||
term.WriteString(secondaryPromptDefault)
|
||||
}
|
||||
|
||||
// MultilineColumnPrint prints the multiline editor column status indicator.
|
||||
@@ -209,7 +234,7 @@ func (p *Prompt) MultilineColumnPrint() {
|
||||
column += fmt.Sprintf("\n\x1b[1;30m%d\x1b[0m", pos+2)
|
||||
}
|
||||
|
||||
fmt.Print(column)
|
||||
term.WriteString(column)
|
||||
|
||||
case len(custom) > 0:
|
||||
column := ""
|
||||
@@ -217,7 +242,7 @@ func (p *Prompt) MultilineColumnPrint() {
|
||||
column += fmt.Sprintf("\n%s\x1b[0m", custom)
|
||||
}
|
||||
|
||||
fmt.Print(column)
|
||||
term.WriteString(column)
|
||||
|
||||
case defaultCol:
|
||||
column := ""
|
||||
@@ -225,7 +250,7 @@ func (p *Prompt) MultilineColumnPrint() {
|
||||
column += "\n" + multilineColumnDefault
|
||||
}
|
||||
|
||||
fmt.Print(column)
|
||||
term.WriteString(column)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,9 +274,9 @@ func (p *Prompt) RightPrint(startColumn int, force bool) {
|
||||
}
|
||||
|
||||
if prompt, canPrint := p.formatRightPrompt(rprompt, startColumn); canPrint {
|
||||
fmt.Print(prompt)
|
||||
term.WriteString(prompt)
|
||||
} else {
|
||||
fmt.Print(term.ClearLineAfter)
|
||||
term.WriteString(term.ClearLineAfter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,10 +289,10 @@ func (p *Prompt) TransientPrint() {
|
||||
// Clean everything below where the prompt will be printed.
|
||||
term.MoveCursorBackwards(term.GetWidth())
|
||||
term.MoveCursorUp(p.primaryRows)
|
||||
fmt.Print(term.ClearScreenBelow)
|
||||
term.WriteString(term.ClearScreenBelow)
|
||||
|
||||
// And print the prompt
|
||||
fmt.Print(p.transientF())
|
||||
term.WriteString(p.transientF())
|
||||
}
|
||||
|
||||
// Refreshing returns true if the prompt is currently redisplaying
|
||||
|
||||
+3
-1
@@ -56,11 +56,13 @@ func (rl *Shell) Readline() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
defer term.Restore(descriptor, state)
|
||||
defer term.WriteString(term.BracketedPasteDisable)
|
||||
term.WriteString(term.BracketedPasteEnable)
|
||||
|
||||
// Prompts and cursor styles
|
||||
rl.Display.PrintPrimaryPrompt()
|
||||
defer rl.Display.RefreshTransient()
|
||||
defer fmt.Print(keymap.CursorStyle("default"))
|
||||
defer term.WriteString(keymap.CursorStyle("default").String())
|
||||
|
||||
rl.init()
|
||||
|
||||
|
||||
+2
-2
@@ -145,7 +145,7 @@ func (rl *Shell) Printf(msg string, args ...any) (n int, err error) {
|
||||
// and clear everything below (hints and completions).
|
||||
rl.Display.CursorBelowLine()
|
||||
term.MoveCursorBackwards(term.GetWidth())
|
||||
fmt.Print(term.ClearScreenBelow)
|
||||
term.WriteString(term.ClearScreenBelow)
|
||||
|
||||
// Skip a line, and print the formatted message.
|
||||
n, err = fmt.Printf(msg+"\n", args...)
|
||||
@@ -165,7 +165,7 @@ func (rl *Shell) PrintTransientf(msg string, args ...any) (n int, err error) {
|
||||
rl.Display.CursorToLineStart()
|
||||
term.MoveCursorBackwards(term.GetWidth())
|
||||
term.MoveCursorUp(rl.Prompt.PrimaryUsed())
|
||||
fmt.Print(term.ClearScreenBelow)
|
||||
term.WriteString(term.ClearScreenBelow)
|
||||
|
||||
// Print the logged message.
|
||||
n, err = fmt.Printf(msg+"\n", args...)
|
||||
|
||||
Vendored
+5
-4
@@ -581,15 +581,15 @@ github.com/pmezard/go-difflib/difflib
|
||||
# github.com/prometheus-community/pro-bing v0.4.0
|
||||
## explicit; go 1.19
|
||||
github.com/prometheus-community/pro-bing
|
||||
# github.com/reeflective/console v0.1.25
|
||||
## explicit; go 1.23.6
|
||||
# github.com/reeflective/console v0.1.25 => github.com/moloch--/console v0.0.0-20260128035037-680800942770
|
||||
## explicit; go 1.24.0
|
||||
github.com/reeflective/console
|
||||
github.com/reeflective/console/commands/readline
|
||||
github.com/reeflective/console/internal/completion
|
||||
github.com/reeflective/console/internal/line
|
||||
github.com/reeflective/console/internal/strutil
|
||||
github.com/reeflective/console/internal/ui
|
||||
# github.com/reeflective/readline v1.1.4 => github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d
|
||||
# github.com/reeflective/readline v1.1.4 => github.com/moloch--/readline v0.0.0-20260128035102-9f373ad18989
|
||||
## explicit; go 1.23.6
|
||||
github.com/reeflective/readline
|
||||
github.com/reeflective/readline/inputrc
|
||||
@@ -1397,4 +1397,5 @@ tailscale.com/wgengine/wgcfg/nmcfg
|
||||
tailscale.com/wgengine/wgint
|
||||
tailscale.com/wgengine/wglog
|
||||
# github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.46.3-0.20231214181515-27e49f3c3b69
|
||||
# github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d
|
||||
# github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-20260128035102-9f373ad18989
|
||||
# github.com/reeflective/console => github.com/moloch--/console v0.0.0-20260128035037-680800942770
|
||||
Reference in new issue
Block a user