mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
app/vlogscli: add interactive command-line tool for querying VictoriaLogs
This commit is contained in:
parent
61ae077e43
commit
630211cfed
79 changed files with 17406 additions and 19 deletions
59
Makefile
59
Makefile
|
@ -27,6 +27,7 @@ include package/release/Makefile
|
|||
all: \
|
||||
victoria-metrics-prod \
|
||||
victoria-logs-prod \
|
||||
vlogscli-prod \
|
||||
vmagent-prod \
|
||||
vmalert-prod \
|
||||
vmalert-tool-prod \
|
||||
|
@ -51,6 +52,7 @@ publish: \
|
|||
package: \
|
||||
package-victoria-metrics \
|
||||
package-victoria-logs \
|
||||
package-vlogscli \
|
||||
package-vmagent \
|
||||
package-vmalert \
|
||||
package-vmalert-tool \
|
||||
|
@ -320,6 +322,63 @@ release-victoria-logs-windows-goarch: victoria-logs-windows-$(GOARCH)-prod
|
|||
cd bin && rm -rf \
|
||||
victoria-logs-windows-$(GOARCH)-prod.exe
|
||||
|
||||
release-vlogscli:
|
||||
$(MAKE_PARALLEL) release-vlogscli-linux-386 \
|
||||
release-vlogscli-linux-amd64 \
|
||||
release-vlogscli-linux-arm \
|
||||
release-vlogscli-linux-arm64 \
|
||||
release-vlogscli-darwin-amd64 \
|
||||
release-vlogscli-darwin-arm64 \
|
||||
release-vlogscli-freebsd-amd64 \
|
||||
release-vlogscli-openbsd-amd64 \
|
||||
release-vlogscli-windows-amd64
|
||||
|
||||
release-vlogscli-linux-386:
|
||||
GOOS=linux GOARCH=386 $(MAKE) release-vlogscli-goos-goarch
|
||||
|
||||
release-vlogscli-linux-amd64:
|
||||
GOOS=linux GOARCH=amd64 $(MAKE) release-vlogscli-goos-goarch
|
||||
|
||||
release-vlogscli-linux-arm:
|
||||
GOOS=linux GOARCH=arm $(MAKE) release-vlogscli-goos-goarch
|
||||
|
||||
release-vlogscli-linux-arm64:
|
||||
GOOS=linux GOARCH=arm64 $(MAKE) release-vlogscli-goos-goarch
|
||||
|
||||
release-vlogscli-darwin-amd64:
|
||||
GOOS=darwin GOARCH=amd64 $(MAKE) release-vlogscli-goos-goarch
|
||||
|
||||
release-vlogscli-darwin-arm64:
|
||||
GOOS=darwin GOARCH=arm64 $(MAKE) release-vlogscli-goos-goarch
|
||||
|
||||
release-vlogscli-freebsd-amd64:
|
||||
GOOS=freebsd GOARCH=amd64 $(MAKE) release-vlogscli-goos-goarch
|
||||
|
||||
release-vlogscli-openbsd-amd64:
|
||||
GOOS=openbsd GOARCH=amd64 $(MAKE) release-vlogscli-goos-goarch
|
||||
|
||||
release-vlogscli-windows-amd64:
|
||||
GOARCH=amd64 $(MAKE) release-vlogscli-windows-goarch
|
||||
|
||||
release-vlogscli-goos-goarch: vlogscli-$(GOOS)-$(GOARCH)-prod
|
||||
cd bin && \
|
||||
tar $(TAR_OWNERSHIP) --transform="flags=r;s|-$(GOOS)-$(GOARCH)||" -czf vlogscli-$(GOOS)-$(GOARCH)-$(PKG_TAG).tar.gz \
|
||||
vlogscli-$(GOOS)-$(GOARCH)-prod \
|
||||
&& sha256sum vlogscli-$(GOOS)-$(GOARCH)-$(PKG_TAG).tar.gz \
|
||||
vlogscli-$(GOOS)-$(GOARCH)-prod \
|
||||
| sed s/-$(GOOS)-$(GOARCH)-prod/-prod/ > vlogscli-$(GOOS)-$(GOARCH)-$(PKG_TAG)_checksums.txt
|
||||
cd bin && rm -rf vlogscli-$(GOOS)-$(GOARCH)-prod
|
||||
|
||||
release-vlogscli-windows-goarch: vlogscli-windows-$(GOARCH)-prod
|
||||
cd bin && \
|
||||
zip vlogscli-windows-$(GOARCH)-$(PKG_TAG).zip \
|
||||
vlogscli-windows-$(GOARCH)-prod.exe \
|
||||
&& sha256sum vlogscli-windows-$(GOARCH)-$(PKG_TAG).zip \
|
||||
vlogscli-windows-$(GOARCH)-prod.exe \
|
||||
> vlogscli-windows-$(GOARCH)-$(PKG_TAG)_checksums.txt
|
||||
cd bin && rm -rf \
|
||||
vlogscli-windows-$(GOARCH)-prod.exe
|
||||
|
||||
release-vmutils: \
|
||||
release-vmutils-linux-386 \
|
||||
release-vmutils-linux-amd64 \
|
||||
|
|
109
app/vlogscli/Makefile
Normal file
109
app/vlogscli/Makefile
Normal file
|
@ -0,0 +1,109 @@
|
|||
# All these commands must run from repository root.
|
||||
|
||||
vlogscli:
|
||||
APP_NAME=vlogscli $(MAKE) app-local
|
||||
|
||||
vlogscli-race:
|
||||
APP_NAME=vlogscli RACE=-race $(MAKE) app-local
|
||||
|
||||
vlogscli-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker
|
||||
|
||||
vlogscli-pure-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker-pure
|
||||
|
||||
vlogscli-linux-amd64-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker-linux-amd64
|
||||
|
||||
vlogscli-linux-arm-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker-linux-arm
|
||||
|
||||
vlogscli-linux-arm64-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker-linux-arm64
|
||||
|
||||
vlogscli-linux-ppc64le-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker-linux-ppc64le
|
||||
|
||||
vlogscli-linux-386-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker-linux-386
|
||||
|
||||
vlogscli-darwin-amd64-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker-darwin-amd64
|
||||
|
||||
vlogscli-darwin-arm64-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker-darwin-arm64
|
||||
|
||||
vlogscli-freebsd-amd64-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker-freebsd-amd64
|
||||
|
||||
vlogscli-openbsd-amd64-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker-openbsd-amd64
|
||||
|
||||
vlogscli-windows-amd64-prod:
|
||||
APP_NAME=vlogscli $(MAKE) app-via-docker-windows-amd64
|
||||
|
||||
package-vlogscli:
|
||||
APP_NAME=vlogscli $(MAKE) package-via-docker
|
||||
|
||||
package-vlogscli-pure:
|
||||
APP_NAME=vlogscli $(MAKE) package-via-docker-pure
|
||||
|
||||
package-vlogscli-amd64:
|
||||
APP_NAME=vlogscli $(MAKE) package-via-docker-amd64
|
||||
|
||||
package-vlogscli-arm:
|
||||
APP_NAME=vlogscli $(MAKE) package-via-docker-arm
|
||||
|
||||
package-vlogscli-arm64:
|
||||
APP_NAME=vlogscli $(MAKE) package-via-docker-arm64
|
||||
|
||||
package-vlogscli-ppc64le:
|
||||
APP_NAME=vlogscli $(MAKE) package-via-docker-ppc64le
|
||||
|
||||
package-vlogscli-386:
|
||||
APP_NAME=vlogscli $(MAKE) package-via-docker-386
|
||||
|
||||
publish-vlogscli:
|
||||
APP_NAME=vlogscli $(MAKE) publish-via-docker
|
||||
|
||||
vlogscli-linux-amd64:
|
||||
APP_NAME=vlogscli CGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(MAKE) app-local-goos-goarch
|
||||
|
||||
vlogscli-linux-arm:
|
||||
APP_NAME=vlogscli CGO_ENABLED=0 GOOS=linux GOARCH=arm $(MAKE) app-local-goos-goarch
|
||||
|
||||
vlogscli-linux-arm64:
|
||||
APP_NAME=vlogscli CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(MAKE) app-local-goos-goarch
|
||||
|
||||
vlogscli-linux-ppc64le:
|
||||
APP_NAME=vlogscli CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le $(MAKE) app-local-goos-goarch
|
||||
|
||||
vlogscli-linux-s390x:
|
||||
APP_NAME=vlogscli CGO_ENABLED=0 GOOS=linux GOARCH=s390x $(MAKE) app-local-goos-goarch
|
||||
|
||||
vlogscli-linux-loong64:
|
||||
APP_NAME=vlogscli CGO_ENABLED=0 GOOS=linux GOARCH=loong64 $(MAKE) app-local-goos-goarch
|
||||
|
||||
vlogscli-linux-386:
|
||||
APP_NAME=vlogscli CGO_ENABLED=0 GOOS=linux GOARCH=386 $(MAKE) app-local-goos-goarch
|
||||
|
||||
vlogscli-darwin-amd64:
|
||||
APP_NAME=vlogscli CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(MAKE) app-local-goos-goarch
|
||||
|
||||
vlogscli-darwin-arm64:
|
||||
APP_NAME=vlogscli CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(MAKE) app-local-goos-goarch
|
||||
|
||||
vlogscli-freebsd-amd64:
|
||||
APP_NAME=vlogscli CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 $(MAKE) app-local-goos-goarch
|
||||
|
||||
vlogscli-openbsd-amd64:
|
||||
APP_NAME=vlogscli CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 $(MAKE) app-local-goos-goarch
|
||||
|
||||
vlogscli-windows-amd64:
|
||||
GOARCH=amd64 APP_NAME=vlogscli $(MAKE) app-local-windows-goarch
|
||||
|
||||
vlogscli-pure:
|
||||
APP_NAME=vlogscli $(MAKE) app-local-pure
|
||||
|
||||
run-vlogscli:
|
||||
APP_NAME=vlogscli $(MAKE) run-via-docker
|
5
app/vlogscli/README.md
Normal file
5
app/vlogscli/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# vlogscli
|
||||
|
||||
Command-line utility for querying [VictoriaLogs](https://docs.victoriametrics.com/victorialogs/).
|
||||
|
||||
See [these docs](https://docs.victoriametrics.com/victorialogs/querying/vlogscli/).
|
6
app/vlogscli/deployment/Dockerfile
Normal file
6
app/vlogscli/deployment/Dockerfile
Normal file
|
@ -0,0 +1,6 @@
|
|||
ARG base_image=non-existing
|
||||
FROM $base_image
|
||||
|
||||
ENTRYPOINT ["/vlogscli-prod"]
|
||||
ARG src_binary=non-existing
|
||||
COPY $src_binary ./vlogscli-prod
|
73
app/vlogscli/json_prettifier.go
Normal file
73
app/vlogscli/json_prettifier.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type jsonPrettifier struct {
|
||||
rOriginal io.ReadCloser
|
||||
|
||||
d *json.Decoder
|
||||
|
||||
pr *io.PipeReader
|
||||
pw *io.PipeWriter
|
||||
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func newJSONPrettifier(r io.ReadCloser) *jsonPrettifier {
|
||||
d := json.NewDecoder(r)
|
||||
pr, pw := io.Pipe()
|
||||
|
||||
jp := &jsonPrettifier{
|
||||
rOriginal: r,
|
||||
d: d,
|
||||
pr: pr,
|
||||
pw: pw,
|
||||
}
|
||||
|
||||
jp.wg.Add(1)
|
||||
go func() {
|
||||
defer jp.wg.Done()
|
||||
err := jp.prettifyJSONLines()
|
||||
jp.closePipesWithError(err)
|
||||
}()
|
||||
|
||||
return jp
|
||||
}
|
||||
|
||||
func (jp *jsonPrettifier) closePipesWithError(err error) {
|
||||
_ = jp.pr.CloseWithError(err)
|
||||
_ = jp.pw.CloseWithError(err)
|
||||
}
|
||||
|
||||
func (jp *jsonPrettifier) prettifyJSONLines() error {
|
||||
for jp.d.More() {
|
||||
var v any
|
||||
if err := jp.d.Decode(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
line, err := json.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("BUG: cannot marshal %v to JSON: %w", v, err))
|
||||
}
|
||||
if _, err := fmt.Fprintf(jp.pw, "%s\n", line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (jp *jsonPrettifier) Close() error {
|
||||
jp.closePipesWithError(io.ErrUnexpectedEOF)
|
||||
err := jp.rOriginal.Close()
|
||||
jp.wg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
func (jp *jsonPrettifier) Read(p []byte) (int, error) {
|
||||
return jp.pr.Read(p)
|
||||
}
|
116
app/vlogscli/less_wrapper.go
Normal file
116
app/vlogscli/less_wrapper.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
func isTerminal() bool {
|
||||
return isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stderr.Fd())
|
||||
}
|
||||
|
||||
func readWithLess(r io.Reader) error {
|
||||
if !isTerminal() {
|
||||
// Just write everything to stdout if no terminal is available.
|
||||
_, err := io.Copy(os.Stdout, r)
|
||||
if err != nil && !isErrPipe(err) {
|
||||
return fmt.Errorf("error when forwarding data to stdout: %w", err)
|
||||
}
|
||||
if err := os.Stdout.Sync(); err != nil {
|
||||
return fmt.Errorf("cannot sync data to stdout: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
pr, pw, err := os.Pipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create pipe: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = pr.Close()
|
||||
_ = pw.Close()
|
||||
}()
|
||||
|
||||
// Ignore Ctrl+C in the current process, so 'less' could handle it properly
|
||||
cancel := ignoreSignals(os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
// Start 'less' process
|
||||
path, err := exec.LookPath("less")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot find 'less' command: %w", err)
|
||||
}
|
||||
p, err := os.StartProcess(path, []string{"less", "-F"}, &os.ProcAttr{
|
||||
Env: append(os.Environ(), "LESSCHARSET=utf-8"),
|
||||
Files: []*os.File{pr, os.Stdout, os.Stderr},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot start 'less' process: %w", err)
|
||||
}
|
||||
|
||||
// Close pr after 'less' finishes in a parallel goroutine
|
||||
// in order to unblock forwarding data to stopped 'less' below.
|
||||
waitch := make(chan *os.ProcessState)
|
||||
go func() {
|
||||
// Wait for 'less' process to finish.
|
||||
ps, err := p.Wait()
|
||||
if err != nil {
|
||||
fatalf("unexpected error when waiting for 'less' process: %w", err)
|
||||
}
|
||||
_ = pr.Close()
|
||||
waitch <- ps
|
||||
}()
|
||||
|
||||
// Forward data from r to 'less'
|
||||
_, err = io.Copy(pw, r)
|
||||
_ = pw.Sync()
|
||||
_ = pw.Close()
|
||||
|
||||
// Wait until 'less' finished
|
||||
ps := <-waitch
|
||||
|
||||
// Verify 'less' status.
|
||||
if !ps.Success() {
|
||||
return fmt.Errorf("'less' finished with unexpected code %d", ps.ExitCode())
|
||||
}
|
||||
|
||||
if err != nil && !isErrPipe(err) {
|
||||
return fmt.Errorf("error when forwarding data to 'less': %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isErrPipe(err error) bool {
|
||||
return errors.Is(err, syscall.EPIPE) || errors.Is(err, io.ErrClosedPipe)
|
||||
}
|
||||
|
||||
func ignoreSignals(sigs ...os.Signal) func() {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, sigs...)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
_, ok := <-ch
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return func() {
|
||||
signal.Stop(ch)
|
||||
close(ch)
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
301
app/vlogscli/main.go
Normal file
301
app/vlogscli/main.go
Normal file
|
@ -0,0 +1,301 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ergochat/readline"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
|
||||
)
|
||||
|
||||
var (
|
||||
datasourceURL = flag.String("datasource.url", "http://localhost:9428/select/logsql/query", "URL for querying VictoriaLogs; "+
|
||||
"see https://docs.victoriametrics.com/victorialogs/querying/#querying-logs")
|
||||
historyFile = flag.String("historyFile", "vlogscli-history", "Path to file with command history")
|
||||
header = flagutil.NewArrayString("header", "Optional header to pass in request -datasource.url in the form 'HeaderName: value'")
|
||||
)
|
||||
|
||||
const (
|
||||
firstLinePrompt = ";> "
|
||||
nextLinePrompt = ""
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Write flags and help message to stdout, since it is easier to grep or pipe.
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
flag.Usage = usage
|
||||
envflag.Parse()
|
||||
buildinfo.Init()
|
||||
logger.InitNoLogFlags()
|
||||
|
||||
hes, err := parseHeaders(*header)
|
||||
if err != nil {
|
||||
fatalf("cannot parse -header command-line flag: %s", err)
|
||||
}
|
||||
headers = hes
|
||||
|
||||
isEmptyLine := true
|
||||
cfg := &readline.Config{
|
||||
Prompt: firstLinePrompt,
|
||||
DisableAutoSaveHistory: true,
|
||||
Listener: func(line []rune, pos int, _ rune) ([]rune, int, bool) {
|
||||
isEmptyLine = len(line) == 0
|
||||
return line, pos, false
|
||||
},
|
||||
}
|
||||
rl, err := readline.NewFromConfig(cfg)
|
||||
if err != nil {
|
||||
fatalf("cannot initialize readline: %s", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(rl, "sending queries to %s\n", *datasourceURL)
|
||||
|
||||
runReadlineLoop(rl, &isEmptyLine)
|
||||
|
||||
if err := rl.Close(); err != nil {
|
||||
fatalf("cannot close readline: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func runReadlineLoop(rl *readline.Instance, isEmptyLine *bool) {
|
||||
historyLines, err := loadFromHistory(*historyFile)
|
||||
if err != nil {
|
||||
fatalf("cannot load query history: %s", err)
|
||||
}
|
||||
for _, line := range historyLines {
|
||||
if err := rl.SaveToHistory(line); err != nil {
|
||||
fatalf("cannot initialize query history: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
s := ""
|
||||
for {
|
||||
line, err := rl.ReadLine()
|
||||
if err != nil {
|
||||
switch err {
|
||||
case io.EOF:
|
||||
if s != "" {
|
||||
// This is non-interactive query execution.
|
||||
if err := executeQuery(context.Background(), rl, s); err != nil {
|
||||
fmt.Fprintf(rl, "%s\n", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
case readline.ErrInterrupt:
|
||||
if s == "" && *isEmptyLine {
|
||||
fmt.Fprintf(rl, "interrupted\n")
|
||||
os.Exit(128 + int(syscall.SIGINT))
|
||||
}
|
||||
// Default value for Ctrl+C - clear the prompt
|
||||
s = ""
|
||||
rl.SetPrompt(firstLinePrompt)
|
||||
continue
|
||||
default:
|
||||
fatalf("unexpected error in readline: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
s += line
|
||||
if isQuitCommand(s) {
|
||||
fmt.Fprintf(rl, "bye!\n")
|
||||
return
|
||||
}
|
||||
if s == "" {
|
||||
// Skip empty lines
|
||||
continue
|
||||
}
|
||||
if line != "" && !strings.HasSuffix(line, ";") {
|
||||
// Assume the query is incomplete and allow the user finishing the query on the next line
|
||||
s += "\n"
|
||||
rl.SetPrompt(nextLinePrompt)
|
||||
continue
|
||||
}
|
||||
|
||||
// Execute the query
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
err = executeQuery(ctx, rl, s)
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
fmt.Fprintf(rl, "\n")
|
||||
} else {
|
||||
fmt.Fprintf(rl, "%s\n", err)
|
||||
}
|
||||
// Save queries in the history even if they weren't finished successfully
|
||||
}
|
||||
|
||||
s = strings.TrimSpace(s)
|
||||
if len(historyLines) == 0 || historyLines[len(historyLines)-1] != s {
|
||||
historyLines = append(historyLines, s)
|
||||
if len(historyLines) > 500 {
|
||||
historyLines = historyLines[len(historyLines)-500:]
|
||||
}
|
||||
if err := saveToHistory(*historyFile, historyLines); err != nil {
|
||||
fatalf("cannot save query history: %s", err)
|
||||
}
|
||||
}
|
||||
if err := rl.SaveToHistory(s); err != nil {
|
||||
fatalf("cannot update query history: %s", err)
|
||||
}
|
||||
s = ""
|
||||
rl.SetPrompt(firstLinePrompt)
|
||||
}
|
||||
}
|
||||
|
||||
func loadFromHistory(filePath string) ([]string, error) {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
linesQuoted := strings.Split(string(data), "\n")
|
||||
lines := make([]string, 0, len(linesQuoted))
|
||||
i := 0
|
||||
for _, lineQuoted := range linesQuoted {
|
||||
i++
|
||||
if lineQuoted == "" {
|
||||
continue
|
||||
}
|
||||
line, err := strconv.Unquote(lineQuoted)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse line #%d at %s: %w; line: [%s]", i, filePath, err, line)
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func saveToHistory(filePath string, lines []string) error {
|
||||
linesQuoted := make([]string, len(lines))
|
||||
for i, line := range lines {
|
||||
lineQuoted := strconv.Quote(line)
|
||||
linesQuoted[i] = lineQuoted
|
||||
}
|
||||
data := strings.Join(linesQuoted, "\n")
|
||||
return os.WriteFile(filePath, []byte(data), 0600)
|
||||
}
|
||||
|
||||
func isQuitCommand(s string) bool {
|
||||
switch s {
|
||||
case "q", "quit", "exit":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func executeQuery(ctx context.Context, output io.Writer, s string) error {
|
||||
// Parse the query and convert it to canonical view.
|
||||
s = strings.TrimSuffix(s, ";")
|
||||
q, err := logstorage.ParseQuery(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse query: %w", err)
|
||||
}
|
||||
qStr := q.String()
|
||||
fmt.Fprintf(output, "executing [%s]...", qStr)
|
||||
|
||||
// Prepare HTTP request for VictoriaLogs
|
||||
args := make(url.Values)
|
||||
args.Set("query", qStr)
|
||||
data := strings.NewReader(args.Encode())
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", *datasourceURL, data)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("BUG: cannot prepare request to server: %w", err))
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
for _, h := range headers {
|
||||
req.Header.Set(h.Name, h.Value)
|
||||
}
|
||||
|
||||
// Execute HTTP request at VictoriaLogs
|
||||
startTime := time.Now()
|
||||
resp, err := httpClient.Do(req)
|
||||
queryDuration := time.Since(startTime)
|
||||
fmt.Fprintf(output, "; duration: %.3fs\n", queryDuration.Seconds())
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot execute query: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
body = []byte(fmt.Sprintf("cannot read response body: %s", err))
|
||||
}
|
||||
return fmt.Errorf("unexpected status code: %d; response body:\n%s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
// Prettify the response and stream it to 'less'.
|
||||
jp := newJSONPrettifier(resp.Body)
|
||||
defer func() {
|
||||
_ = jp.Close()
|
||||
}()
|
||||
|
||||
if err := readWithLess(jp); err != nil {
|
||||
return fmt.Errorf("error when reading query response: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var httpClient = &http.Client{}
|
||||
|
||||
var headers []headerEntry
|
||||
|
||||
type headerEntry struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
func parseHeaders(a []string) ([]headerEntry, error) {
|
||||
hes := make([]headerEntry, len(a))
|
||||
for i, s := range a {
|
||||
a := strings.SplitN(s, ":", 2)
|
||||
if len(a) != 2 {
|
||||
return nil, fmt.Errorf("cannot parse header=%q; it must contain at least one ':'; for example, 'Cookie: foo'", s)
|
||||
}
|
||||
hes[i] = headerEntry{
|
||||
Name: strings.TrimSpace(a[0]),
|
||||
Value: strings.TrimSpace(a[1]),
|
||||
}
|
||||
}
|
||||
return hes, nil
|
||||
}
|
||||
|
||||
func fatalf(format string, args ...any) {
|
||||
fmt.Fprintf(os.Stderr, format+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func usage() {
|
||||
const s = `
|
||||
vlogscli is a command-line tool for querying VictoriaLogs.
|
||||
|
||||
See the docs at https://docs.victoriametrics.com/victorialogs/querying/vlogscli/
|
||||
`
|
||||
flagutil.Usage(s)
|
||||
}
|
11
app/vlogscli/multiarch/Dockerfile
Normal file
11
app/vlogscli/multiarch/Dockerfile
Normal file
|
@ -0,0 +1,11 @@
|
|||
# See https://medium.com/on-docker/use-multi-stage-builds-to-inject-ca-certs-ad1e8f01de1b
|
||||
ARG certs_image=non-existing
|
||||
ARG root_image=non-existing
|
||||
FROM $certs_image AS certs
|
||||
RUN apk update && apk upgrade && apk --update --no-cache add ca-certificates
|
||||
|
||||
FROM $root_image
|
||||
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
ENTRYPOINT ["/vlogscli-prod"]
|
||||
ARG TARGETARCH
|
||||
COPY vlogscli-linux-${TARGETARCH}-prod ./vlogscli-prod
|
|
@ -15,6 +15,8 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
|
|||
|
||||
## tip
|
||||
|
||||
* FEATURE: add interactive command-line tool for querying VictoriaLogs - [`vlogscli`](https://docs.victoriametrics.com/victorialogs/querying/vlogscli/).
|
||||
|
||||
## [v0.32.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.32.1-victorialogs)
|
||||
|
||||
Released at 2024-09-30
|
||||
|
|
|
@ -3,25 +3,27 @@ from [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics/).
|
|||
|
||||
VictoriaLogs provides the following features:
|
||||
|
||||
- VictoriaLogs can accept logs from popular log collectors. See [these docs](https://docs.victoriametrics.com/victorialogs/data-ingestion/).
|
||||
- VictoriaLogs is much easier to set up and operate compared to Elasticsearch and Grafana Loki.
|
||||
- It can accept logs from popular log collectors. See [these docs](https://docs.victoriametrics.com/victorialogs/data-ingestion/).
|
||||
- It is much easier to set up and operate compared to Elasticsearch and Grafana Loki.
|
||||
See [these docs](https://docs.victoriametrics.com/victorialogs/quickstart/).
|
||||
- VictoriaLogs provides easy yet powerful query language with full-text search across
|
||||
- It provides easy yet powerful query language with full-text search across
|
||||
all the [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||
See [LogsQL docs](https://docs.victoriametrics.com/victorialogs/logsql/).
|
||||
- VictoriaLogs can be seamlessly combined with good old Unix tools for log analysis such as `grep`, `less`, `sort`, `jq`, etc.
|
||||
- It provides interactive command-line tool for querying VictoriaLogs - [vlogscli](https://docs.victoriametrics.com/victorialogs/querying/vlogscli/).
|
||||
- It can be seamlessly combined with good old Unix tools for log analysis such as `grep`, `less`, `sort`, `jq`, etc.
|
||||
See [these docs](https://docs.victoriametrics.com/victorialogs/querying/#command-line) for details.
|
||||
- VictoriaLogs capacity and performance scales linearly with the available resources (CPU, RAM, disk IO, disk space).
|
||||
It runs smoothly on both Raspberry PI and a server with hundreds of CPU cores and terabytes of RAM.
|
||||
- VictoriaLogs can handle up to 30x bigger data volumes than Elasticsearch and Grafana Loki when running on the same hardware.
|
||||
- VictoriaLogs' capacity and performance scales linearly with the available resources (CPU, RAM, disk IO, disk space).
|
||||
It runs smoothly on Raspberry PI and on servers with hundreds of CPU cores and terabytes of RAM.
|
||||
- It can handle up to 30x bigger data volumes than Elasticsearch and Grafana Loki when running on the same hardware.
|
||||
See [these docs](#benchmarks).
|
||||
- VictoriaLogs supports fast full-text search over high-cardinality [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
|
||||
such as `trace_id`, `user_id` and `ip`.
|
||||
- VictoriaLogs supports multitenancy - see [these docs](#multitenancy).
|
||||
- VictoriaLogs supports out-of-order logs' ingestion aka backfilling.
|
||||
- VictoriaLogs supports live tailing for newly ingested logs. See [these docs](https://docs.victoriametrics.com/victorialogs/querying/#live-tailing).
|
||||
- VictoriaLogs supports selecting surrounding logs in front and after the selected logs. See [these docs](https://docs.victoriametrics.com/victorialogs/logsql/#stream_context-pipe).
|
||||
- VictoriaLogs provides web UI for querying logs - see [these docs](https://docs.victoriametrics.com/victorialogs/querying/#web-ui).
|
||||
- It provides fast full-text search out of the box for [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
|
||||
with high cardinality (e.g. high number of unique values) such as `trace_id`, `user_id` and `ip`.
|
||||
- It supports multitenancy - see [these docs](#multitenancy).
|
||||
- It supports out-of-order logs' ingestion aka backfilling.
|
||||
- It supports live tailing for newly ingested logs. See [these docs](https://docs.victoriametrics.com/victorialogs/querying/#live-tailing).
|
||||
- It supports selecting surrounding logs in front and after the selected logs. See [these docs](https://docs.victoriametrics.com/victorialogs/logsql/#stream_context-pipe).
|
||||
- It provides web UI for querying logs - see [these docs](https://docs.victoriametrics.com/victorialogs/querying/#web-ui).
|
||||
- It provides [Grafana plugin for querying logs](https://docs.victoriametrics.com/victorialogs/victorialogs-datasource/).
|
||||
|
||||
If you have questions about VictoriaLogs, then read [this FAQ](https://docs.victoriametrics.com/victorialogs/faq/).
|
||||
Also feel free asking any questions at [VictoriaMetrics community Slack chat](https://victoriametrics.slack.com/),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
via the following ways:
|
||||
|
||||
- [Web UI](#web-ui) - a web-based UI for querying logs
|
||||
- [Visualization in Grafana](#visualization-in-grafana)
|
||||
- [Grafana plugin](#visualization-in-grafana)
|
||||
- [HTTP API](#http-api)
|
||||
- [Command-line interface](#command-line)
|
||||
|
||||
|
@ -800,7 +800,10 @@ See also [command line interface](#command-line).
|
|||
|
||||
## Command-line
|
||||
|
||||
VictoriaLogs integrates well with `curl` and other command-line tools during querying because of the following features:
|
||||
VictoriaLogs provides `vlogsqcli` interactive command-line tool for querying logs. See [these docs](https://docs.victoriametrics.com/victorialogs/querying/vlogscli/).
|
||||
|
||||
VictoriaLogs [querying API](https://docs.victoriametrics.com/victorialogs/querying/#querying-logs) integrates well with `curl`
|
||||
and other Unix command-line tools because of the following features:
|
||||
|
||||
- Matching log entries are sent to the response stream as soon as they are found.
|
||||
This allows forwarding the response stream to arbitrary [Unix pipes](https://en.wikipedia.org/wiki/Pipeline_(Unix))
|
||||
|
|
76
docs/VictoriaLogs/querying/vlogscli.md
Normal file
76
docs/VictoriaLogs/querying/vlogscli.md
Normal file
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
weight:
|
||||
title: vlogscli
|
||||
disableToc: true
|
||||
menu:
|
||||
docs:
|
||||
parent: "victorialogs-querying"
|
||||
weight: 1
|
||||
---
|
||||
|
||||
`vlogsqcli` is an interactive command-line tool for querying [VictoriaLogs](https://docs.victoriametrics.com/victorialogs/).
|
||||
This tool can be obtained from the linked release pages at the [changelog](https://docs.victoriametrics.com/victorialogs/changelog/)
|
||||
or from [docker images](https://hub.docker.com/r/victoriametrics/vlogscli/tags).
|
||||
|
||||
By default `vlogscli` sends queries to [`http://localhost:8429/select/logsql/query`](https://docs.victoriametrics.com/victorialogs/querying/#querying-logs).
|
||||
The url to query can be changed via `-datasource.url` command-line flag. For example, the following command instructs
|
||||
`vlogsql` sending queries to `https://victoria-logs.some-domain.com/select/logsql/query`:
|
||||
|
||||
```sh
|
||||
./vlogsql -datasource.url='https://victoria-logs.some-domain.com/select/logsql/query'
|
||||
```
|
||||
|
||||
If some HTTP request headers must be passed to the querying API, then set `-header` command-line flag.
|
||||
For example, the following command starts `vlogsql`,
|
||||
which queries `(AccountID=123, ProjectID=456)` [tenant](https://docs.victoriametrics.com/victorialogs/#multitenancy):
|
||||
|
||||
```sh
|
||||
./vlogsql -header='AccountID: 123' -header='ProjectID: 456'
|
||||
```
|
||||
|
||||
After the start `vlogsql` provides a prompt for writing [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/) queries.
|
||||
The query can be multi-line. The is sent to VictoriaLogs as soon as it contains `;` at the end or if a blank line follows the query.
|
||||
For example:
|
||||
|
||||
```sh
|
||||
;> _time:1y | count();
|
||||
executing [_time:1y | stats count(*) as "count(*)"]...
|
||||
{
|
||||
"count(*)": "1923019991"
|
||||
}
|
||||
duration: 0.688s
|
||||
```
|
||||
|
||||
Query execution can be interrupted at any time by pressing `Ctrl+C`.
|
||||
|
||||
Type `q`, `quit` or `exit` and the press `Enter` for exit from `vlogsql`.
|
||||
|
||||
If the query response exceeds vertical screen space, the `vlogsql` pipes query response to `less` utility,
|
||||
so you can scroll the response as needed. This allows executing queries, which potentially
|
||||
may return billions of rows, without any problems at both VictoriaMetrics and `vlogsql` sides,
|
||||
thanks to the way how `less` interacts with [`/select/logsql/query`](https://docs.victoriametrics.com/victorialogs/querying/#querying-logs):
|
||||
|
||||
- `less` reads the response when needed, e.g. when you scroll it down.
|
||||
`less` pauses reading the response when you stop scrolling. VictoriaLogs pauses processing the query
|
||||
when `less` stops reading the response, and automatically resumes processing the response
|
||||
when `less` continues reading it.
|
||||
- `less` closes the response stream after exit from scroll mode (e.g. by typing `q`).
|
||||
VictoriaLogs stops query processing and frees up all the associated resources
|
||||
after the response stream is closed.
|
||||
|
||||
See also [`less` docs](https://man7.org/linux/man-pages/man1/less.1.html) and
|
||||
[command-line integration docs for VictoriaMetrics](https://docs.victoriametrics.com/victorialogs/querying/#command-line).
|
||||
|
||||
|
||||
## Query history
|
||||
|
||||
`vlogsql` supports query history - press `up` and `down` keys for navigating the history.
|
||||
By default the history is stored in the `vlogsql-history` file at the directory where `vlogsql` runs,
|
||||
so the history is available between `vlogsql` runs.
|
||||
The path to the file can be changed via `-historyFile` command-line flag.
|
||||
|
||||
Quick tip: type some text and then press `Ctrl+R` for searching queries with the given text in the history.
|
||||
Press `Ctrl+R` multiple times for searching other matching queries in the history.
|
||||
Press `Enter` when the needed query is found in order to execute it.
|
||||
Press `Ctrl+C` for exit from the `search history` mode.
|
||||
See also [other available shortcuts](https://github.com/chzyer/readline/blob/f533ef1caae91a1fcc90875ff9a5a030f0237c6a/doc/shortcut.md).
|
3
go.mod
3
go.mod
|
@ -18,11 +18,13 @@ require (
|
|||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/cheggaaa/pb/v3 v3.1.5
|
||||
github.com/ergochat/readline v0.1.3
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/snappy v0.0.4
|
||||
github.com/googleapis/gax-go/v2 v2.13.0
|
||||
github.com/influxdata/influxdb v1.11.6
|
||||
github.com/klauspost/compress v1.17.10
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/prometheus/prometheus v0.54.1
|
||||
github.com/urfave/cli/v2 v2.27.4
|
||||
github.com/valyala/fastjson v1.6.4
|
||||
|
@ -87,7 +89,6 @@ require (
|
|||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -182,6 +182,8 @@ github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnv
|
|||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
|
||||
github.com/ergochat/readline v0.1.3 h1:/DytGTmwdUJcLAe3k3VJgowh5vNnsdifYT6uVaf4pSo=
|
||||
github.com/ergochat/readline v0.1.3/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY=
|
||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
|
||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
|
|
|
@ -37,14 +37,28 @@ var (
|
|||
//
|
||||
// There is no need in calling Init from tests.
|
||||
func Init() {
|
||||
initInternal(true)
|
||||
}
|
||||
|
||||
// InitNoLogFlags initializes the logger without writing flags to stdout
|
||||
//
|
||||
// InitNoLogFlags must be called after flag.Parse()
|
||||
func InitNoLogFlags() {
|
||||
initInternal(false)
|
||||
}
|
||||
|
||||
func initInternal(logFlags bool) {
|
||||
setLoggerJSONFields()
|
||||
setLoggerOutput()
|
||||
validateLoggerLevel()
|
||||
validateLoggerFormat()
|
||||
initTimezone()
|
||||
go logLimiterCleaner()
|
||||
|
||||
if logFlags {
|
||||
logAllFlags()
|
||||
}
|
||||
}
|
||||
|
||||
func initTimezone() {
|
||||
tz, err := time.LoadLocation(*loggerTimezone)
|
||||
|
|
|
@ -841,7 +841,7 @@ func parseGenericFilter(lex *lexer, fieldName string) (filter, error) {
|
|||
return f, nil
|
||||
case lex.isKeyword("("):
|
||||
if !lex.isSkippedSpace && !lex.isPrevToken("", ":", "(", "!", "-", "not") {
|
||||
return nil, fmt.Errorf("missing whitespace before the search word %q", lex.prevToken)
|
||||
return nil, fmt.Errorf("missing whitespace after the search word %q", lex.prevToken)
|
||||
}
|
||||
return parseParensFilter(lex, fieldName)
|
||||
case lex.isKeyword(">"):
|
||||
|
|
14
vendor/github.com/ergochat/readline/.check-gofmt.sh
generated
vendored
Normal file
14
vendor/github.com/ergochat/readline/.check-gofmt.sh
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
# exclude vendor/
|
||||
SOURCES="*.go internal/ example/"
|
||||
|
||||
if [ "$1" = "--fix" ]; then
|
||||
exec gofmt -s -w $SOURCES
|
||||
fi
|
||||
|
||||
if [ -n "$(gofmt -s -l $SOURCES)" ]; then
|
||||
echo "Go code is not formatted correctly with \`gofmt -s\`:"
|
||||
gofmt -s -d $SOURCES
|
||||
exit 1
|
||||
fi
|
2
vendor/github.com/ergochat/readline/.gitignore
generated
vendored
Normal file
2
vendor/github.com/ergochat/readline/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.vscode/*
|
||||
*.swp
|
22
vendor/github.com/ergochat/readline/LICENSE
generated
vendored
Normal file
22
vendor/github.com/ergochat/readline/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Chzyer
|
||||
|
||||
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.
|
||||
|
18
vendor/github.com/ergochat/readline/Makefile
generated
vendored
Normal file
18
vendor/github.com/ergochat/readline/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
.PHONY: all test examples ci gofmt
|
||||
|
||||
all: ci
|
||||
|
||||
ci: test examples
|
||||
|
||||
test:
|
||||
go test -race ./...
|
||||
go vet ./...
|
||||
./.check-gofmt.sh
|
||||
|
||||
examples:
|
||||
GOOS=linux go build -o /dev/null example/readline-demo/readline-demo.go
|
||||
GOOS=windows go build -o /dev/null example/readline-demo/readline-demo.go
|
||||
GOOS=darwin go build -o /dev/null example/readline-demo/readline-demo.go
|
||||
|
||||
gofmt:
|
||||
./.check-gofmt.sh --fix
|
47
vendor/github.com/ergochat/readline/README.md
generated
vendored
Normal file
47
vendor/github.com/ergochat/readline/README.md
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
readline
|
||||
========
|
||||
|
||||
[![Godoc](https://godoc.org/github.com/ergochat/readline?status.svg)](https://godoc.org/github.com/ergochat/readline)
|
||||
|
||||
This is a pure Go implementation of functionality comparable to [GNU Readline](https://en.wikipedia.org/wiki/GNU_Readline), i.e. line editing and command history for simple TUI programs.
|
||||
|
||||
It is a fork of [chzyer/readline](https://github.com/chzyer/readline).
|
||||
|
||||
* Relative to the upstream repository, it is actively maintained and has numerous bug fixes
|
||||
- See our [changelog](docs/CHANGELOG.md) for details on fixes and improvements
|
||||
- See our [migration guide](docs/MIGRATING.md) for advice on how to migrate from upstream
|
||||
* Relative to [x/term](https://pkg.go.dev/golang.org/x/term), it has more features (e.g. tab-completion)
|
||||
* In use by multiple projects: [gopass](https://github.com/gopasspw/gopass), [fq](https://github.com/wader/fq), and [ircdog](https://github.com/ergochat/ircdog)
|
||||
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/ergochat/readline"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// see readline.NewFromConfig for advanced options:
|
||||
rl, err := readline.New("> ")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rl.Close()
|
||||
log.SetOutput(rl.Stderr()) // redraw the prompt correctly after log output
|
||||
|
||||
for {
|
||||
line, err := rl.ReadLine()
|
||||
// `err` is either nil, io.EOF, readline.ErrInterrupt, or an unexpected
|
||||
// condition in stdin:
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// `line` is returned without the terminating \n or CRLF:
|
||||
fmt.Fprintf(rl, "you wrote: %s\n", line)
|
||||
}
|
||||
}
|
||||
```
|
499
vendor/github.com/ergochat/readline/complete.go
generated
vendored
Normal file
499
vendor/github.com/ergochat/readline/complete.go
generated
vendored
Normal file
|
@ -0,0 +1,499 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ergochat/readline/internal/platform"
|
||||
"github.com/ergochat/readline/internal/runes"
|
||||
)
|
||||
|
||||
type AutoCompleter interface {
|
||||
// Readline will pass the whole line and current offset to it
|
||||
// Completer need to pass all the candidates, and how long they shared the same characters in line
|
||||
// Example:
|
||||
// [go, git, git-shell, grep]
|
||||
// Do("g", 1) => ["o", "it", "it-shell", "rep"], 1
|
||||
// Do("gi", 2) => ["t", "t-shell"], 2
|
||||
// Do("git", 3) => ["", "-shell"], 3
|
||||
Do(line []rune, pos int) (newLine [][]rune, length int)
|
||||
}
|
||||
|
||||
type opCompleter struct {
|
||||
w *terminal
|
||||
op *operation
|
||||
|
||||
inCompleteMode atomic.Uint32 // this is read asynchronously from wrapWriter
|
||||
inSelectMode bool
|
||||
|
||||
candidate [][]rune // list of candidates
|
||||
candidateSource []rune // buffer string when tab was pressed
|
||||
candidateOff int // num runes in common from buf where candidate start
|
||||
candidateChoice int // absolute index of the chosen candidate (indexing the candidate array which might not all display in current page)
|
||||
candidateColNum int // num columns candidates take 0..wraps, 1 col, 2 cols etc.
|
||||
candidateColWidth int // width of candidate columns
|
||||
linesAvail int // number of lines available below the user's prompt which could be used for rendering the completion
|
||||
pageStartIdx []int // start index in the candidate array on each page (candidatePageStart[i] = absolute idx of the first candidate on page i)
|
||||
curPage int // index of the current page
|
||||
}
|
||||
|
||||
func newOpCompleter(w *terminal, op *operation) *opCompleter {
|
||||
return &opCompleter{
|
||||
w: w,
|
||||
op: op,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opCompleter) doSelect() {
|
||||
if len(o.candidate) == 1 {
|
||||
o.op.buf.WriteRunes(o.candidate[0])
|
||||
o.ExitCompleteMode(false)
|
||||
return
|
||||
}
|
||||
o.nextCandidate()
|
||||
o.CompleteRefresh()
|
||||
}
|
||||
|
||||
// Convert absolute index of the chosen candidate to a page-relative index
|
||||
func (o *opCompleter) candidateChoiceWithinPage() int {
|
||||
return o.candidateChoice - o.pageStartIdx[o.curPage]
|
||||
}
|
||||
|
||||
// Given a page relative index of the chosen candidate, update the absolute index
|
||||
func (o *opCompleter) updateAbsolutechoice(choiceWithinPage int) {
|
||||
o.candidateChoice = choiceWithinPage + o.pageStartIdx[o.curPage]
|
||||
}
|
||||
|
||||
// Move selection to the next candidate, updating page if necessary
|
||||
// Note: we don't allow passing arbitrary offset to this function because, e.g.,
|
||||
// we don't have the 3rd page offset initialized when the user is just seeing the first page,
|
||||
// so we only allow users to navigate into the 2nd page but not to an arbirary page as a result
|
||||
// of calling this method
|
||||
func (o *opCompleter) nextCandidate() {
|
||||
o.candidateChoice = (o.candidateChoice + 1) % len(o.candidate)
|
||||
// Wrapping around
|
||||
if o.candidateChoice == 0 {
|
||||
o.curPage = 0
|
||||
return
|
||||
}
|
||||
// Going to next page
|
||||
if o.candidateChoice == o.pageStartIdx[o.curPage+1] {
|
||||
o.curPage += 1
|
||||
}
|
||||
}
|
||||
|
||||
// Move selection to the next ith col in the current line, wrapping to the line start/end if needed
|
||||
func (o *opCompleter) nextCol(i int) {
|
||||
// If o.candidateColNum == 1 or 0, there is only one col per line and this is a noop
|
||||
if o.candidateColNum > 1 {
|
||||
idxWithinPage := o.candidateChoiceWithinPage()
|
||||
curLine := idxWithinPage / o.candidateColNum
|
||||
offsetInLine := idxWithinPage % o.candidateColNum
|
||||
nextOffset := offsetInLine + i
|
||||
nextOffset %= o.candidateColNum
|
||||
if nextOffset < 0 {
|
||||
nextOffset += o.candidateColNum
|
||||
}
|
||||
|
||||
nextIdxWithinPage := curLine*o.candidateColNum + nextOffset
|
||||
o.updateAbsolutechoice(nextIdxWithinPage)
|
||||
}
|
||||
}
|
||||
|
||||
// Move selection to the line below
|
||||
func (o *opCompleter) nextLine() {
|
||||
colNum := 1
|
||||
if o.candidateColNum > 1 {
|
||||
colNum = o.candidateColNum
|
||||
}
|
||||
|
||||
idxWithinPage := o.candidateChoiceWithinPage()
|
||||
|
||||
idxWithinPage += colNum
|
||||
if idxWithinPage >= o.getMatrixSize() {
|
||||
idxWithinPage -= o.getMatrixSize()
|
||||
} else if idxWithinPage >= o.numCandidateCurPage() {
|
||||
idxWithinPage += colNum
|
||||
idxWithinPage -= o.getMatrixSize()
|
||||
}
|
||||
|
||||
o.updateAbsolutechoice(idxWithinPage)
|
||||
}
|
||||
|
||||
// Move selection to the line above
|
||||
func (o *opCompleter) prevLine() {
|
||||
colNum := 1
|
||||
if o.candidateColNum > 1 {
|
||||
colNum = o.candidateColNum
|
||||
}
|
||||
|
||||
idxWithinPage := o.candidateChoiceWithinPage()
|
||||
|
||||
idxWithinPage -= colNum
|
||||
if idxWithinPage < 0 {
|
||||
idxWithinPage += o.getMatrixSize()
|
||||
if idxWithinPage >= o.numCandidateCurPage() {
|
||||
idxWithinPage -= colNum
|
||||
}
|
||||
}
|
||||
|
||||
o.updateAbsolutechoice(idxWithinPage)
|
||||
}
|
||||
|
||||
// Move selection to the start of the current line
|
||||
func (o *opCompleter) lineStart() {
|
||||
if o.candidateColNum > 1 {
|
||||
idxWithinPage := o.candidateChoiceWithinPage()
|
||||
lineOffset := idxWithinPage % o.candidateColNum
|
||||
idxWithinPage -= lineOffset
|
||||
o.updateAbsolutechoice(idxWithinPage)
|
||||
}
|
||||
}
|
||||
|
||||
// Move selection to the end of the current line
|
||||
func (o *opCompleter) lineEnd() {
|
||||
if o.candidateColNum > 1 {
|
||||
idxWithinPage := o.candidateChoiceWithinPage()
|
||||
offsetToLineEnd := o.candidateColNum - idxWithinPage%o.candidateColNum - 1
|
||||
idxWithinPage += offsetToLineEnd
|
||||
o.updateAbsolutechoice(idxWithinPage)
|
||||
if o.candidateChoice >= len(o.candidate) {
|
||||
o.candidateChoice = len(o.candidate) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the next page if possible, returning selection to the first item in the page
|
||||
func (o *opCompleter) nextPage() {
|
||||
// Check that this is not the last page already
|
||||
nextPageStart := o.pageStartIdx[o.curPage+1]
|
||||
if nextPageStart < len(o.candidate) {
|
||||
o.curPage += 1
|
||||
o.candidateChoice = o.pageStartIdx[o.curPage]
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the previous page if possible, returning selection to the first item in the page
|
||||
func (o *opCompleter) prevPage() {
|
||||
if o.curPage > 0 {
|
||||
o.curPage -= 1
|
||||
o.candidateChoice = o.pageStartIdx[o.curPage]
|
||||
}
|
||||
}
|
||||
|
||||
// OnComplete returns true if complete mode is available. Used to ring bell
|
||||
// when tab pressed if cannot do complete for reason such as width unknown
|
||||
// or no candidates available.
|
||||
func (o *opCompleter) OnComplete() (ringBell bool) {
|
||||
tWidth, tHeight := o.w.GetWidthHeight()
|
||||
if tWidth == 0 || tHeight < 3 {
|
||||
return false
|
||||
}
|
||||
if o.IsInCompleteSelectMode() {
|
||||
o.doSelect()
|
||||
return true
|
||||
}
|
||||
|
||||
buf := o.op.buf
|
||||
rs := buf.Runes()
|
||||
|
||||
// If in complete mode and nothing else typed then we must be entering select mode
|
||||
if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) {
|
||||
if len(o.candidate) > 1 {
|
||||
same, size := runes.Aggregate(o.candidate)
|
||||
if size > 0 {
|
||||
buf.WriteRunes(same)
|
||||
o.ExitCompleteMode(false)
|
||||
return false // partial completion so ring the bell
|
||||
}
|
||||
}
|
||||
o.EnterCompleteSelectMode()
|
||||
o.doSelect()
|
||||
return true
|
||||
}
|
||||
|
||||
newLines, offset := o.op.GetConfig().AutoComplete.Do(rs, buf.idx)
|
||||
if len(newLines) == 0 || (len(newLines) == 1 && len(newLines[0]) == 0) {
|
||||
o.ExitCompleteMode(false)
|
||||
return false // will ring bell on initial tab press
|
||||
}
|
||||
if o.candidateOff > offset {
|
||||
// part of buffer we are completing has changed. Example might be that we were completing "ls" and
|
||||
// user typed space so we are no longer completing "ls" but now we are completing an argument of
|
||||
// the ls command. Instead of continuing in complete mode, we exit.
|
||||
o.ExitCompleteMode(false)
|
||||
return true
|
||||
}
|
||||
o.candidateSource = rs
|
||||
|
||||
// only Aggregate candidates in non-complete mode
|
||||
if !o.IsInCompleteMode() {
|
||||
if len(newLines) == 1 {
|
||||
// not yet in complete mode but only 1 candidate so complete it
|
||||
buf.WriteRunes(newLines[0])
|
||||
o.ExitCompleteMode(false)
|
||||
return true
|
||||
}
|
||||
|
||||
// check if all candidates have common prefix and return it and its size
|
||||
same, size := runes.Aggregate(newLines)
|
||||
if size > 0 {
|
||||
buf.WriteRunes(same)
|
||||
o.ExitCompleteMode(false)
|
||||
return false // partial completion so ring the bell
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, we just enter complete mode (which does a refresh)
|
||||
o.EnterCompleteMode(offset, newLines)
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *opCompleter) IsInCompleteSelectMode() bool {
|
||||
return o.inSelectMode
|
||||
}
|
||||
|
||||
func (o *opCompleter) IsInCompleteMode() bool {
|
||||
return o.inCompleteMode.Load() == 1
|
||||
}
|
||||
|
||||
func (o *opCompleter) HandleCompleteSelect(r rune) (stayInMode bool) {
|
||||
next := true
|
||||
switch r {
|
||||
case CharEnter, CharCtrlJ:
|
||||
next = false
|
||||
o.op.buf.WriteRunes(o.candidate[o.candidateChoice])
|
||||
o.ExitCompleteMode(false)
|
||||
case CharLineStart:
|
||||
o.lineStart()
|
||||
case CharLineEnd:
|
||||
o.lineEnd()
|
||||
case CharBackspace:
|
||||
o.ExitCompleteSelectMode()
|
||||
next = false
|
||||
case CharTab:
|
||||
o.nextCandidate()
|
||||
case CharForward:
|
||||
o.nextCol(1)
|
||||
case CharBell, CharInterrupt:
|
||||
o.ExitCompleteMode(true)
|
||||
next = false
|
||||
case CharNext:
|
||||
o.nextLine()
|
||||
case CharBackward, MetaShiftTab:
|
||||
o.nextCol(-1)
|
||||
case CharPrev:
|
||||
o.prevLine()
|
||||
case 'j', 'J':
|
||||
o.prevPage()
|
||||
case 'k', 'K':
|
||||
o.nextPage()
|
||||
default:
|
||||
next = false
|
||||
o.ExitCompleteSelectMode()
|
||||
}
|
||||
if next {
|
||||
o.CompleteRefresh()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *opCompleter) getMatrixSize() int {
|
||||
colNum := 1
|
||||
if o.candidateColNum > 1 {
|
||||
colNum = o.candidateColNum
|
||||
}
|
||||
line := o.getMatrixNumRows()
|
||||
return line * colNum
|
||||
}
|
||||
|
||||
// Number of candidate that could fit on current page
|
||||
func (o *opCompleter) numCandidateCurPage() int {
|
||||
// Safety: we will always render the first page, and whenever we finished rendering page i,
|
||||
// we always populate o.candidatePageStart through at least i + 1, so when this is called, we
|
||||
// always know the start of the next page
|
||||
return o.pageStartIdx[o.curPage+1] - o.pageStartIdx[o.curPage]
|
||||
}
|
||||
|
||||
// Get number of rows of current page viewed as a matrix of candidates
|
||||
func (o *opCompleter) getMatrixNumRows() int {
|
||||
candidateCurPage := o.numCandidateCurPage()
|
||||
// Normal case where there is no wrap
|
||||
if o.candidateColNum > 1 {
|
||||
numLine := candidateCurPage / o.candidateColNum
|
||||
if candidateCurPage%o.candidateColNum != 0 {
|
||||
numLine++
|
||||
}
|
||||
return numLine
|
||||
}
|
||||
|
||||
// Now since there are wraps, each candidate will be put on its own line, so the number of lines is just the number of candidate
|
||||
return candidateCurPage
|
||||
}
|
||||
|
||||
// setColumnInfo calculates column width and number of columns required
|
||||
// to present the list of candidates on the terminal.
|
||||
func (o *opCompleter) setColumnInfo() {
|
||||
same := o.op.buf.RuneSlice(-o.candidateOff)
|
||||
sameWidth := runes.WidthAll(same)
|
||||
|
||||
colWidth := 0
|
||||
for _, c := range o.candidate {
|
||||
w := sameWidth + runes.WidthAll(c)
|
||||
if w > colWidth {
|
||||
colWidth = w
|
||||
}
|
||||
}
|
||||
colWidth++ // whitespace between cols
|
||||
|
||||
tWidth, _ := o.w.GetWidthHeight()
|
||||
|
||||
// -1 to avoid end of line issues
|
||||
width := tWidth - 1
|
||||
colNum := width / colWidth
|
||||
if colNum != 0 {
|
||||
colWidth += (width - (colWidth * colNum)) / colNum
|
||||
}
|
||||
|
||||
o.candidateColNum = colNum
|
||||
o.candidateColWidth = colWidth
|
||||
}
|
||||
|
||||
// CompleteRefresh is used for completemode and selectmode
|
||||
func (o *opCompleter) CompleteRefresh() {
|
||||
if !o.IsInCompleteMode() {
|
||||
return
|
||||
}
|
||||
|
||||
buf := bufio.NewWriter(o.w)
|
||||
// calculate num lines from cursor pos to where choices should be written
|
||||
lineCnt := o.op.buf.CursorLineCount()
|
||||
buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) // move down from cursor to start of candidates
|
||||
buf.WriteString("\033[J")
|
||||
|
||||
same := o.op.buf.RuneSlice(-o.candidateOff)
|
||||
tWidth, _ := o.w.GetWidthHeight()
|
||||
|
||||
colIdx := 0
|
||||
lines := 0
|
||||
sameWidth := runes.WidthAll(same)
|
||||
|
||||
// Show completions for the current page
|
||||
idx := o.pageStartIdx[o.curPage]
|
||||
for ; idx < len(o.candidate); idx++ {
|
||||
// If writing the current candidate would overflow the page,
|
||||
// we know that it is the start of the next page.
|
||||
if colIdx == 0 && lines == o.linesAvail {
|
||||
if o.curPage == len(o.pageStartIdx)-1 {
|
||||
o.pageStartIdx = append(o.pageStartIdx, idx)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
c := o.candidate[idx]
|
||||
inSelect := idx == o.candidateChoice && o.IsInCompleteSelectMode()
|
||||
cWidth := sameWidth + runes.WidthAll(c)
|
||||
cLines := 1
|
||||
if tWidth > 0 {
|
||||
sWidth := 0
|
||||
if platform.IsWindows && inSelect {
|
||||
sWidth = 1 // adjust for hightlighting on Windows
|
||||
}
|
||||
cLines = (cWidth + sWidth) / tWidth
|
||||
if (cWidth+sWidth)%tWidth > 0 {
|
||||
cLines++
|
||||
}
|
||||
}
|
||||
|
||||
if lines > 0 && colIdx == 0 {
|
||||
// After line 1, if we're printing to the first column
|
||||
// goto a new line. We do it here, instead of at the end
|
||||
// of the loop, to avoid the last \n taking up a blank
|
||||
// line at the end and stealing realestate.
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
|
||||
if inSelect {
|
||||
buf.WriteString("\033[30;47m")
|
||||
}
|
||||
|
||||
buf.WriteString(string(same))
|
||||
buf.WriteString(string(c))
|
||||
if o.candidateColNum >= 1 {
|
||||
// only output spaces between columns if everything fits
|
||||
buf.Write(bytes.Repeat([]byte(" "), o.candidateColWidth-cWidth))
|
||||
}
|
||||
|
||||
if inSelect {
|
||||
buf.WriteString("\033[0m")
|
||||
}
|
||||
|
||||
colIdx++
|
||||
if colIdx >= o.candidateColNum {
|
||||
lines += cLines
|
||||
colIdx = 0
|
||||
if platform.IsWindows {
|
||||
// Windows EOL edge-case.
|
||||
buf.WriteString("\b")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if idx == len(o.candidate) {
|
||||
// Book-keeping for the last page.
|
||||
o.pageStartIdx = append(o.pageStartIdx, len(o.candidate))
|
||||
}
|
||||
|
||||
if colIdx > 0 {
|
||||
lines++ // mid-line so count it.
|
||||
}
|
||||
|
||||
// Show the guidance if there are more pages
|
||||
if idx != len(o.candidate) || o.curPage > 0 {
|
||||
buf.WriteString("\n-- (j: prev page) (k: next page) --")
|
||||
lines++
|
||||
}
|
||||
|
||||
// wrote out choices over "lines", move back to cursor (positioned at index)
|
||||
fmt.Fprintf(buf, "\033[%dA", lines)
|
||||
buf.Write(o.op.buf.getBackspaceSequence())
|
||||
buf.Flush()
|
||||
}
|
||||
|
||||
func (o *opCompleter) EnterCompleteSelectMode() {
|
||||
o.inSelectMode = true
|
||||
o.candidateChoice = -1
|
||||
}
|
||||
|
||||
func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) {
|
||||
o.inCompleteMode.Store(1)
|
||||
o.candidate = candidate
|
||||
o.candidateOff = offset
|
||||
o.setColumnInfo()
|
||||
o.initPage()
|
||||
o.CompleteRefresh()
|
||||
}
|
||||
|
||||
func (o *opCompleter) initPage() {
|
||||
_, tHeight := o.w.GetWidthHeight()
|
||||
buflineCnt := o.op.buf.LineCount() // lines taken by buffer content
|
||||
o.linesAvail = tHeight - buflineCnt - 1 // lines available without scrolling buffer off screen, reserve one line for the guidance message
|
||||
o.pageStartIdx = []int{0} // first page always start at 0
|
||||
o.curPage = 0
|
||||
}
|
||||
|
||||
func (o *opCompleter) ExitCompleteSelectMode() {
|
||||
o.inSelectMode = false
|
||||
o.candidateChoice = -1
|
||||
}
|
||||
|
||||
func (o *opCompleter) ExitCompleteMode(revent bool) {
|
||||
o.inCompleteMode.Store(0)
|
||||
o.candidate = nil
|
||||
o.candidateOff = -1
|
||||
o.candidateSource = nil
|
||||
o.ExitCompleteSelectMode()
|
||||
}
|
156
vendor/github.com/ergochat/readline/complete_helper.go
generated
vendored
Normal file
156
vendor/github.com/ergochat/readline/complete_helper.go
generated
vendored
Normal file
|
@ -0,0 +1,156 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/ergochat/readline/internal/runes"
|
||||
)
|
||||
|
||||
// PrefixCompleter implements AutoCompleter via a recursive tree.
|
||||
type PrefixCompleter struct {
|
||||
// Name is the name of a command, subcommand, or argument eligible for completion.
|
||||
Name string
|
||||
// Callback is optional; if defined, it takes the current line and returns
|
||||
// a list of possible completions associated with the current node (i.e.
|
||||
// in place of Name).
|
||||
Callback func(string) []string
|
||||
// Children is a list of possible completions that can follow the current node.
|
||||
Children []*PrefixCompleter
|
||||
|
||||
nameRunes []rune // just a cache
|
||||
}
|
||||
|
||||
var _ AutoCompleter = (*PrefixCompleter)(nil)
|
||||
|
||||
func (p *PrefixCompleter) Tree(prefix string) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
p.print(prefix, 0, buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func prefixPrint(p *PrefixCompleter, prefix string, level int, buf *bytes.Buffer) {
|
||||
if strings.TrimSpace(p.Name) != "" {
|
||||
buf.WriteString(prefix)
|
||||
if level > 0 {
|
||||
buf.WriteString("├")
|
||||
buf.WriteString(strings.Repeat("─", (level*4)-2))
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
buf.WriteString(p.Name)
|
||||
buf.WriteByte('\n')
|
||||
level++
|
||||
}
|
||||
for _, ch := range p.Children {
|
||||
ch.print(prefix, level, buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) print(prefix string, level int, buf *bytes.Buffer) {
|
||||
prefixPrint(p, prefix, level, buf)
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) getName() []rune {
|
||||
if p.nameRunes == nil {
|
||||
if p.Name != "" {
|
||||
p.nameRunes = []rune(p.Name)
|
||||
} else {
|
||||
p.nameRunes = make([]rune, 0)
|
||||
}
|
||||
}
|
||||
return p.nameRunes
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) getDynamicNames(line []rune) [][]rune {
|
||||
var result [][]rune
|
||||
for _, name := range p.Callback(string(line)) {
|
||||
nameRunes := []rune(name)
|
||||
nameRunes = append(nameRunes, ' ')
|
||||
result = append(result, nameRunes)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) SetChildren(children []*PrefixCompleter) {
|
||||
p.Children = children
|
||||
}
|
||||
|
||||
func NewPrefixCompleter(pc ...*PrefixCompleter) *PrefixCompleter {
|
||||
return PcItem("", pc...)
|
||||
}
|
||||
|
||||
func PcItem(name string, pc ...*PrefixCompleter) *PrefixCompleter {
|
||||
name += " "
|
||||
result := &PrefixCompleter{
|
||||
Name: name,
|
||||
Children: pc,
|
||||
}
|
||||
result.getName() // initialize nameRunes member
|
||||
return result
|
||||
}
|
||||
|
||||
func PcItemDynamic(callback func(string) []string, pc ...*PrefixCompleter) *PrefixCompleter {
|
||||
return &PrefixCompleter{
|
||||
Callback: callback,
|
||||
Children: pc,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) {
|
||||
return doInternal(p, line, pos, line)
|
||||
}
|
||||
|
||||
func doInternal(p *PrefixCompleter, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) {
|
||||
line = runes.TrimSpaceLeft(line[:pos])
|
||||
goNext := false
|
||||
var lineCompleter *PrefixCompleter
|
||||
for _, child := range p.Children {
|
||||
var childNames [][]rune
|
||||
if child.Callback != nil {
|
||||
childNames = child.getDynamicNames(origLine)
|
||||
} else {
|
||||
childNames = make([][]rune, 1)
|
||||
childNames[0] = child.getName()
|
||||
}
|
||||
|
||||
for _, childName := range childNames {
|
||||
if len(line) >= len(childName) {
|
||||
if runes.HasPrefix(line, childName) {
|
||||
if len(line) == len(childName) {
|
||||
newLine = append(newLine, []rune{' '})
|
||||
} else {
|
||||
newLine = append(newLine, childName)
|
||||
}
|
||||
offset = len(childName)
|
||||
lineCompleter = child
|
||||
goNext = true
|
||||
}
|
||||
} else {
|
||||
if runes.HasPrefix(childName, line) {
|
||||
newLine = append(newLine, childName[len(line):])
|
||||
offset = len(line)
|
||||
lineCompleter = child
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(newLine) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
tmpLine := make([]rune, 0, len(line))
|
||||
for i := offset; i < len(line); i++ {
|
||||
if line[i] == ' ' {
|
||||
continue
|
||||
}
|
||||
|
||||
tmpLine = append(tmpLine, line[i:]...)
|
||||
return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine)
|
||||
}
|
||||
|
||||
if goNext {
|
||||
return doInternal(lineCompleter, nil, 0, origLine)
|
||||
}
|
||||
return
|
||||
}
|
330
vendor/github.com/ergochat/readline/history.go
generated
vendored
Normal file
330
vendor/github.com/ergochat/readline/history.go
generated
vendored
Normal file
|
@ -0,0 +1,330 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ergochat/readline/internal/runes"
|
||||
)
|
||||
|
||||
type hisItem struct {
|
||||
Source []rune
|
||||
Version int64
|
||||
Tmp []rune
|
||||
}
|
||||
|
||||
func (h *hisItem) Clean() {
|
||||
h.Source = nil
|
||||
h.Tmp = nil
|
||||
}
|
||||
|
||||
type opHistory struct {
|
||||
operation *operation
|
||||
history *list.List
|
||||
historyVer int64
|
||||
current *list.Element
|
||||
fd *os.File
|
||||
fdLock sync.Mutex
|
||||
enable bool
|
||||
}
|
||||
|
||||
func newOpHistory(operation *operation) (o *opHistory) {
|
||||
o = &opHistory{
|
||||
operation: operation,
|
||||
history: list.New(),
|
||||
enable: true,
|
||||
}
|
||||
o.initHistory()
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *opHistory) isEnabled() bool {
|
||||
return o.enable && o.operation.GetConfig().HistoryLimit > 0
|
||||
}
|
||||
|
||||
func (o *opHistory) Reset() {
|
||||
o.history = list.New()
|
||||
o.current = nil
|
||||
}
|
||||
|
||||
func (o *opHistory) initHistory() {
|
||||
cfg := o.operation.GetConfig()
|
||||
if cfg.HistoryFile != "" {
|
||||
o.historyUpdatePath(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
// only called by newOpHistory
|
||||
func (o *opHistory) historyUpdatePath(cfg *Config) {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
f, err := os.OpenFile(cfg.HistoryFile, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
o.fd = f
|
||||
r := bufio.NewReader(o.fd)
|
||||
total := 0
|
||||
for ; ; total++ {
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// ignore the empty line
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
o.Push([]rune(line))
|
||||
o.Compact()
|
||||
}
|
||||
if total > cfg.HistoryLimit {
|
||||
o.rewriteLocked()
|
||||
}
|
||||
o.historyVer++
|
||||
o.Push(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opHistory) Compact() {
|
||||
cfg := o.operation.GetConfig()
|
||||
for o.history.Len() > cfg.HistoryLimit && o.history.Len() > 0 {
|
||||
o.history.Remove(o.history.Front())
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opHistory) Rewrite() {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
o.rewriteLocked()
|
||||
}
|
||||
|
||||
func (o *opHistory) rewriteLocked() {
|
||||
cfg := o.operation.GetConfig()
|
||||
if cfg.HistoryFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
tmpFile := cfg.HistoryFile + ".tmp"
|
||||
fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf := bufio.NewWriter(fd)
|
||||
for elem := o.history.Front(); elem != nil; elem = elem.Next() {
|
||||
buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n")
|
||||
}
|
||||
buf.Flush()
|
||||
|
||||
// replace history file
|
||||
if err = os.Rename(tmpFile, cfg.HistoryFile); err != nil {
|
||||
fd.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if o.fd != nil {
|
||||
o.fd.Close()
|
||||
}
|
||||
// fd is write only, just satisfy what we need.
|
||||
o.fd = fd
|
||||
}
|
||||
|
||||
func (o *opHistory) Close() {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
if o.fd != nil {
|
||||
o.fd.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) {
|
||||
for elem := o.current; elem != nil; elem = elem.Prev() {
|
||||
item := o.showItem(elem.Value)
|
||||
if isNewSearch {
|
||||
start += len(rs)
|
||||
}
|
||||
if elem == o.current {
|
||||
if len(item) >= start {
|
||||
item = item[:start]
|
||||
}
|
||||
}
|
||||
idx := runes.IndexAllBckEx(item, rs, o.operation.GetConfig().HistorySearchFold)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
return idx, elem
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) {
|
||||
for elem := o.current; elem != nil; elem = elem.Next() {
|
||||
item := o.showItem(elem.Value)
|
||||
if isNewSearch {
|
||||
start -= len(rs)
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
}
|
||||
if elem == o.current {
|
||||
if len(item)-1 >= start {
|
||||
item = item[start:]
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
idx := runes.IndexAllEx(item, rs, o.operation.GetConfig().HistorySearchFold)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
if elem == o.current {
|
||||
idx += start
|
||||
}
|
||||
return idx, elem
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func (o *opHistory) showItem(obj interface{}) []rune {
|
||||
item := obj.(*hisItem)
|
||||
if item.Version == o.historyVer {
|
||||
return item.Tmp
|
||||
}
|
||||
return item.Source
|
||||
}
|
||||
|
||||
func (o *opHistory) Prev() []rune {
|
||||
if o.current == nil {
|
||||
return nil
|
||||
}
|
||||
current := o.current.Prev()
|
||||
if current == nil {
|
||||
return nil
|
||||
}
|
||||
o.current = current
|
||||
return runes.Copy(o.showItem(current.Value))
|
||||
}
|
||||
|
||||
func (o *opHistory) Next() ([]rune, bool) {
|
||||
if o.current == nil {
|
||||
return nil, false
|
||||
}
|
||||
current := o.current.Next()
|
||||
if current == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
o.current = current
|
||||
return runes.Copy(o.showItem(current.Value)), true
|
||||
}
|
||||
|
||||
// Disable the current history
|
||||
func (o *opHistory) Disable() {
|
||||
o.enable = false
|
||||
}
|
||||
|
||||
// Enable the current history
|
||||
func (o *opHistory) Enable() {
|
||||
o.enable = true
|
||||
}
|
||||
|
||||
func (o *opHistory) debug() {
|
||||
debugPrint("-------")
|
||||
for item := o.history.Front(); item != nil; item = item.Next() {
|
||||
debugPrint(fmt.Sprintf("%+v", item.Value))
|
||||
}
|
||||
}
|
||||
|
||||
// save history
|
||||
func (o *opHistory) New(current []rune) (err error) {
|
||||
if !o.isEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
current = runes.Copy(current)
|
||||
|
||||
// if just use last command without modify
|
||||
// just clean lastest history
|
||||
if back := o.history.Back(); back != nil {
|
||||
prev := back.Prev()
|
||||
if prev != nil {
|
||||
if runes.Equal(current, prev.Value.(*hisItem).Source) {
|
||||
o.current = o.history.Back()
|
||||
o.current.Value.(*hisItem).Clean()
|
||||
o.historyVer++
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(current) == 0 {
|
||||
o.current = o.history.Back()
|
||||
if o.current != nil {
|
||||
o.current.Value.(*hisItem).Clean()
|
||||
o.historyVer++
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if o.current != o.history.Back() {
|
||||
// move history item to current command
|
||||
currentItem := o.current.Value.(*hisItem)
|
||||
// set current to last item
|
||||
o.current = o.history.Back()
|
||||
|
||||
current = runes.Copy(currentItem.Tmp)
|
||||
}
|
||||
|
||||
// err only can be a IO error, just report
|
||||
err = o.Update(current, true)
|
||||
|
||||
// push a new one to commit current command
|
||||
o.historyVer++
|
||||
o.Push(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opHistory) Revert() {
|
||||
o.historyVer++
|
||||
o.current = o.history.Back()
|
||||
}
|
||||
|
||||
func (o *opHistory) Update(s []rune, commit bool) (err error) {
|
||||
if !o.isEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
s = runes.Copy(s)
|
||||
if o.current == nil {
|
||||
o.Push(s)
|
||||
o.Compact()
|
||||
return
|
||||
}
|
||||
r := o.current.Value.(*hisItem)
|
||||
r.Version = o.historyVer
|
||||
if commit {
|
||||
r.Source = s
|
||||
if o.fd != nil {
|
||||
// just report the error
|
||||
_, err = o.fd.Write([]byte(string(r.Source) + "\n"))
|
||||
}
|
||||
} else {
|
||||
r.Tmp = append(r.Tmp[:0], s...)
|
||||
}
|
||||
o.current.Value = r
|
||||
o.Compact()
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opHistory) Push(s []rune) {
|
||||
s = runes.Copy(s)
|
||||
elem := o.history.PushBack(&hisItem{Source: s})
|
||||
o.current = elem
|
||||
}
|
7
vendor/github.com/ergochat/readline/internal/ansi/ansi.go
generated
vendored
Normal file
7
vendor/github.com/ergochat/readline/internal/ansi/ansi.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
//go:build !windows
|
||||
|
||||
package ansi
|
||||
|
||||
func EnableANSI() error {
|
||||
return nil
|
||||
}
|
85
vendor/github.com/ergochat/readline/internal/ansi/ansi_windows.go
generated
vendored
Normal file
85
vendor/github.com/ergochat/readline/internal/ansi/ansi_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
//go:build windows
|
||||
|
||||
/*
|
||||
Copyright (c) Jason Walton <dev@lucid.thedremaing.org> (https://www.thedreaming.org)
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
|
||||
Released under the MIT 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.
|
||||
*/
|
||||
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
ansiErr error
|
||||
ansiOnce sync.Once
|
||||
)
|
||||
|
||||
func EnableANSI() error {
|
||||
ansiOnce.Do(func() {
|
||||
ansiErr = realEnableANSI()
|
||||
})
|
||||
return ansiErr
|
||||
}
|
||||
|
||||
func realEnableANSI() error {
|
||||
// We want to enable the following modes, if they are not already set:
|
||||
// ENABLE_VIRTUAL_TERMINAL_PROCESSING on stdout (color support)
|
||||
// ENABLE_VIRTUAL_TERMINAL_INPUT on stdin (ansi input sequences)
|
||||
// See https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
|
||||
if err := windowsSetMode(windows.STD_OUTPUT_HANDLE, windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := windowsSetMode(windows.STD_INPUT_HANDLE, windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func windowsSetMode(stdhandle uint32, modeFlag uint32) (err error) {
|
||||
handle, err := windows.GetStdHandle(stdhandle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the existing console mode.
|
||||
var mode uint32
|
||||
err = windows.GetConsoleMode(handle, &mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Enable the mode if it is not currently set
|
||||
if mode&modeFlag != modeFlag {
|
||||
mode = mode | modeFlag
|
||||
err = windows.SetConsoleMode(handle, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
83
vendor/github.com/ergochat/readline/internal/platform/utils_unix.go
generated
vendored
Normal file
83
vendor/github.com/ergochat/readline/internal/platform/utils_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
//go:build aix || darwin || dragonfly || freebsd || (linux && !appengine) || netbsd || openbsd || os400 || solaris || zos
|
||||
|
||||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/ergochat/readline/internal/term"
|
||||
)
|
||||
|
||||
const (
|
||||
IsWindows = false
|
||||
)
|
||||
|
||||
// SuspendProcess suspends the process with SIGTSTP,
|
||||
// then blocks until it is resumed.
|
||||
func SuspendProcess() {
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGCONT)
|
||||
defer stop()
|
||||
|
||||
p, err := os.FindProcess(os.Getpid())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.Signal(syscall.SIGTSTP)
|
||||
// wait for SIGCONT
|
||||
<-ctx.Done()
|
||||
}
|
||||
|
||||
// getWidthHeight of the terminal using given file descriptor
|
||||
func getWidthHeight(stdoutFd int) (width int, height int) {
|
||||
width, height, err := term.GetSize(stdoutFd)
|
||||
if err != nil {
|
||||
return -1, -1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetScreenSize returns the width/height of the terminal or -1,-1 or error
|
||||
func GetScreenSize() (width int, height int) {
|
||||
width, height = getWidthHeight(syscall.Stdout)
|
||||
if width < 0 {
|
||||
width, height = getWidthHeight(syscall.Stderr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func DefaultIsTerminal() bool {
|
||||
return term.IsTerminal(syscall.Stdin) && (term.IsTerminal(syscall.Stdout) || term.IsTerminal(syscall.Stderr))
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
sizeChange sync.Once
|
||||
sizeChangeCallback func()
|
||||
)
|
||||
|
||||
func DefaultOnWidthChanged(f func()) {
|
||||
DefaultOnSizeChanged(f)
|
||||
}
|
||||
|
||||
func DefaultOnSizeChanged(f func()) {
|
||||
sizeChangeCallback = f
|
||||
sizeChange.Do(func() {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGWINCH)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
_, ok := <-ch
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
sizeChangeCallback()
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
38
vendor/github.com/ergochat/readline/internal/platform/utils_windows.go
generated
vendored
Normal file
38
vendor/github.com/ergochat/readline/internal/platform/utils_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
//go:build windows
|
||||
|
||||
package platform
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/ergochat/readline/internal/term"
|
||||
)
|
||||
|
||||
const (
|
||||
IsWindows = true
|
||||
)
|
||||
|
||||
func SuspendProcess() {
|
||||
}
|
||||
|
||||
// GetScreenSize returns the width, height of the terminal or -1,-1
|
||||
func GetScreenSize() (width int, height int) {
|
||||
width, height, err := term.GetSize(int(syscall.Stdout))
|
||||
if err == nil {
|
||||
return width, height
|
||||
} else {
|
||||
return 0, 0
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultIsTerminal() bool {
|
||||
return term.IsTerminal(int(syscall.Stdin)) && term.IsTerminal(int(syscall.Stdout))
|
||||
}
|
||||
|
||||
func DefaultOnWidthChanged(f func()) {
|
||||
DefaultOnSizeChanged(f)
|
||||
}
|
||||
|
||||
func DefaultOnSizeChanged(f func()) {
|
||||
// TODO: does Windows have a SIGWINCH analogue?
|
||||
}
|
222
vendor/github.com/ergochat/readline/internal/ringbuf/ringbuf.go
generated
vendored
Normal file
222
vendor/github.com/ergochat/readline/internal/ringbuf/ringbuf.go
generated
vendored
Normal file
|
@ -0,0 +1,222 @@
|
|||
// Copyright (c) 2023 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
||||
// released under the MIT license
|
||||
|
||||
package ringbuf
|
||||
|
||||
type Buffer[T any] struct {
|
||||
// three possible states:
|
||||
// empty: start == end == -1
|
||||
// partially full: start != end
|
||||
// full: start == end > 0
|
||||
// if entries exist, they go from `start` to `(end - 1) % length`
|
||||
buffer []T
|
||||
start int
|
||||
end int
|
||||
maximumSize int
|
||||
}
|
||||
|
||||
func NewExpandableBuffer[T any](initialSize, maximumSize int) (result *Buffer[T]) {
|
||||
result = new(Buffer[T])
|
||||
result.Initialize(initialSize, maximumSize)
|
||||
return
|
||||
}
|
||||
|
||||
func (hist *Buffer[T]) Initialize(initialSize, maximumSize int) {
|
||||
if maximumSize == 0 {
|
||||
panic("maximum size cannot be 0")
|
||||
}
|
||||
hist.buffer = make([]T, initialSize)
|
||||
hist.start = -1
|
||||
hist.end = -1
|
||||
hist.maximumSize = maximumSize
|
||||
}
|
||||
|
||||
// Add adds an item to the buffer
|
||||
func (list *Buffer[T]) Add(item T) {
|
||||
list.maybeExpand()
|
||||
|
||||
var pos int
|
||||
if list.start == -1 { // empty
|
||||
pos = 0
|
||||
list.start = 0
|
||||
list.end = 1 % len(list.buffer)
|
||||
} else if list.start != list.end { // partially full
|
||||
pos = list.end
|
||||
list.end = (list.end + 1) % len(list.buffer)
|
||||
} else if list.start == list.end { // full
|
||||
pos = list.end
|
||||
list.end = (list.end + 1) % len(list.buffer)
|
||||
list.start = list.end // advance start as well, overwriting first entry
|
||||
}
|
||||
|
||||
list.buffer[pos] = item
|
||||
}
|
||||
|
||||
func (list *Buffer[T]) Pop() (item T, success bool) {
|
||||
length := list.Length()
|
||||
if length == 0 {
|
||||
return item, false
|
||||
} else {
|
||||
pos := list.prev(list.end)
|
||||
item = list.buffer[pos]
|
||||
list.buffer[pos] = *new(T) // TODO verify that this doesn't allocate
|
||||
if length > 1 {
|
||||
list.end = pos
|
||||
} else {
|
||||
// reset to empty buffer
|
||||
list.start = -1
|
||||
list.end = -1
|
||||
}
|
||||
return item, true
|
||||
}
|
||||
}
|
||||
|
||||
func (list *Buffer[T]) Range(ascending bool, rangeFunction func(item *T) (stopIteration bool)) {
|
||||
if list.start == -1 || len(list.buffer) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var pos, stop int
|
||||
if ascending {
|
||||
pos = list.start
|
||||
stop = list.prev(list.end)
|
||||
} else {
|
||||
pos = list.prev(list.end)
|
||||
stop = list.start
|
||||
}
|
||||
|
||||
for {
|
||||
if shouldStop := rangeFunction(&list.buffer[pos]); shouldStop {
|
||||
return
|
||||
}
|
||||
|
||||
if pos == stop {
|
||||
return
|
||||
}
|
||||
|
||||
if ascending {
|
||||
pos = list.next(pos)
|
||||
} else {
|
||||
pos = list.prev(pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Predicate[T any] func(item *T) (matches bool)
|
||||
|
||||
func (list *Buffer[T]) Match(ascending bool, predicate Predicate[T], limit int) []T {
|
||||
var results []T
|
||||
rangeFunc := func(item *T) (stopIteration bool) {
|
||||
if predicate(item) {
|
||||
results = append(results, *item)
|
||||
return limit > 0 && len(results) >= limit
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
list.Range(ascending, rangeFunc)
|
||||
return results
|
||||
}
|
||||
|
||||
func (list *Buffer[T]) prev(index int) int {
|
||||
switch index {
|
||||
case 0:
|
||||
return len(list.buffer) - 1
|
||||
default:
|
||||
return index - 1
|
||||
}
|
||||
}
|
||||
|
||||
func (list *Buffer[T]) next(index int) int {
|
||||
switch index {
|
||||
case len(list.buffer) - 1:
|
||||
return 0
|
||||
default:
|
||||
return index + 1
|
||||
}
|
||||
}
|
||||
|
||||
func (list *Buffer[T]) maybeExpand() {
|
||||
length := list.Length()
|
||||
if length < len(list.buffer) {
|
||||
return // we have spare capacity already
|
||||
}
|
||||
|
||||
if len(list.buffer) == list.maximumSize {
|
||||
return // cannot expand any further
|
||||
}
|
||||
|
||||
newSize := roundUpToPowerOfTwo(length + 1)
|
||||
if list.maximumSize < newSize {
|
||||
newSize = list.maximumSize
|
||||
}
|
||||
list.resize(newSize)
|
||||
}
|
||||
|
||||
// return n such that v <= n and n == 2**i for some i
|
||||
func roundUpToPowerOfTwo(v int) int {
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html
|
||||
v -= 1
|
||||
v |= v >> 1
|
||||
v |= v >> 2
|
||||
v |= v >> 4
|
||||
v |= v >> 8
|
||||
v |= v >> 16
|
||||
return v + 1
|
||||
}
|
||||
|
||||
func (hist *Buffer[T]) Length() int {
|
||||
if hist.start == -1 {
|
||||
return 0
|
||||
} else if hist.start < hist.end {
|
||||
return hist.end - hist.start
|
||||
} else {
|
||||
return len(hist.buffer) - (hist.start - hist.end)
|
||||
}
|
||||
}
|
||||
|
||||
func (list *Buffer[T]) resize(size int) {
|
||||
newbuffer := make([]T, size)
|
||||
|
||||
if list.start == -1 {
|
||||
// indices are already correct and nothing needs to be copied
|
||||
} else if size == 0 {
|
||||
// this is now the empty list
|
||||
list.start = -1
|
||||
list.end = -1
|
||||
} else {
|
||||
currentLength := list.Length()
|
||||
start := list.start
|
||||
end := list.end
|
||||
// if we're truncating, keep the latest entries, not the earliest
|
||||
if size < currentLength {
|
||||
start = list.end - size
|
||||
if start < 0 {
|
||||
start += len(list.buffer)
|
||||
}
|
||||
}
|
||||
if start < end {
|
||||
copied := copy(newbuffer, list.buffer[start:end])
|
||||
list.start = 0
|
||||
list.end = copied % size
|
||||
} else {
|
||||
lenInitial := len(list.buffer) - start
|
||||
copied := copy(newbuffer, list.buffer[start:])
|
||||
copied += copy(newbuffer[lenInitial:], list.buffer[:end])
|
||||
list.start = 0
|
||||
list.end = copied % size
|
||||
}
|
||||
}
|
||||
|
||||
list.buffer = newbuffer
|
||||
}
|
||||
|
||||
func (hist *Buffer[T]) Clear() {
|
||||
hist.Range(true, func(item *T) bool {
|
||||
var zero T
|
||||
*item = zero
|
||||
return false
|
||||
})
|
||||
hist.start = -1
|
||||
hist.end = -1
|
||||
}
|
264
vendor/github.com/ergochat/readline/internal/runes/runes.go
generated
vendored
Normal file
264
vendor/github.com/ergochat/readline/internal/runes/runes.go
generated
vendored
Normal file
|
@ -0,0 +1,264 @@
|
|||
package runes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"golang.org/x/text/width"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var TabWidth = 4
|
||||
|
||||
func EqualRune(a, b rune, fold bool) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
if !fold {
|
||||
return false
|
||||
}
|
||||
if a > b {
|
||||
a, b = b, a
|
||||
}
|
||||
if b < utf8.RuneSelf && 'A' <= a && a <= 'Z' {
|
||||
if b == a+'a'-'A' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func EqualRuneFold(a, b rune) bool {
|
||||
return EqualRune(a, b, true)
|
||||
}
|
||||
|
||||
func EqualFold(a, b []rune) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
if EqualRuneFold(a[i], b[i]) {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func Equal(a, b []rune) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func IndexAllBckEx(r, sub []rune, fold bool) int {
|
||||
for i := len(r) - len(sub); i >= 0; i-- {
|
||||
found := true
|
||||
for j := 0; j < len(sub); j++ {
|
||||
if !EqualRune(r[i+j], sub[j], fold) {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Search in runes from end to front
|
||||
func IndexAllBck(r, sub []rune) int {
|
||||
return IndexAllBckEx(r, sub, false)
|
||||
}
|
||||
|
||||
// Search in runes from front to end
|
||||
func IndexAll(r, sub []rune) int {
|
||||
return IndexAllEx(r, sub, false)
|
||||
}
|
||||
|
||||
func IndexAllEx(r, sub []rune, fold bool) int {
|
||||
for i := 0; i < len(r); i++ {
|
||||
found := true
|
||||
if len(r[i:]) < len(sub) {
|
||||
return -1
|
||||
}
|
||||
for j := 0; j < len(sub); j++ {
|
||||
if !EqualRune(r[i+j], sub[j], fold) {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func Index(r rune, rs []rune) int {
|
||||
for i := 0; i < len(rs); i++ {
|
||||
if rs[i] == r {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func ColorFilter(r []rune) []rune {
|
||||
newr := make([]rune, 0, len(r))
|
||||
for pos := 0; pos < len(r); pos++ {
|
||||
if r[pos] == '\033' && r[pos+1] == '[' {
|
||||
idx := Index('m', r[pos+2:])
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
pos += idx + 2
|
||||
continue
|
||||
}
|
||||
newr = append(newr, r[pos])
|
||||
}
|
||||
return newr
|
||||
}
|
||||
|
||||
var zeroWidth = []*unicode.RangeTable{
|
||||
unicode.Mn,
|
||||
unicode.Me,
|
||||
unicode.Cc,
|
||||
unicode.Cf,
|
||||
}
|
||||
|
||||
var doubleWidth = []*unicode.RangeTable{
|
||||
unicode.Han,
|
||||
unicode.Hangul,
|
||||
unicode.Hiragana,
|
||||
unicode.Katakana,
|
||||
}
|
||||
|
||||
func Width(r rune) int {
|
||||
if r == '\t' {
|
||||
return TabWidth
|
||||
}
|
||||
if unicode.IsOneOf(zeroWidth, r) {
|
||||
return 0
|
||||
}
|
||||
switch width.LookupRune(r).Kind() {
|
||||
case width.EastAsianWide, width.EastAsianFullwidth:
|
||||
return 2
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func WidthAll(r []rune) (length int) {
|
||||
for i := 0; i < len(r); i++ {
|
||||
length += Width(r[i])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Backspace(r []rune) []byte {
|
||||
return bytes.Repeat([]byte{'\b'}, WidthAll(r))
|
||||
}
|
||||
|
||||
func Copy(r []rune) []rune {
|
||||
n := make([]rune, len(r))
|
||||
copy(n, r)
|
||||
return n
|
||||
}
|
||||
|
||||
func HasPrefixFold(r, prefix []rune) bool {
|
||||
if len(r) < len(prefix) {
|
||||
return false
|
||||
}
|
||||
return EqualFold(r[:len(prefix)], prefix)
|
||||
}
|
||||
|
||||
func HasPrefix(r, prefix []rune) bool {
|
||||
if len(r) < len(prefix) {
|
||||
return false
|
||||
}
|
||||
return Equal(r[:len(prefix)], prefix)
|
||||
}
|
||||
|
||||
func Aggregate(candicate [][]rune) (same []rune, size int) {
|
||||
for i := 0; i < len(candicate[0]); i++ {
|
||||
for j := 0; j < len(candicate)-1; j++ {
|
||||
if i >= len(candicate[j]) || i >= len(candicate[j+1]) {
|
||||
goto aggregate
|
||||
}
|
||||
if candicate[j][i] != candicate[j+1][i] {
|
||||
goto aggregate
|
||||
}
|
||||
}
|
||||
size = i + 1
|
||||
}
|
||||
aggregate:
|
||||
if size > 0 {
|
||||
same = Copy(candicate[0][:size])
|
||||
for i := 0; i < len(candicate); i++ {
|
||||
n := Copy(candicate[i])
|
||||
copy(n, n[size:])
|
||||
candicate[i] = n[:len(n)-size]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TrimSpaceLeft(in []rune) []rune {
|
||||
firstIndex := len(in)
|
||||
for i, r := range in {
|
||||
if unicode.IsSpace(r) == false {
|
||||
firstIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return in[firstIndex:]
|
||||
}
|
||||
|
||||
func IsWordBreak(i rune) bool {
|
||||
switch {
|
||||
case i >= 'a' && i <= 'z':
|
||||
case i >= 'A' && i <= 'Z':
|
||||
case i >= '0' && i <= '9':
|
||||
default:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// split prompt + runes into lines by screenwidth starting from an offset.
|
||||
// the prompt should be filtered before passing to only its display runes.
|
||||
// if you know the width of the next character, pass it in as it is used
|
||||
// to decide if we generate an extra empty rune array to show next is new
|
||||
// line.
|
||||
func SplitByLine(prompt, rs []rune, offset, screenWidth, nextWidth int) [][]rune {
|
||||
ret := make([][]rune, 0)
|
||||
prs := append(prompt, rs...)
|
||||
si := 0
|
||||
currentWidth := offset
|
||||
for i, r := range prs {
|
||||
w := Width(r)
|
||||
if r == '\n' {
|
||||
ret = append(ret, prs[si:i+1])
|
||||
si = i + 1
|
||||
currentWidth = 0
|
||||
} else if currentWidth+w > screenWidth {
|
||||
ret = append(ret, prs[si:i])
|
||||
si = i
|
||||
currentWidth = 0
|
||||
}
|
||||
currentWidth += w
|
||||
}
|
||||
ret = append(ret, prs[si:])
|
||||
if currentWidth+nextWidth > screenWidth {
|
||||
ret = append(ret, []rune{})
|
||||
}
|
||||
return ret
|
||||
}
|
60
vendor/github.com/ergochat/readline/internal/term/term.go
generated
vendored
Normal file
60
vendor/github.com/ergochat/readline/internal/term/term.go
generated
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2019 The Go 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 term provides support functions for dealing with terminals, as
|
||||
// commonly found on UNIX systems.
|
||||
//
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
||||
//
|
||||
// oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
//
|
||||
// Note that on non-Unix systems os.Stdin.Fd() may not be 0.
|
||||
package term
|
||||
|
||||
// State contains the state of a terminal.
|
||||
type State struct {
|
||||
state
|
||||
}
|
||||
|
||||
// IsTerminal returns whether the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) 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 int) (*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 int) (*State, error) {
|
||||
return getState(fd)
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, 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 int) (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 int) ([]byte, error) {
|
||||
return readPassword(fd)
|
||||
}
|
42
vendor/github.com/ergochat/readline/internal/term/term_plan9.go
generated
vendored
Normal file
42
vendor/github.com/ergochat/readline/internal/term/term_plan9.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2019 The Go 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 term
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/plan9"
|
||||
)
|
||||
|
||||
type state struct{}
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
path, err := plan9.Fd2path(fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return path == "/dev/cons" || path == "/mnt/term/dev/cons"
|
||||
}
|
||||
|
||||
func makeRaw(fd int) (*State, error) {
|
||||
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func getState(fd int) (*State, error) {
|
||||
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func restore(fd int, state *State) error {
|
||||
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func getSize(fd int) (width, height int, err error) {
|
||||
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func readPassword(fd int) ([]byte, error) {
|
||||
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
91
vendor/github.com/ergochat/readline/internal/term/term_unix.go
generated
vendored
Normal file
91
vendor/github.com/ergochat/readline/internal/term/term_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||
|
||||
package term
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type state struct {
|
||||
termios unix.Termios
|
||||
}
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func makeRaw(fd int) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(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(fd, ioctlWriteTermios, termios); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
}
|
||||
|
||||
func getState(fd int) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &State{state{termios: *termios}}, nil
|
||||
}
|
||||
|
||||
func restore(fd int, state *State) error {
|
||||
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
|
||||
}
|
||||
|
||||
func getSize(fd int) (width, height int, err error) {
|
||||
ws, err := unix.IoctlGetWinsize(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 int) ([]byte, error) {
|
||||
termios, err := unix.IoctlGetTermios(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(fd, ioctlWriteTermios, &newState); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer unix.IoctlSetTermios(fd, ioctlWriteTermios, termios)
|
||||
|
||||
return readPasswordLine(passwordReader(fd))
|
||||
}
|
12
vendor/github.com/ergochat/readline/internal/term/term_unix_bsd.go
generated
vendored
Normal file
12
vendor/github.com/ergochat/readline/internal/term/term_unix_bsd.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||
|
||||
package term
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TIOCGETA
|
||||
const ioctlWriteTermios = unix.TIOCSETA
|
12
vendor/github.com/ergochat/readline/internal/term/term_unix_other.go
generated
vendored
Normal file
12
vendor/github.com/ergochat/readline/internal/term/term_unix_other.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build aix || linux || solaris || zos
|
||||
|
||||
package term
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TCGETS
|
||||
const ioctlWriteTermios = unix.TCSETS
|
38
vendor/github.com/ergochat/readline/internal/term/term_unsupported.go
generated
vendored
Normal file
38
vendor/github.com/ergochat/readline/internal/term/term_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !zos && !windows && !solaris && !plan9
|
||||
|
||||
package term
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type state struct{}
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func makeRaw(fd int) (*State, error) {
|
||||
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func getState(fd int) (*State, error) {
|
||||
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func restore(fd int, state *State) error {
|
||||
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func getSize(fd int) (width, height int, err error) {
|
||||
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func readPassword(fd int) ([]byte, error) {
|
||||
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
79
vendor/github.com/ergochat/readline/internal/term/term_windows.go
generated
vendored
Normal file
79
vendor/github.com/ergochat/readline/internal/term/term_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2019 The Go 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 term
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type state struct {
|
||||
mode uint32
|
||||
}
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
var st uint32
|
||||
err := windows.GetConsoleMode(windows.Handle(fd), &st)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func makeRaw(fd int) (*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)
|
||||
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &State{state{st}}, nil
|
||||
}
|
||||
|
||||
func getState(fd int) (*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 int, state *State) error {
|
||||
return windows.SetConsoleMode(windows.Handle(fd), state.mode)
|
||||
}
|
||||
|
||||
func getSize(fd int) (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 int) ([]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)
|
||||
}
|
986
vendor/github.com/ergochat/readline/internal/term/terminal.go
generated
vendored
Normal file
986
vendor/github.com/ergochat/readline/internal/term/terminal.go
generated
vendored
Normal file
|
@ -0,0 +1,986 @@
|
|||
// Copyright 2011 The Go 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 term
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// EscapeCodes contains escape sequences that can be written to the terminal in
|
||||
// order to achieve different styles of text.
|
||||
type EscapeCodes struct {
|
||||
// Foreground colors
|
||||
Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
|
||||
|
||||
// Reset all attributes
|
||||
Reset []byte
|
||||
}
|
||||
|
||||
var vt100EscapeCodes = EscapeCodes{
|
||||
Black: []byte{keyEscape, '[', '3', '0', 'm'},
|
||||
Red: []byte{keyEscape, '[', '3', '1', 'm'},
|
||||
Green: []byte{keyEscape, '[', '3', '2', 'm'},
|
||||
Yellow: []byte{keyEscape, '[', '3', '3', 'm'},
|
||||
Blue: []byte{keyEscape, '[', '3', '4', 'm'},
|
||||
Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
|
||||
Cyan: []byte{keyEscape, '[', '3', '6', 'm'},
|
||||
White: []byte{keyEscape, '[', '3', '7', 'm'},
|
||||
|
||||
Reset: []byte{keyEscape, '[', '0', 'm'},
|
||||
}
|
||||
|
||||
// Terminal contains the state for running a VT100 terminal that is capable of
|
||||
// reading lines of input.
|
||||
type Terminal struct {
|
||||
// AutoCompleteCallback, if non-null, is called for each keypress with
|
||||
// the full input line and the current position of the cursor (in
|
||||
// bytes, as an index into |line|). If it returns ok=false, the key
|
||||
// press is processed normally. Otherwise it returns a replacement line
|
||||
// and the new cursor position.
|
||||
AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
|
||||
|
||||
// Escape contains a pointer to the escape codes for this terminal.
|
||||
// It's always a valid pointer, although the escape codes themselves
|
||||
// may be empty if the terminal doesn't support them.
|
||||
Escape *EscapeCodes
|
||||
|
||||
// lock protects the terminal and the state in this object from
|
||||
// concurrent processing of a key press and a Write() call.
|
||||
lock sync.Mutex
|
||||
|
||||
c io.ReadWriter
|
||||
prompt []rune
|
||||
|
||||
// line is the current line being entered.
|
||||
line []rune
|
||||
// pos is the logical position of the cursor in line
|
||||
pos int
|
||||
// echo is true if local echo is enabled
|
||||
echo bool
|
||||
// pasteActive is true iff there is a bracketed paste operation in
|
||||
// progress.
|
||||
pasteActive bool
|
||||
|
||||
// cursorX contains the current X value of the cursor where the left
|
||||
// edge is 0. cursorY contains the row number where the first row of
|
||||
// the current line is 0.
|
||||
cursorX, cursorY int
|
||||
// maxLine is the greatest value of cursorY so far.
|
||||
maxLine int
|
||||
|
||||
termWidth, termHeight int
|
||||
|
||||
// outBuf contains the terminal data to be sent.
|
||||
outBuf []byte
|
||||
// remainder contains the remainder of any partial key sequences after
|
||||
// a read. It aliases into inBuf.
|
||||
remainder []byte
|
||||
inBuf [256]byte
|
||||
|
||||
// history contains previously entered commands so that they can be
|
||||
// accessed with the up and down keys.
|
||||
history stRingBuffer
|
||||
// historyIndex stores the currently accessed history entry, where zero
|
||||
// means the immediately previous entry.
|
||||
historyIndex int
|
||||
// When navigating up and down the history it's possible to return to
|
||||
// the incomplete, initial line. That value is stored in
|
||||
// historyPending.
|
||||
historyPending string
|
||||
}
|
||||
|
||||
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
|
||||
// a local terminal, that terminal must first have been put into raw mode.
|
||||
// prompt is a string that is written at the start of each input line (i.e.
|
||||
// "> ").
|
||||
func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
|
||||
return &Terminal{
|
||||
Escape: &vt100EscapeCodes,
|
||||
c: c,
|
||||
prompt: []rune(prompt),
|
||||
termWidth: 80,
|
||||
termHeight: 24,
|
||||
echo: true,
|
||||
historyIndex: -1,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
keyCtrlC = 3
|
||||
keyCtrlD = 4
|
||||
keyCtrlU = 21
|
||||
keyEnter = '\r'
|
||||
keyEscape = 27
|
||||
keyBackspace = 127
|
||||
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
|
||||
keyUp
|
||||
keyDown
|
||||
keyLeft
|
||||
keyRight
|
||||
keyAltLeft
|
||||
keyAltRight
|
||||
keyHome
|
||||
keyEnd
|
||||
keyDeleteWord
|
||||
keyDeleteLine
|
||||
keyClearScreen
|
||||
keyPasteStart
|
||||
keyPasteEnd
|
||||
)
|
||||
|
||||
var (
|
||||
crlf = []byte{'\r', '\n'}
|
||||
pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
|
||||
pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
|
||||
)
|
||||
|
||||
// bytesToKey tries to parse a key sequence from b. If successful, it returns
|
||||
// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
|
||||
func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
|
||||
if len(b) == 0 {
|
||||
return utf8.RuneError, nil
|
||||
}
|
||||
|
||||
if !pasteActive {
|
||||
switch b[0] {
|
||||
case 1: // ^A
|
||||
return keyHome, b[1:]
|
||||
case 2: // ^B
|
||||
return keyLeft, b[1:]
|
||||
case 5: // ^E
|
||||
return keyEnd, b[1:]
|
||||
case 6: // ^F
|
||||
return keyRight, b[1:]
|
||||
case 8: // ^H
|
||||
return keyBackspace, b[1:]
|
||||
case 11: // ^K
|
||||
return keyDeleteLine, b[1:]
|
||||
case 12: // ^L
|
||||
return keyClearScreen, b[1:]
|
||||
case 23: // ^W
|
||||
return keyDeleteWord, b[1:]
|
||||
case 14: // ^N
|
||||
return keyDown, b[1:]
|
||||
case 16: // ^P
|
||||
return keyUp, b[1:]
|
||||
}
|
||||
}
|
||||
|
||||
if b[0] != keyEscape {
|
||||
if !utf8.FullRune(b) {
|
||||
return utf8.RuneError, b
|
||||
}
|
||||
r, l := utf8.DecodeRune(b)
|
||||
return r, b[l:]
|
||||
}
|
||||
|
||||
if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
|
||||
switch b[2] {
|
||||
case 'A':
|
||||
return keyUp, b[3:]
|
||||
case 'B':
|
||||
return keyDown, b[3:]
|
||||
case 'C':
|
||||
return keyRight, b[3:]
|
||||
case 'D':
|
||||
return keyLeft, b[3:]
|
||||
case 'H':
|
||||
return keyHome, b[3:]
|
||||
case 'F':
|
||||
return keyEnd, b[3:]
|
||||
}
|
||||
}
|
||||
|
||||
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
||||
switch b[5] {
|
||||
case 'C':
|
||||
return keyAltRight, b[6:]
|
||||
case 'D':
|
||||
return keyAltLeft, b[6:]
|
||||
}
|
||||
}
|
||||
|
||||
if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) {
|
||||
return keyPasteStart, b[6:]
|
||||
}
|
||||
|
||||
if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) {
|
||||
return keyPasteEnd, b[6:]
|
||||
}
|
||||
|
||||
// If we get here then we have a key that we don't recognise, or a
|
||||
// partial sequence. It's not clear how one should find the end of a
|
||||
// sequence without knowing them all, but it seems that [a-zA-Z~] only
|
||||
// appears at the end of a sequence.
|
||||
for i, c := range b[0:] {
|
||||
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
|
||||
return keyUnknown, b[i+1:]
|
||||
}
|
||||
}
|
||||
|
||||
return utf8.RuneError, b
|
||||
}
|
||||
|
||||
// queue appends data to the end of t.outBuf
|
||||
func (t *Terminal) queue(data []rune) {
|
||||
t.outBuf = append(t.outBuf, []byte(string(data))...)
|
||||
}
|
||||
|
||||
var space = []rune{' '}
|
||||
|
||||
func isPrintable(key rune) bool {
|
||||
isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
|
||||
return key >= 32 && !isInSurrogateArea
|
||||
}
|
||||
|
||||
// moveCursorToPos appends data to t.outBuf which will move the cursor to the
|
||||
// given, logical position in the text.
|
||||
func (t *Terminal) moveCursorToPos(pos int) {
|
||||
if !t.echo {
|
||||
return
|
||||
}
|
||||
|
||||
x := visualLength(t.prompt) + pos
|
||||
y := x / t.termWidth
|
||||
x = x % t.termWidth
|
||||
|
||||
up := 0
|
||||
if y < t.cursorY {
|
||||
up = t.cursorY - y
|
||||
}
|
||||
|
||||
down := 0
|
||||
if y > t.cursorY {
|
||||
down = y - t.cursorY
|
||||
}
|
||||
|
||||
left := 0
|
||||
if x < t.cursorX {
|
||||
left = t.cursorX - x
|
||||
}
|
||||
|
||||
right := 0
|
||||
if x > t.cursorX {
|
||||
right = x - t.cursorX
|
||||
}
|
||||
|
||||
t.cursorX = x
|
||||
t.cursorY = y
|
||||
t.move(up, down, left, right)
|
||||
}
|
||||
|
||||
func (t *Terminal) move(up, down, left, right int) {
|
||||
m := []rune{}
|
||||
|
||||
// 1 unit up can be expressed as ^[[A or ^[A
|
||||
// 5 units up can be expressed as ^[[5A
|
||||
|
||||
if up == 1 {
|
||||
m = append(m, keyEscape, '[', 'A')
|
||||
} else if up > 1 {
|
||||
m = append(m, keyEscape, '[')
|
||||
m = append(m, []rune(strconv.Itoa(up))...)
|
||||
m = append(m, 'A')
|
||||
}
|
||||
|
||||
if down == 1 {
|
||||
m = append(m, keyEscape, '[', 'B')
|
||||
} else if down > 1 {
|
||||
m = append(m, keyEscape, '[')
|
||||
m = append(m, []rune(strconv.Itoa(down))...)
|
||||
m = append(m, 'B')
|
||||
}
|
||||
|
||||
if right == 1 {
|
||||
m = append(m, keyEscape, '[', 'C')
|
||||
} else if right > 1 {
|
||||
m = append(m, keyEscape, '[')
|
||||
m = append(m, []rune(strconv.Itoa(right))...)
|
||||
m = append(m, 'C')
|
||||
}
|
||||
|
||||
if left == 1 {
|
||||
m = append(m, keyEscape, '[', 'D')
|
||||
} else if left > 1 {
|
||||
m = append(m, keyEscape, '[')
|
||||
m = append(m, []rune(strconv.Itoa(left))...)
|
||||
m = append(m, 'D')
|
||||
}
|
||||
|
||||
t.queue(m)
|
||||
}
|
||||
|
||||
func (t *Terminal) clearLineToRight() {
|
||||
op := []rune{keyEscape, '[', 'K'}
|
||||
t.queue(op)
|
||||
}
|
||||
|
||||
const maxLineLength = 4096
|
||||
|
||||
func (t *Terminal) setLine(newLine []rune, newPos int) {
|
||||
if t.echo {
|
||||
t.moveCursorToPos(0)
|
||||
t.writeLine(newLine)
|
||||
for i := len(newLine); i < len(t.line); i++ {
|
||||
t.writeLine(space)
|
||||
}
|
||||
t.moveCursorToPos(newPos)
|
||||
}
|
||||
t.line = newLine
|
||||
t.pos = newPos
|
||||
}
|
||||
|
||||
func (t *Terminal) advanceCursor(places int) {
|
||||
t.cursorX += places
|
||||
t.cursorY += t.cursorX / t.termWidth
|
||||
if t.cursorY > t.maxLine {
|
||||
t.maxLine = t.cursorY
|
||||
}
|
||||
t.cursorX = t.cursorX % t.termWidth
|
||||
|
||||
if places > 0 && t.cursorX == 0 {
|
||||
// Normally terminals will advance the current position
|
||||
// when writing a character. But that doesn't happen
|
||||
// for the last character in a line. However, when
|
||||
// writing a character (except a new line) that causes
|
||||
// a line wrap, the position will be advanced two
|
||||
// places.
|
||||
//
|
||||
// So, if we are stopping at the end of a line, we
|
||||
// need to write a newline so that our cursor can be
|
||||
// advanced to the next line.
|
||||
t.outBuf = append(t.outBuf, '\r', '\n')
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) eraseNPreviousChars(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if t.pos < n {
|
||||
n = t.pos
|
||||
}
|
||||
t.pos -= n
|
||||
t.moveCursorToPos(t.pos)
|
||||
|
||||
copy(t.line[t.pos:], t.line[n+t.pos:])
|
||||
t.line = t.line[:len(t.line)-n]
|
||||
if t.echo {
|
||||
t.writeLine(t.line[t.pos:])
|
||||
for i := 0; i < n; i++ {
|
||||
t.queue(space)
|
||||
}
|
||||
t.advanceCursor(n)
|
||||
t.moveCursorToPos(t.pos)
|
||||
}
|
||||
}
|
||||
|
||||
// countToLeftWord returns then number of characters from the cursor to the
|
||||
// start of the previous word.
|
||||
func (t *Terminal) countToLeftWord() int {
|
||||
if t.pos == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
pos := t.pos - 1
|
||||
for pos > 0 {
|
||||
if t.line[pos] != ' ' {
|
||||
break
|
||||
}
|
||||
pos--
|
||||
}
|
||||
for pos > 0 {
|
||||
if t.line[pos] == ' ' {
|
||||
pos++
|
||||
break
|
||||
}
|
||||
pos--
|
||||
}
|
||||
|
||||
return t.pos - pos
|
||||
}
|
||||
|
||||
// countToRightWord returns then number of characters from the cursor to the
|
||||
// start of the next word.
|
||||
func (t *Terminal) countToRightWord() int {
|
||||
pos := t.pos
|
||||
for pos < len(t.line) {
|
||||
if t.line[pos] == ' ' {
|
||||
break
|
||||
}
|
||||
pos++
|
||||
}
|
||||
for pos < len(t.line) {
|
||||
if t.line[pos] != ' ' {
|
||||
break
|
||||
}
|
||||
pos++
|
||||
}
|
||||
return pos - t.pos
|
||||
}
|
||||
|
||||
// visualLength returns the number of visible glyphs in s.
|
||||
func visualLength(runes []rune) int {
|
||||
inEscapeSeq := false
|
||||
length := 0
|
||||
|
||||
for _, r := range runes {
|
||||
switch {
|
||||
case inEscapeSeq:
|
||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
|
||||
inEscapeSeq = false
|
||||
}
|
||||
case r == '\x1b':
|
||||
inEscapeSeq = true
|
||||
default:
|
||||
length++
|
||||
}
|
||||
}
|
||||
|
||||
return length
|
||||
}
|
||||
|
||||
// handleKey processes the given key and, optionally, returns a line of text
|
||||
// that the user has entered.
|
||||
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
||||
if t.pasteActive && key != keyEnter {
|
||||
t.addKeyToLine(key)
|
||||
return
|
||||
}
|
||||
|
||||
switch key {
|
||||
case keyBackspace:
|
||||
if t.pos == 0 {
|
||||
return
|
||||
}
|
||||
t.eraseNPreviousChars(1)
|
||||
case keyAltLeft:
|
||||
// move left by a word.
|
||||
t.pos -= t.countToLeftWord()
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyAltRight:
|
||||
// move right by a word.
|
||||
t.pos += t.countToRightWord()
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyLeft:
|
||||
if t.pos == 0 {
|
||||
return
|
||||
}
|
||||
t.pos--
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyRight:
|
||||
if t.pos == len(t.line) {
|
||||
return
|
||||
}
|
||||
t.pos++
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyHome:
|
||||
if t.pos == 0 {
|
||||
return
|
||||
}
|
||||
t.pos = 0
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyEnd:
|
||||
if t.pos == len(t.line) {
|
||||
return
|
||||
}
|
||||
t.pos = len(t.line)
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyUp:
|
||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if t.historyIndex == -1 {
|
||||
t.historyPending = string(t.line)
|
||||
}
|
||||
t.historyIndex++
|
||||
runes := []rune(entry)
|
||||
t.setLine(runes, len(runes))
|
||||
case keyDown:
|
||||
switch t.historyIndex {
|
||||
case -1:
|
||||
return
|
||||
case 0:
|
||||
runes := []rune(t.historyPending)
|
||||
t.setLine(runes, len(runes))
|
||||
t.historyIndex--
|
||||
default:
|
||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
|
||||
if ok {
|
||||
t.historyIndex--
|
||||
runes := []rune(entry)
|
||||
t.setLine(runes, len(runes))
|
||||
}
|
||||
}
|
||||
case keyEnter:
|
||||
t.moveCursorToPos(len(t.line))
|
||||
t.queue([]rune("\r\n"))
|
||||
line = string(t.line)
|
||||
ok = true
|
||||
t.line = t.line[:0]
|
||||
t.pos = 0
|
||||
t.cursorX = 0
|
||||
t.cursorY = 0
|
||||
t.maxLine = 0
|
||||
case keyDeleteWord:
|
||||
// Delete zero or more spaces and then one or more characters.
|
||||
t.eraseNPreviousChars(t.countToLeftWord())
|
||||
case keyDeleteLine:
|
||||
// Delete everything from the current cursor position to the
|
||||
// end of line.
|
||||
for i := t.pos; i < len(t.line); i++ {
|
||||
t.queue(space)
|
||||
t.advanceCursor(1)
|
||||
}
|
||||
t.line = t.line[:t.pos]
|
||||
t.moveCursorToPos(t.pos)
|
||||
case keyCtrlD:
|
||||
// Erase the character under the current position.
|
||||
// The EOF case when the line is empty is handled in
|
||||
// readLine().
|
||||
if t.pos < len(t.line) {
|
||||
t.pos++
|
||||
t.eraseNPreviousChars(1)
|
||||
}
|
||||
case keyCtrlU:
|
||||
t.eraseNPreviousChars(t.pos)
|
||||
case keyClearScreen:
|
||||
// Erases the screen and moves the cursor to the home position.
|
||||
t.queue([]rune("\x1b[2J\x1b[H"))
|
||||
t.queue(t.prompt)
|
||||
t.cursorX, t.cursorY = 0, 0
|
||||
t.advanceCursor(visualLength(t.prompt))
|
||||
t.setLine(t.line, t.pos)
|
||||
default:
|
||||
if t.AutoCompleteCallback != nil {
|
||||
prefix := string(t.line[:t.pos])
|
||||
suffix := string(t.line[t.pos:])
|
||||
|
||||
t.lock.Unlock()
|
||||
newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
|
||||
t.lock.Lock()
|
||||
|
||||
if completeOk {
|
||||
t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
|
||||
return
|
||||
}
|
||||
}
|
||||
if !isPrintable(key) {
|
||||
return
|
||||
}
|
||||
if len(t.line) == maxLineLength {
|
||||
return
|
||||
}
|
||||
t.addKeyToLine(key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// addKeyToLine inserts the given key at the current position in the current
|
||||
// line.
|
||||
func (t *Terminal) addKeyToLine(key rune) {
|
||||
if len(t.line) == cap(t.line) {
|
||||
newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
|
||||
copy(newLine, t.line)
|
||||
t.line = newLine
|
||||
}
|
||||
t.line = t.line[:len(t.line)+1]
|
||||
copy(t.line[t.pos+1:], t.line[t.pos:])
|
||||
t.line[t.pos] = key
|
||||
if t.echo {
|
||||
t.writeLine(t.line[t.pos:])
|
||||
}
|
||||
t.pos++
|
||||
t.moveCursorToPos(t.pos)
|
||||
}
|
||||
|
||||
func (t *Terminal) writeLine(line []rune) {
|
||||
for len(line) != 0 {
|
||||
remainingOnLine := t.termWidth - t.cursorX
|
||||
todo := len(line)
|
||||
if todo > remainingOnLine {
|
||||
todo = remainingOnLine
|
||||
}
|
||||
t.queue(line[:todo])
|
||||
t.advanceCursor(visualLength(line[:todo]))
|
||||
line = line[todo:]
|
||||
}
|
||||
}
|
||||
|
||||
// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n.
|
||||
func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
|
||||
for len(buf) > 0 {
|
||||
i := bytes.IndexByte(buf, '\n')
|
||||
todo := len(buf)
|
||||
if i >= 0 {
|
||||
todo = i
|
||||
}
|
||||
|
||||
var nn int
|
||||
nn, err = w.Write(buf[:todo])
|
||||
n += nn
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
buf = buf[todo:]
|
||||
|
||||
if i >= 0 {
|
||||
if _, err = w.Write(crlf); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
buf = buf[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (t *Terminal) Write(buf []byte) (n int, err error) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if t.cursorX == 0 && t.cursorY == 0 {
|
||||
// This is the easy case: there's nothing on the screen that we
|
||||
// have to move out of the way.
|
||||
return writeWithCRLF(t.c, buf)
|
||||
}
|
||||
|
||||
// We have a prompt and possibly user input on the screen. We
|
||||
// have to clear it first.
|
||||
t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
|
||||
t.cursorX = 0
|
||||
t.clearLineToRight()
|
||||
|
||||
for t.cursorY > 0 {
|
||||
t.move(1 /* up */, 0, 0, 0)
|
||||
t.cursorY--
|
||||
t.clearLineToRight()
|
||||
}
|
||||
|
||||
if _, err = t.c.Write(t.outBuf); err != nil {
|
||||
return
|
||||
}
|
||||
t.outBuf = t.outBuf[:0]
|
||||
|
||||
if n, err = writeWithCRLF(t.c, buf); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.writeLine(t.prompt)
|
||||
if t.echo {
|
||||
t.writeLine(t.line)
|
||||
}
|
||||
|
||||
t.moveCursorToPos(t.pos)
|
||||
|
||||
if _, err = t.c.Write(t.outBuf); err != nil {
|
||||
return
|
||||
}
|
||||
t.outBuf = t.outBuf[:0]
|
||||
return
|
||||
}
|
||||
|
||||
// ReadPassword temporarily changes the prompt and reads a password, without
|
||||
// echo, from the terminal.
|
||||
func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
oldPrompt := t.prompt
|
||||
t.prompt = []rune(prompt)
|
||||
t.echo = false
|
||||
|
||||
line, err = t.readLine()
|
||||
|
||||
t.prompt = oldPrompt
|
||||
t.echo = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadLine returns a line of input from the terminal.
|
||||
func (t *Terminal) ReadLine() (line string, err error) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
return t.readLine()
|
||||
}
|
||||
|
||||
func (t *Terminal) readLine() (line string, err error) {
|
||||
// t.lock must be held at this point
|
||||
|
||||
if t.cursorX == 0 && t.cursorY == 0 {
|
||||
t.writeLine(t.prompt)
|
||||
t.c.Write(t.outBuf)
|
||||
t.outBuf = t.outBuf[:0]
|
||||
}
|
||||
|
||||
lineIsPasted := t.pasteActive
|
||||
|
||||
for {
|
||||
rest := t.remainder
|
||||
lineOk := false
|
||||
for !lineOk {
|
||||
var key rune
|
||||
key, rest = bytesToKey(rest, t.pasteActive)
|
||||
if key == utf8.RuneError {
|
||||
break
|
||||
}
|
||||
if !t.pasteActive {
|
||||
if key == keyCtrlD {
|
||||
if len(t.line) == 0 {
|
||||
return "", io.EOF
|
||||
}
|
||||
}
|
||||
if key == keyCtrlC {
|
||||
return "", io.EOF
|
||||
}
|
||||
if key == keyPasteStart {
|
||||
t.pasteActive = true
|
||||
if len(t.line) == 0 {
|
||||
lineIsPasted = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else if key == keyPasteEnd {
|
||||
t.pasteActive = false
|
||||
continue
|
||||
}
|
||||
if !t.pasteActive {
|
||||
lineIsPasted = false
|
||||
}
|
||||
line, lineOk = t.handleKey(key)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
n := copy(t.inBuf[:], rest)
|
||||
t.remainder = t.inBuf[:n]
|
||||
} else {
|
||||
t.remainder = nil
|
||||
}
|
||||
t.c.Write(t.outBuf)
|
||||
t.outBuf = t.outBuf[:0]
|
||||
if lineOk {
|
||||
if t.echo {
|
||||
t.historyIndex = -1
|
||||
t.history.Add(line)
|
||||
}
|
||||
if lineIsPasted {
|
||||
err = ErrPasteIndicator
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// t.remainder is a slice at the beginning of t.inBuf
|
||||
// containing a partial key sequence
|
||||
readBuf := t.inBuf[len(t.remainder):]
|
||||
var n int
|
||||
|
||||
t.lock.Unlock()
|
||||
n, err = t.c.Read(readBuf)
|
||||
t.lock.Lock()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.remainder = t.inBuf[:n+len(t.remainder)]
|
||||
}
|
||||
}
|
||||
|
||||
// SetPrompt sets the prompt to be used when reading subsequent lines.
|
||||
func (t *Terminal) SetPrompt(prompt string) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
t.prompt = []rune(prompt)
|
||||
}
|
||||
|
||||
func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
|
||||
// Move cursor to column zero at the start of the line.
|
||||
t.move(t.cursorY, 0, t.cursorX, 0)
|
||||
t.cursorX, t.cursorY = 0, 0
|
||||
t.clearLineToRight()
|
||||
for t.cursorY < numPrevLines {
|
||||
// Move down a line
|
||||
t.move(0, 1, 0, 0)
|
||||
t.cursorY++
|
||||
t.clearLineToRight()
|
||||
}
|
||||
// Move back to beginning.
|
||||
t.move(t.cursorY, 0, 0, 0)
|
||||
t.cursorX, t.cursorY = 0, 0
|
||||
|
||||
t.queue(t.prompt)
|
||||
t.advanceCursor(visualLength(t.prompt))
|
||||
t.writeLine(t.line)
|
||||
t.moveCursorToPos(t.pos)
|
||||
}
|
||||
|
||||
func (t *Terminal) SetSize(width, height int) error {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if width == 0 {
|
||||
width = 1
|
||||
}
|
||||
|
||||
oldWidth := t.termWidth
|
||||
t.termWidth, t.termHeight = width, height
|
||||
|
||||
switch {
|
||||
case width == oldWidth:
|
||||
// If the width didn't change then nothing else needs to be
|
||||
// done.
|
||||
return nil
|
||||
case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
|
||||
// If there is nothing on current line and no prompt printed,
|
||||
// just do nothing
|
||||
return nil
|
||||
case width < oldWidth:
|
||||
// Some terminals (e.g. xterm) will truncate lines that were
|
||||
// too long when shinking. Others, (e.g. gnome-terminal) will
|
||||
// attempt to wrap them. For the former, repainting t.maxLine
|
||||
// works great, but that behaviour goes badly wrong in the case
|
||||
// of the latter because they have doubled every full line.
|
||||
|
||||
// We assume that we are working on a terminal that wraps lines
|
||||
// and adjust the cursor position based on every previous line
|
||||
// wrapping and turning into two. This causes the prompt on
|
||||
// xterms to move upwards, which isn't great, but it avoids a
|
||||
// huge mess with gnome-terminal.
|
||||
if t.cursorX >= t.termWidth {
|
||||
t.cursorX = t.termWidth - 1
|
||||
}
|
||||
t.cursorY *= 2
|
||||
t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
|
||||
case width > oldWidth:
|
||||
// If the terminal expands then our position calculations will
|
||||
// be wrong in the future because we think the cursor is
|
||||
// |t.pos| chars into the string, but there will be a gap at
|
||||
// the end of any wrapped line.
|
||||
//
|
||||
// But the position will actually be correct until we move, so
|
||||
// we can move back to the beginning and repaint everything.
|
||||
t.clearAndRepaintLinePlusNPrevious(t.maxLine)
|
||||
}
|
||||
|
||||
_, err := t.c.Write(t.outBuf)
|
||||
t.outBuf = t.outBuf[:0]
|
||||
return err
|
||||
}
|
||||
|
||||
type pasteIndicatorError struct{}
|
||||
|
||||
func (pasteIndicatorError) Error() string {
|
||||
return "terminal: ErrPasteIndicator not correctly handled"
|
||||
}
|
||||
|
||||
// ErrPasteIndicator may be returned from ReadLine as the error, in addition
|
||||
// to valid line data. It indicates that bracketed paste mode is enabled and
|
||||
// that the returned line consists only of pasted data. Programs may wish to
|
||||
// interpret pasted data more literally than typed data.
|
||||
var ErrPasteIndicator = pasteIndicatorError{}
|
||||
|
||||
// SetBracketedPasteMode requests that the terminal bracket paste operations
|
||||
// with markers. Not all terminals support this but, if it is supported, then
|
||||
// enabling this mode will stop any autocomplete callback from running due to
|
||||
// pastes. Additionally, any lines that are completely pasted will be returned
|
||||
// from ReadLine with the error set to ErrPasteIndicator.
|
||||
func (t *Terminal) SetBracketedPasteMode(on bool) {
|
||||
if on {
|
||||
io.WriteString(t.c, "\x1b[?2004h")
|
||||
} else {
|
||||
io.WriteString(t.c, "\x1b[?2004l")
|
||||
}
|
||||
}
|
||||
|
||||
// stRingBuffer is a ring buffer of strings.
|
||||
type stRingBuffer struct {
|
||||
// entries contains max elements.
|
||||
entries []string
|
||||
max int
|
||||
// head contains the index of the element most recently added to the ring.
|
||||
head int
|
||||
// size contains the number of elements in the ring.
|
||||
size int
|
||||
}
|
||||
|
||||
func (s *stRingBuffer) Add(a string) {
|
||||
if s.entries == nil {
|
||||
const defaultNumEntries = 100
|
||||
s.entries = make([]string, defaultNumEntries)
|
||||
s.max = defaultNumEntries
|
||||
}
|
||||
|
||||
s.head = (s.head + 1) % s.max
|
||||
s.entries[s.head] = a
|
||||
if s.size < s.max {
|
||||
s.size++
|
||||
}
|
||||
}
|
||||
|
||||
// NthPreviousEntry returns the value passed to the nth previous call to Add.
|
||||
// If n is zero then the immediately prior value is returned, if one, then the
|
||||
// next most recent, and so on. If such an element doesn't exist then ok is
|
||||
// false.
|
||||
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
|
||||
if n < 0 || n >= s.size {
|
||||
return "", false
|
||||
}
|
||||
index := s.head - n
|
||||
if index < 0 {
|
||||
index += s.max
|
||||
}
|
||||
return s.entries[index], true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
555
vendor/github.com/ergochat/readline/operation.go
generated
vendored
Normal file
555
vendor/github.com/ergochat/readline/operation.go
generated
vendored
Normal file
|
@ -0,0 +1,555 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ergochat/readline/internal/platform"
|
||||
"github.com/ergochat/readline/internal/runes"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInterrupt = errors.New("Interrupt")
|
||||
)
|
||||
|
||||
type operation struct {
|
||||
m sync.Mutex
|
||||
t *terminal
|
||||
buf *runeBuffer
|
||||
wrapOut atomic.Pointer[wrapWriter]
|
||||
wrapErr atomic.Pointer[wrapWriter]
|
||||
|
||||
isPrompting bool // true when prompt written and waiting for input
|
||||
|
||||
history *opHistory
|
||||
search *opSearch
|
||||
completer *opCompleter
|
||||
vim *opVim
|
||||
undo *opUndo
|
||||
}
|
||||
|
||||
func (o *operation) SetBuffer(what string) {
|
||||
o.buf.SetNoRefresh([]rune(what))
|
||||
}
|
||||
|
||||
type wrapWriter struct {
|
||||
o *operation
|
||||
target io.Writer
|
||||
}
|
||||
|
||||
func (w *wrapWriter) Write(b []byte) (int, error) {
|
||||
return w.o.write(w.target, b)
|
||||
}
|
||||
|
||||
func (o *operation) write(target io.Writer, b []byte) (int, error) {
|
||||
o.m.Lock()
|
||||
defer o.m.Unlock()
|
||||
|
||||
if !o.isPrompting {
|
||||
return target.Write(b)
|
||||
}
|
||||
|
||||
var (
|
||||
n int
|
||||
err error
|
||||
)
|
||||
o.buf.Refresh(func() {
|
||||
n, err = target.Write(b)
|
||||
// Adjust the prompt start position by b
|
||||
rout := runes.ColorFilter([]rune(string(b[:])))
|
||||
tWidth, _ := o.t.GetWidthHeight()
|
||||
sp := runes.SplitByLine(rout, []rune{}, o.buf.ppos, tWidth, 1)
|
||||
if len(sp) > 1 {
|
||||
o.buf.ppos = len(sp[len(sp)-1])
|
||||
} else {
|
||||
o.buf.ppos += len(rout)
|
||||
}
|
||||
})
|
||||
|
||||
o.search.RefreshIfNeeded()
|
||||
if o.completer.IsInCompleteMode() {
|
||||
o.completer.CompleteRefresh()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func newOperation(t *terminal) *operation {
|
||||
cfg := t.GetConfig()
|
||||
op := &operation{
|
||||
t: t,
|
||||
buf: newRuneBuffer(t),
|
||||
}
|
||||
op.SetConfig(cfg)
|
||||
op.vim = newVimMode(op)
|
||||
op.completer = newOpCompleter(op.buf.w, op)
|
||||
cfg.FuncOnWidthChanged(t.OnSizeChange)
|
||||
return op
|
||||
}
|
||||
|
||||
func (o *operation) GetConfig() *Config {
|
||||
return o.t.GetConfig()
|
||||
}
|
||||
|
||||
func (o *operation) readline(deadline chan struct{}) ([]rune, error) {
|
||||
isTyping := false // don't add new undo entries during normal typing
|
||||
|
||||
for {
|
||||
keepInSearchMode := false
|
||||
keepInCompleteMode := false
|
||||
r, err := o.t.GetRune(deadline)
|
||||
|
||||
if cfg := o.GetConfig(); cfg.FuncFilterInputRune != nil && err == nil {
|
||||
var process bool
|
||||
r, process = cfg.FuncFilterInputRune(r)
|
||||
if !process {
|
||||
o.buf.Refresh(nil) // to refresh the line
|
||||
continue // ignore this rune
|
||||
}
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
if o.buf.Len() == 0 {
|
||||
o.buf.Clean()
|
||||
return nil, io.EOF
|
||||
} else {
|
||||
// if stdin got io.EOF and there is something left in buffer,
|
||||
// let's flush them by sending CharEnter.
|
||||
// And we will got io.EOF int next loop.
|
||||
r = CharEnter
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isUpdateHistory := true
|
||||
|
||||
if o.completer.IsInCompleteSelectMode() {
|
||||
keepInCompleteMode = o.completer.HandleCompleteSelect(r)
|
||||
if keepInCompleteMode {
|
||||
continue
|
||||
}
|
||||
|
||||
o.buf.Refresh(nil)
|
||||
switch r {
|
||||
case CharEnter, CharCtrlJ:
|
||||
o.history.Update(o.buf.Runes(), false)
|
||||
fallthrough
|
||||
case CharInterrupt:
|
||||
fallthrough
|
||||
case CharBell:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if o.vim.IsEnableVimMode() {
|
||||
r = o.vim.HandleVim(r, func() rune {
|
||||
r, err := o.t.GetRune(deadline)
|
||||
if err == nil {
|
||||
return r
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
if r == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var result []rune
|
||||
|
||||
isTypingRune := false
|
||||
|
||||
switch r {
|
||||
case CharBell:
|
||||
if o.search.IsSearchMode() {
|
||||
o.search.ExitSearchMode(true)
|
||||
o.buf.Refresh(nil)
|
||||
}
|
||||
if o.completer.IsInCompleteMode() {
|
||||
o.completer.ExitCompleteMode(true)
|
||||
o.buf.Refresh(nil)
|
||||
}
|
||||
case CharBckSearch:
|
||||
if !o.search.SearchMode(searchDirectionBackward) {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
keepInSearchMode = true
|
||||
case CharCtrlU:
|
||||
o.undo.add()
|
||||
o.buf.KillFront()
|
||||
case CharFwdSearch:
|
||||
if !o.search.SearchMode(searchDirectionForward) {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
keepInSearchMode = true
|
||||
case CharKill:
|
||||
o.undo.add()
|
||||
o.buf.Kill()
|
||||
keepInCompleteMode = true
|
||||
case MetaForward:
|
||||
o.buf.MoveToNextWord()
|
||||
case CharTranspose:
|
||||
o.undo.add()
|
||||
o.buf.Transpose()
|
||||
case MetaBackward:
|
||||
o.buf.MoveToPrevWord()
|
||||
case MetaDelete:
|
||||
o.undo.add()
|
||||
o.buf.DeleteWord()
|
||||
case CharLineStart:
|
||||
o.buf.MoveToLineStart()
|
||||
case CharLineEnd:
|
||||
o.buf.MoveToLineEnd()
|
||||
case CharBackspace, CharCtrlH:
|
||||
o.undo.add()
|
||||
if o.search.IsSearchMode() {
|
||||
o.search.SearchBackspace()
|
||||
keepInSearchMode = true
|
||||
break
|
||||
}
|
||||
|
||||
if o.buf.Len() == 0 {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
o.buf.Backspace()
|
||||
case CharCtrlZ:
|
||||
if !platform.IsWindows {
|
||||
o.buf.Clean()
|
||||
o.t.SleepToResume()
|
||||
o.Refresh()
|
||||
}
|
||||
case CharCtrlL:
|
||||
clearScreen(o.t)
|
||||
o.buf.SetOffset(cursorPosition{1, 1})
|
||||
o.Refresh()
|
||||
case MetaBackspace, CharCtrlW:
|
||||
o.undo.add()
|
||||
o.buf.BackEscapeWord()
|
||||
case MetaShiftTab:
|
||||
// no-op
|
||||
case CharCtrlY:
|
||||
o.buf.Yank()
|
||||
case CharCtrl_:
|
||||
o.undo.undo()
|
||||
case CharEnter, CharCtrlJ:
|
||||
if o.search.IsSearchMode() {
|
||||
o.search.ExitSearchMode(false)
|
||||
}
|
||||
if o.completer.IsInCompleteMode() {
|
||||
o.completer.ExitCompleteMode(true)
|
||||
o.buf.Refresh(nil)
|
||||
}
|
||||
o.buf.MoveToLineEnd()
|
||||
var data []rune
|
||||
o.buf.WriteRune('\n')
|
||||
data = o.buf.Reset()
|
||||
data = data[:len(data)-1] // trim \n
|
||||
result = data
|
||||
if !o.GetConfig().DisableAutoSaveHistory {
|
||||
// ignore IO error
|
||||
_ = o.history.New(data)
|
||||
} else {
|
||||
isUpdateHistory = false
|
||||
}
|
||||
o.undo.init()
|
||||
case CharBackward:
|
||||
o.buf.MoveBackward()
|
||||
case CharForward:
|
||||
o.buf.MoveForward()
|
||||
case CharPrev:
|
||||
buf := o.history.Prev()
|
||||
if buf != nil {
|
||||
o.buf.Set(buf)
|
||||
o.undo.init()
|
||||
} else {
|
||||
o.t.Bell()
|
||||
}
|
||||
case CharNext:
|
||||
buf, ok := o.history.Next()
|
||||
if ok {
|
||||
o.buf.Set(buf)
|
||||
o.undo.init()
|
||||
} else {
|
||||
o.t.Bell()
|
||||
}
|
||||
case MetaDeleteKey, CharEOT:
|
||||
o.undo.add()
|
||||
// on Delete key or Ctrl-D, attempt to delete a character:
|
||||
if o.buf.Len() > 0 || !o.IsNormalMode() {
|
||||
if !o.buf.Delete() {
|
||||
o.t.Bell()
|
||||
}
|
||||
break
|
||||
}
|
||||
if r != CharEOT {
|
||||
break
|
||||
}
|
||||
// Ctrl-D on an empty buffer: treated as EOF
|
||||
o.buf.WriteString(o.GetConfig().EOFPrompt + "\n")
|
||||
o.buf.Reset()
|
||||
isUpdateHistory = false
|
||||
o.history.Revert()
|
||||
o.buf.Clean()
|
||||
return nil, io.EOF
|
||||
case CharInterrupt:
|
||||
if o.search.IsSearchMode() {
|
||||
o.search.ExitSearchMode(true)
|
||||
break
|
||||
}
|
||||
if o.completer.IsInCompleteMode() {
|
||||
o.completer.ExitCompleteMode(true)
|
||||
o.buf.Refresh(nil)
|
||||
break
|
||||
}
|
||||
o.buf.MoveToLineEnd()
|
||||
o.buf.Refresh(nil)
|
||||
hint := o.GetConfig().InterruptPrompt + "\n"
|
||||
o.buf.WriteString(hint)
|
||||
remain := o.buf.Reset()
|
||||
remain = remain[:len(remain)-len([]rune(hint))]
|
||||
isUpdateHistory = false
|
||||
o.history.Revert()
|
||||
return nil, ErrInterrupt
|
||||
case CharTab:
|
||||
if o.GetConfig().AutoComplete != nil {
|
||||
if o.completer.OnComplete() {
|
||||
if o.completer.IsInCompleteMode() {
|
||||
keepInCompleteMode = true
|
||||
continue // redraw is done, loop
|
||||
}
|
||||
} else {
|
||||
o.t.Bell()
|
||||
}
|
||||
o.buf.Refresh(nil)
|
||||
break
|
||||
} // else: process as a normal input character
|
||||
fallthrough
|
||||
default:
|
||||
isTypingRune = true
|
||||
if !isTyping {
|
||||
o.undo.add()
|
||||
}
|
||||
if o.search.IsSearchMode() {
|
||||
o.search.SearchChar(r)
|
||||
keepInSearchMode = true
|
||||
break
|
||||
}
|
||||
o.buf.WriteRune(r)
|
||||
if o.completer.IsInCompleteMode() {
|
||||
o.completer.OnComplete()
|
||||
if o.completer.IsInCompleteMode() {
|
||||
keepInCompleteMode = true
|
||||
} else {
|
||||
o.buf.Refresh(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isTyping = isTypingRune
|
||||
|
||||
// suppress the Listener callback if we received Enter or similar and are
|
||||
// submitting the result, since the buffer has already been cleared:
|
||||
if result == nil {
|
||||
if listener := o.GetConfig().Listener; listener != nil {
|
||||
newLine, newPos, ok := listener(o.buf.Runes(), o.buf.Pos(), r)
|
||||
if ok {
|
||||
o.buf.SetWithIdx(newPos, newLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
o.m.Lock()
|
||||
if !keepInSearchMode && o.search.IsSearchMode() {
|
||||
o.search.ExitSearchMode(false)
|
||||
o.buf.Refresh(nil)
|
||||
o.undo.init()
|
||||
} else if o.completer.IsInCompleteMode() {
|
||||
if !keepInCompleteMode {
|
||||
o.completer.ExitCompleteMode(false)
|
||||
o.refresh()
|
||||
o.undo.init()
|
||||
} else {
|
||||
o.buf.Refresh(nil)
|
||||
o.completer.CompleteRefresh()
|
||||
}
|
||||
}
|
||||
if isUpdateHistory && !o.search.IsSearchMode() {
|
||||
// it will cause null history
|
||||
o.history.Update(o.buf.Runes(), false)
|
||||
}
|
||||
o.m.Unlock()
|
||||
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *operation) Stderr() io.Writer {
|
||||
return o.wrapErr.Load()
|
||||
}
|
||||
|
||||
func (o *operation) Stdout() io.Writer {
|
||||
return o.wrapOut.Load()
|
||||
}
|
||||
|
||||
func (o *operation) String() (string, error) {
|
||||
r, err := o.Runes()
|
||||
return string(r), err
|
||||
}
|
||||
|
||||
func (o *operation) Runes() ([]rune, error) {
|
||||
o.t.EnterRawMode()
|
||||
defer o.t.ExitRawMode()
|
||||
|
||||
cfg := o.GetConfig()
|
||||
listener := cfg.Listener
|
||||
if listener != nil {
|
||||
listener(nil, 0, 0)
|
||||
}
|
||||
|
||||
// Before writing the prompt and starting to read, get a lock
|
||||
// so we don't race with wrapWriter trying to write and refresh.
|
||||
o.m.Lock()
|
||||
o.isPrompting = true
|
||||
// Query cursor position before printing the prompt as there
|
||||
// may be existing text on the same line that ideally we don't
|
||||
// want to overwrite and cause prompt to jump left.
|
||||
o.getAndSetOffset(nil)
|
||||
o.buf.Print() // print prompt & buffer contents
|
||||
// Prompt written safely, unlock until read completes and then
|
||||
// lock again to unset.
|
||||
o.m.Unlock()
|
||||
|
||||
if cfg.Undo {
|
||||
o.undo = newOpUndo(o)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
o.m.Lock()
|
||||
o.isPrompting = false
|
||||
o.buf.SetOffset(cursorPosition{1, 1})
|
||||
o.m.Unlock()
|
||||
}()
|
||||
|
||||
return o.readline(nil)
|
||||
}
|
||||
|
||||
func (o *operation) getAndSetOffset(deadline chan struct{}) {
|
||||
if !o.GetConfig().isInteractive {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle lineedge cases where existing text before before
|
||||
// the prompt is printed would leave us at the right edge of
|
||||
// the screen but the next character would actually be printed
|
||||
// at the beginning of the next line.
|
||||
// TODO ???
|
||||
o.t.Write([]byte(" \b"))
|
||||
|
||||
if offset, err := o.t.GetCursorPosition(deadline); err == nil {
|
||||
o.buf.SetOffset(offset)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *operation) GenPasswordConfig() *Config {
|
||||
baseConfig := o.GetConfig()
|
||||
return &Config{
|
||||
EnableMask: true,
|
||||
InterruptPrompt: "\n",
|
||||
EOFPrompt: "\n",
|
||||
HistoryLimit: -1,
|
||||
|
||||
Stdin: baseConfig.Stdin,
|
||||
Stdout: baseConfig.Stdout,
|
||||
Stderr: baseConfig.Stderr,
|
||||
|
||||
FuncIsTerminal: baseConfig.FuncIsTerminal,
|
||||
FuncMakeRaw: baseConfig.FuncMakeRaw,
|
||||
FuncExitRaw: baseConfig.FuncExitRaw,
|
||||
FuncOnWidthChanged: baseConfig.FuncOnWidthChanged,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *operation) ReadLineWithConfig(cfg *Config) (string, error) {
|
||||
backupCfg, err := o.SetConfig(cfg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
o.SetConfig(backupCfg)
|
||||
}()
|
||||
return o.String()
|
||||
}
|
||||
|
||||
func (o *operation) SetTitle(t string) {
|
||||
o.t.Write([]byte("\033[2;" + t + "\007"))
|
||||
}
|
||||
|
||||
func (o *operation) Slice() ([]byte, error) {
|
||||
r, err := o.Runes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(string(r)), nil
|
||||
}
|
||||
|
||||
func (o *operation) Close() {
|
||||
o.history.Close()
|
||||
}
|
||||
|
||||
func (o *operation) IsNormalMode() bool {
|
||||
return !o.completer.IsInCompleteMode() && !o.search.IsSearchMode()
|
||||
}
|
||||
|
||||
func (op *operation) SetConfig(cfg *Config) (*Config, error) {
|
||||
op.m.Lock()
|
||||
defer op.m.Unlock()
|
||||
old := op.t.GetConfig()
|
||||
if err := cfg.init(); err != nil {
|
||||
return old, err
|
||||
}
|
||||
|
||||
// install the config in its canonical location (inside terminal):
|
||||
op.t.SetConfig(cfg)
|
||||
|
||||
op.wrapOut.Store(&wrapWriter{target: cfg.Stdout, o: op})
|
||||
op.wrapErr.Store(&wrapWriter{target: cfg.Stderr, o: op})
|
||||
|
||||
if op.history == nil {
|
||||
op.history = newOpHistory(op)
|
||||
}
|
||||
if op.search == nil {
|
||||
op.search = newOpSearch(op.buf.w, op.buf, op.history)
|
||||
}
|
||||
|
||||
if cfg.AutoComplete != nil && op.completer == nil {
|
||||
op.completer = newOpCompleter(op.buf.w, op)
|
||||
}
|
||||
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func (o *operation) ResetHistory() {
|
||||
o.history.Reset()
|
||||
}
|
||||
|
||||
func (o *operation) SaveToHistory(content string) error {
|
||||
return o.history.New([]rune(content))
|
||||
}
|
||||
|
||||
func (o *operation) Refresh() {
|
||||
o.m.Lock()
|
||||
defer o.m.Unlock()
|
||||
o.refresh()
|
||||
}
|
||||
|
||||
func (o *operation) refresh() {
|
||||
if o.isPrompting {
|
||||
o.buf.Refresh(nil)
|
||||
}
|
||||
}
|
319
vendor/github.com/ergochat/readline/readline.go
generated
vendored
Normal file
319
vendor/github.com/ergochat/readline/readline.go
generated
vendored
Normal file
|
@ -0,0 +1,319 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/ergochat/readline/internal/platform"
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
terminal *terminal
|
||||
operation *operation
|
||||
|
||||
closeOnce sync.Once
|
||||
closeErr error
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// Prompt is the input prompt (ANSI escape sequences are supported on all platforms)
|
||||
Prompt string
|
||||
|
||||
// HistoryFile is the path to the file where persistent history will be stored
|
||||
// (empty string disables).
|
||||
HistoryFile string
|
||||
// HistoryLimit is the maximum number of history entries to store. If it is 0
|
||||
// or unset, the default value is 500; set to -1 to disable.
|
||||
HistoryLimit int
|
||||
DisableAutoSaveHistory bool
|
||||
// HistorySearchFold enables case-insensitive history searching.
|
||||
HistorySearchFold bool
|
||||
|
||||
// AutoComplete defines the tab-completion behavior. See the documentation for
|
||||
// the AutoCompleter interface for details.
|
||||
AutoComplete AutoCompleter
|
||||
|
||||
// Listener is an optional callback to intercept keypresses.
|
||||
Listener Listener
|
||||
|
||||
// Painter is an optional callback to rewrite the buffer for display.
|
||||
Painter Painter
|
||||
|
||||
// FuncFilterInputRune is an optional callback to translate keyboard inputs;
|
||||
// it takes in the input rune and returns (translation, ok). If ok is false,
|
||||
// the rune is skipped.
|
||||
FuncFilterInputRune func(rune) (rune, bool)
|
||||
|
||||
// VimMode enables Vim-style insert mode by default.
|
||||
VimMode bool
|
||||
|
||||
InterruptPrompt string
|
||||
EOFPrompt string
|
||||
|
||||
EnableMask bool
|
||||
MaskRune rune
|
||||
|
||||
// Undo controls whether to maintain an undo buffer (if enabled,
|
||||
// Ctrl+_ will undo the previous action).
|
||||
Undo bool
|
||||
|
||||
// These fields allow customizing terminal handling. Most clients should ignore them.
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
FuncIsTerminal func() bool
|
||||
FuncMakeRaw func() error
|
||||
FuncExitRaw func() error
|
||||
FuncGetSize func() (width int, height int)
|
||||
FuncOnWidthChanged func(func())
|
||||
|
||||
// private fields
|
||||
inited bool
|
||||
isInteractive bool
|
||||
}
|
||||
|
||||
func (c *Config) init() error {
|
||||
if c.inited {
|
||||
return nil
|
||||
}
|
||||
c.inited = true
|
||||
if c.Stdin == nil {
|
||||
c.Stdin = os.Stdin
|
||||
}
|
||||
|
||||
if c.Stdout == nil {
|
||||
c.Stdout = os.Stdout
|
||||
}
|
||||
if c.Stderr == nil {
|
||||
c.Stderr = os.Stderr
|
||||
}
|
||||
if c.HistoryLimit == 0 {
|
||||
c.HistoryLimit = 500
|
||||
}
|
||||
|
||||
if c.InterruptPrompt == "" {
|
||||
c.InterruptPrompt = "^C"
|
||||
} else if c.InterruptPrompt == "\n" {
|
||||
c.InterruptPrompt = ""
|
||||
}
|
||||
if c.EOFPrompt == "" {
|
||||
c.EOFPrompt = "^D"
|
||||
} else if c.EOFPrompt == "\n" {
|
||||
c.EOFPrompt = ""
|
||||
}
|
||||
|
||||
if c.FuncGetSize == nil {
|
||||
c.FuncGetSize = platform.GetScreenSize
|
||||
}
|
||||
if c.FuncIsTerminal == nil {
|
||||
c.FuncIsTerminal = platform.DefaultIsTerminal
|
||||
}
|
||||
rm := new(rawModeHandler)
|
||||
if c.FuncMakeRaw == nil {
|
||||
c.FuncMakeRaw = rm.Enter
|
||||
}
|
||||
if c.FuncExitRaw == nil {
|
||||
c.FuncExitRaw = rm.Exit
|
||||
}
|
||||
if c.FuncOnWidthChanged == nil {
|
||||
c.FuncOnWidthChanged = platform.DefaultOnSizeChanged
|
||||
}
|
||||
if c.Painter == nil {
|
||||
c.Painter = defaultPainter
|
||||
}
|
||||
|
||||
c.isInteractive = c.FuncIsTerminal()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewFromConfig creates a readline instance from the specified configuration.
|
||||
func NewFromConfig(cfg *Config) (*Instance, error) {
|
||||
if err := cfg.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := newTerminal(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o := newOperation(t)
|
||||
return &Instance{
|
||||
terminal: t,
|
||||
operation: o,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewEx is an alias for NewFromConfig, for compatibility.
|
||||
var NewEx = NewFromConfig
|
||||
|
||||
// New creates a readline instance with default configuration.
|
||||
func New(prompt string) (*Instance, error) {
|
||||
return NewFromConfig(&Config{Prompt: prompt})
|
||||
}
|
||||
|
||||
func (i *Instance) ResetHistory() {
|
||||
i.operation.ResetHistory()
|
||||
}
|
||||
|
||||
func (i *Instance) SetPrompt(s string) {
|
||||
cfg := i.GetConfig()
|
||||
cfg.Prompt = s
|
||||
i.SetConfig(cfg)
|
||||
}
|
||||
|
||||
// readline will refresh automatic when write through Stdout()
|
||||
func (i *Instance) Stdout() io.Writer {
|
||||
return i.operation.Stdout()
|
||||
}
|
||||
|
||||
// readline will refresh automatic when write through Stdout()
|
||||
func (i *Instance) Stderr() io.Writer {
|
||||
return i.operation.Stderr()
|
||||
}
|
||||
|
||||
// switch VimMode in runtime
|
||||
func (i *Instance) SetVimMode(on bool) {
|
||||
cfg := i.GetConfig()
|
||||
cfg.VimMode = on
|
||||
i.SetConfig(cfg)
|
||||
}
|
||||
|
||||
func (i *Instance) IsVimMode() bool {
|
||||
return i.operation.vim.IsEnableVimMode()
|
||||
}
|
||||
|
||||
// GeneratePasswordConfig generates a suitable Config for reading passwords;
|
||||
// this config can be modified and then used with ReadLineWithConfig, or
|
||||
// SetConfig.
|
||||
func (i *Instance) GeneratePasswordConfig() *Config {
|
||||
return i.operation.GenPasswordConfig()
|
||||
}
|
||||
|
||||
func (i *Instance) ReadLineWithConfig(cfg *Config) (string, error) {
|
||||
return i.operation.ReadLineWithConfig(cfg)
|
||||
}
|
||||
|
||||
func (i *Instance) ReadPassword(prompt string) ([]byte, error) {
|
||||
if result, err := i.ReadLineWithConfig(i.GeneratePasswordConfig()); err == nil {
|
||||
return []byte(result), nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// ReadLine reads a line from the configured input source, allowing inline editing.
|
||||
// The returned error is either nil, io.EOF, or readline.ErrInterrupt.
|
||||
func (i *Instance) ReadLine() (string, error) {
|
||||
return i.operation.String()
|
||||
}
|
||||
|
||||
// Readline is an alias for ReadLine, for compatibility.
|
||||
func (i *Instance) Readline() (string, error) {
|
||||
return i.ReadLine()
|
||||
}
|
||||
|
||||
// SetDefault prefills a default value for the next call to Readline()
|
||||
// or related methods. The value will appear after the prompt for the user
|
||||
// to edit, with the cursor at the end of the line.
|
||||
func (i *Instance) SetDefault(defaultValue string) {
|
||||
i.operation.SetBuffer(defaultValue)
|
||||
}
|
||||
|
||||
func (i *Instance) ReadLineWithDefault(defaultValue string) (string, error) {
|
||||
i.SetDefault(defaultValue)
|
||||
return i.operation.String()
|
||||
}
|
||||
|
||||
// SaveToHistory adds a string to the instance's stored history. This is particularly
|
||||
// relevant when DisableAutoSaveHistory is configured.
|
||||
func (i *Instance) SaveToHistory(content string) error {
|
||||
return i.operation.SaveToHistory(content)
|
||||
}
|
||||
|
||||
// same as readline
|
||||
func (i *Instance) ReadSlice() ([]byte, error) {
|
||||
return i.operation.Slice()
|
||||
}
|
||||
|
||||
// Close() closes the readline instance, cleaning up state changes to the
|
||||
// terminal. It interrupts any concurrent Readline() operation, so it can be
|
||||
// asynchronously or from a signal handler. It is concurrency-safe and
|
||||
// idempotent, so it can be called multiple times.
|
||||
func (i *Instance) Close() error {
|
||||
i.closeOnce.Do(func() {
|
||||
// TODO reorder these?
|
||||
i.operation.Close()
|
||||
i.closeErr = i.terminal.Close()
|
||||
})
|
||||
return i.closeErr
|
||||
}
|
||||
|
||||
// CaptureExitSignal registers handlers for common exit signals that will
|
||||
// close the readline instance.
|
||||
func (i *Instance) CaptureExitSignal() {
|
||||
cSignal := make(chan os.Signal, 1)
|
||||
// TODO handle other signals in a portable way?
|
||||
signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
for range cSignal {
|
||||
i.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Write writes output to the screen, redrawing the prompt and buffer
|
||||
// as needed.
|
||||
func (i *Instance) Write(b []byte) (int, error) {
|
||||
return i.Stdout().Write(b)
|
||||
}
|
||||
|
||||
// GetConfig returns a copy of the current config.
|
||||
func (i *Instance) GetConfig() *Config {
|
||||
cfg := i.operation.GetConfig()
|
||||
result := new(Config)
|
||||
*result = *cfg
|
||||
return result
|
||||
}
|
||||
|
||||
// SetConfig modifies the current instance's config.
|
||||
func (i *Instance) SetConfig(cfg *Config) error {
|
||||
_, err := i.operation.SetConfig(cfg)
|
||||
return err
|
||||
}
|
||||
|
||||
// Refresh redraws the input buffer on screen.
|
||||
func (i *Instance) Refresh() {
|
||||
i.operation.Refresh()
|
||||
}
|
||||
|
||||
// DisableHistory disables the saving of input lines in history.
|
||||
func (i *Instance) DisableHistory() {
|
||||
i.operation.history.Disable()
|
||||
}
|
||||
|
||||
// EnableHistory enables the saving of input lines in history.
|
||||
func (i *Instance) EnableHistory() {
|
||||
i.operation.history.Enable()
|
||||
}
|
||||
|
||||
// ClearScreen clears the screen.
|
||||
func (i *Instance) ClearScreen() {
|
||||
clearScreen(i.operation.Stdout())
|
||||
}
|
||||
|
||||
// Painter is a callback type to allow modifying the buffer before it is rendered
|
||||
// on screen, for example, to implement real-time syntax highlighting.
|
||||
type Painter func(line []rune, pos int) []rune
|
||||
|
||||
func defaultPainter(line []rune, _ int) []rune {
|
||||
return line
|
||||
}
|
||||
|
||||
// Listener is a callback type to listen for keypresses while the line is being
|
||||
// edited. It is invoked initially with (nil, 0, 0), and then subsequently for
|
||||
// any keypress until (but not including) the newline/enter keypress that completes
|
||||
// the input.
|
||||
type Listener func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
|
684
vendor/github.com/ergochat/readline/runebuf.go
generated
vendored
Normal file
684
vendor/github.com/ergochat/readline/runebuf.go
generated
vendored
Normal file
|
@ -0,0 +1,684 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ergochat/readline/internal/runes"
|
||||
)
|
||||
|
||||
type runeBuffer struct {
|
||||
buf []rune
|
||||
idx int
|
||||
w *terminal
|
||||
|
||||
cpos cursorPosition
|
||||
ppos int // prompt start position (0 == column 1)
|
||||
|
||||
lastKill []rune
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (r *runeBuffer) pushKill(text []rune) {
|
||||
r.lastKill = append([]rune{}, text...)
|
||||
}
|
||||
|
||||
func newRuneBuffer(w *terminal) *runeBuffer {
|
||||
rb := &runeBuffer{
|
||||
w: w,
|
||||
}
|
||||
return rb
|
||||
}
|
||||
|
||||
func (r *runeBuffer) CurrentWidth(x int) int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return runes.WidthAll(r.buf[:x])
|
||||
}
|
||||
|
||||
func (r *runeBuffer) PromptLen() int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return r.promptLen()
|
||||
}
|
||||
|
||||
func (r *runeBuffer) promptLen() int {
|
||||
return runes.WidthAll(runes.ColorFilter([]rune(r.prompt())))
|
||||
}
|
||||
|
||||
func (r *runeBuffer) RuneSlice(i int) []rune {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if i > 0 {
|
||||
rs := make([]rune, i)
|
||||
copy(rs, r.buf[r.idx:r.idx+i])
|
||||
return rs
|
||||
}
|
||||
rs := make([]rune, -i)
|
||||
copy(rs, r.buf[r.idx+i:r.idx])
|
||||
return rs
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Runes() []rune {
|
||||
r.Lock()
|
||||
newr := make([]rune, len(r.buf))
|
||||
copy(newr, r.buf)
|
||||
r.Unlock()
|
||||
return newr
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Pos() int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return r.idx
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Len() int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return len(r.buf)
|
||||
}
|
||||
|
||||
func (r *runeBuffer) MoveToLineStart() {
|
||||
r.Refresh(func() {
|
||||
r.idx = 0
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) MoveBackward() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
r.idx--
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) WriteString(s string) {
|
||||
r.WriteRunes([]rune(s))
|
||||
}
|
||||
|
||||
func (r *runeBuffer) WriteRune(s rune) {
|
||||
r.WriteRunes([]rune{s})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) getConfig() *Config {
|
||||
return r.w.GetConfig()
|
||||
}
|
||||
|
||||
func (r *runeBuffer) isInteractive() bool {
|
||||
return r.getConfig().isInteractive
|
||||
}
|
||||
|
||||
func (r *runeBuffer) prompt() string {
|
||||
return r.getConfig().Prompt
|
||||
}
|
||||
|
||||
func (r *runeBuffer) WriteRunes(s []rune) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if r.idx == len(r.buf) {
|
||||
// cursor is already at end of buf data so just call
|
||||
// append instead of refesh to save redrawing.
|
||||
r.buf = append(r.buf, s...)
|
||||
r.idx += len(s)
|
||||
if r.isInteractive() {
|
||||
r.append(s)
|
||||
}
|
||||
} else {
|
||||
// writing into the data somewhere so do a refresh
|
||||
r.refresh(func() {
|
||||
tail := append(s, r.buf[r.idx:]...)
|
||||
r.buf = append(r.buf[:r.idx], tail...)
|
||||
r.idx += len(s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runeBuffer) MoveForward() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
r.idx++
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) IsCursorInEnd() bool {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return r.idx == len(r.buf)
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Replace(ch rune) {
|
||||
r.Refresh(func() {
|
||||
r.buf[r.idx] = ch
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Erase() {
|
||||
r.Refresh(func() {
|
||||
r.idx = 0
|
||||
r.pushKill(r.buf[:])
|
||||
r.buf = r.buf[:0]
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Delete() (success bool) {
|
||||
r.Refresh(func() {
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
r.pushKill(r.buf[r.idx : r.idx+1])
|
||||
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
|
||||
success = true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r *runeBuffer) DeleteWord() {
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
init := r.idx
|
||||
for init < len(r.buf) && runes.IsWordBreak(r.buf[init]) {
|
||||
init++
|
||||
}
|
||||
for i := init + 1; i < len(r.buf); i++ {
|
||||
if !runes.IsWordBreak(r.buf[i]) && runes.IsWordBreak(r.buf[i-1]) {
|
||||
r.pushKill(r.buf[r.idx : i-1])
|
||||
r.Refresh(func() {
|
||||
r.buf = append(r.buf[:r.idx], r.buf[i-1:]...)
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
r.Kill()
|
||||
}
|
||||
|
||||
func (r *runeBuffer) MoveToPrevWord() (success bool) {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := r.idx - 1; i > 0; i-- {
|
||||
if !runes.IsWordBreak(r.buf[i]) && runes.IsWordBreak(r.buf[i-1]) {
|
||||
r.idx = i
|
||||
success = true
|
||||
return
|
||||
}
|
||||
}
|
||||
r.idx = 0
|
||||
success = true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r *runeBuffer) KillFront() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
length := len(r.buf) - r.idx
|
||||
r.pushKill(r.buf[:r.idx])
|
||||
copy(r.buf[:length], r.buf[r.idx:])
|
||||
r.idx = 0
|
||||
r.buf = r.buf[:length]
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Kill() {
|
||||
r.Refresh(func() {
|
||||
r.pushKill(r.buf[r.idx:])
|
||||
r.buf = r.buf[:r.idx]
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Transpose() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
// match the GNU Readline behavior, Ctrl-T at the start of the line
|
||||
// is a no-op:
|
||||
return
|
||||
}
|
||||
|
||||
// OK, we have at least one character behind us:
|
||||
if r.idx < len(r.buf) {
|
||||
// swap the character in front of us with the one behind us
|
||||
r.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx]
|
||||
// advance the cursor
|
||||
r.idx++
|
||||
} else if r.idx == len(r.buf) && len(r.buf) >= 2 {
|
||||
// swap the two characters behind us
|
||||
r.buf[r.idx-2], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx-2]
|
||||
// leave the cursor in place since there's nowhere to go
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) MoveToNextWord() {
|
||||
r.Refresh(func() {
|
||||
for i := r.idx + 1; i < len(r.buf); i++ {
|
||||
if !runes.IsWordBreak(r.buf[i]) && runes.IsWordBreak(r.buf[i-1]) {
|
||||
r.idx = i
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.idx = len(r.buf)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) MoveToEndWord() {
|
||||
r.Refresh(func() {
|
||||
// already at the end, so do nothing
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
// if we are at the end of a word already, go to next
|
||||
if !runes.IsWordBreak(r.buf[r.idx]) && runes.IsWordBreak(r.buf[r.idx+1]) {
|
||||
r.idx++
|
||||
}
|
||||
|
||||
// keep going until at the end of a word
|
||||
for i := r.idx + 1; i < len(r.buf); i++ {
|
||||
if runes.IsWordBreak(r.buf[i]) && !runes.IsWordBreak(r.buf[i-1]) {
|
||||
r.idx = i - 1
|
||||
return
|
||||
}
|
||||
}
|
||||
r.idx = len(r.buf)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) BackEscapeWord() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
for i := r.idx - 1; i >= 0; i-- {
|
||||
if i == 0 || (runes.IsWordBreak(r.buf[i-1])) && !runes.IsWordBreak(r.buf[i]) {
|
||||
r.pushKill(r.buf[i:r.idx])
|
||||
r.buf = append(r.buf[:i], r.buf[r.idx:]...)
|
||||
r.idx = i
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.buf = r.buf[:0]
|
||||
r.idx = 0
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Yank() {
|
||||
if len(r.lastKill) == 0 {
|
||||
return
|
||||
}
|
||||
r.Refresh(func() {
|
||||
buf := make([]rune, 0, len(r.buf)+len(r.lastKill))
|
||||
buf = append(buf, r.buf[:r.idx]...)
|
||||
buf = append(buf, r.lastKill...)
|
||||
buf = append(buf, r.buf[r.idx:]...)
|
||||
r.buf = buf
|
||||
r.idx += len(r.lastKill)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Backspace() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.idx--
|
||||
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) MoveToLineEnd() {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
r.refresh(func() {
|
||||
r.idx = len(r.buf)
|
||||
})
|
||||
}
|
||||
|
||||
// LineCount returns number of lines the buffer takes as it appears in the terminal.
|
||||
func (r *runeBuffer) LineCount() int {
|
||||
sp := r.getSplitByLine(r.buf, 1)
|
||||
return len(sp)
|
||||
}
|
||||
|
||||
func (r *runeBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
|
||||
r.Refresh(func() {
|
||||
if reverse {
|
||||
for i := r.idx - 1; i >= 0; i-- {
|
||||
if r.buf[i] == ch {
|
||||
r.idx = i
|
||||
if prevChar {
|
||||
r.idx++
|
||||
}
|
||||
success = true
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
for i := r.idx + 1; i < len(r.buf); i++ {
|
||||
if r.buf[i] == ch {
|
||||
r.idx = i
|
||||
if prevChar {
|
||||
r.idx--
|
||||
}
|
||||
success = true
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r *runeBuffer) isInLineEdge() bool {
|
||||
sp := r.getSplitByLine(r.buf, 1)
|
||||
return len(sp[len(sp)-1]) == 0 // last line is 0 len
|
||||
}
|
||||
|
||||
func (r *runeBuffer) getSplitByLine(rs []rune, nextWidth int) [][]rune {
|
||||
tWidth, _ := r.w.GetWidthHeight()
|
||||
cfg := r.getConfig()
|
||||
if cfg.EnableMask {
|
||||
w := runes.Width(cfg.MaskRune)
|
||||
masked := []rune(strings.Repeat(string(cfg.MaskRune), len(rs)))
|
||||
return runes.SplitByLine(runes.ColorFilter([]rune(r.prompt())), masked, r.ppos, tWidth, w)
|
||||
} else {
|
||||
return runes.SplitByLine(runes.ColorFilter([]rune(r.prompt())), rs, r.ppos, tWidth, nextWidth)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runeBuffer) IdxLine(width int) int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return r.idxLine(width)
|
||||
}
|
||||
|
||||
func (r *runeBuffer) idxLine(width int) int {
|
||||
if width == 0 {
|
||||
return 0
|
||||
}
|
||||
nextWidth := 1
|
||||
if r.idx < len(r.buf) {
|
||||
nextWidth = runes.Width(r.buf[r.idx])
|
||||
}
|
||||
sp := r.getSplitByLine(r.buf[:r.idx], nextWidth)
|
||||
return len(sp) - 1
|
||||
}
|
||||
|
||||
func (r *runeBuffer) CursorLineCount() int {
|
||||
tWidth, _ := r.w.GetWidthHeight()
|
||||
return r.LineCount() - r.IdxLine(tWidth)
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Refresh(f func()) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
r.refresh(f)
|
||||
}
|
||||
|
||||
func (r *runeBuffer) refresh(f func()) {
|
||||
if !r.isInteractive() {
|
||||
if f != nil {
|
||||
f()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
r.clean()
|
||||
if f != nil {
|
||||
f()
|
||||
}
|
||||
r.print()
|
||||
}
|
||||
|
||||
func (r *runeBuffer) SetOffset(position cursorPosition) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
r.setOffset(position)
|
||||
}
|
||||
|
||||
func (r *runeBuffer) setOffset(cpos cursorPosition) {
|
||||
r.cpos = cpos
|
||||
tWidth, _ := r.w.GetWidthHeight()
|
||||
if cpos.col > 0 && cpos.col < tWidth {
|
||||
r.ppos = cpos.col - 1 // c should be 1..tWidth
|
||||
} else {
|
||||
r.ppos = 0
|
||||
}
|
||||
}
|
||||
|
||||
// append s to the end of the current output. append is called in
|
||||
// place of print() when clean() was avoided. As output is appended on
|
||||
// the end, the cursor also needs no extra adjustment.
|
||||
// NOTE: assumes len(s) >= 1 which should always be true for append.
|
||||
func (r *runeBuffer) append(s []rune) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
slen := len(s)
|
||||
cfg := r.getConfig()
|
||||
if cfg.EnableMask {
|
||||
if slen > 1 && cfg.MaskRune != 0 {
|
||||
// write a mask character for all runes except the last rune
|
||||
buf.WriteString(strings.Repeat(string(cfg.MaskRune), slen-1))
|
||||
}
|
||||
// for the last rune, write \n or mask it otherwise.
|
||||
if s[slen-1] == '\n' {
|
||||
buf.WriteRune('\n')
|
||||
} else if cfg.MaskRune != 0 {
|
||||
buf.WriteRune(cfg.MaskRune)
|
||||
}
|
||||
} else {
|
||||
for _, e := range cfg.Painter(s, slen) {
|
||||
if e == '\t' {
|
||||
buf.WriteString(strings.Repeat(" ", runes.TabWidth))
|
||||
} else {
|
||||
buf.WriteRune(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.isInLineEdge() {
|
||||
buf.WriteString(" \b")
|
||||
}
|
||||
r.w.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
// Print writes out the prompt and buffer contents at the current cursor position
|
||||
func (r *runeBuffer) Print() {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
if !r.isInteractive() {
|
||||
return
|
||||
}
|
||||
r.print()
|
||||
}
|
||||
|
||||
func (r *runeBuffer) print() {
|
||||
r.w.Write(r.output())
|
||||
}
|
||||
|
||||
func (r *runeBuffer) output() []byte {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.WriteString(r.prompt())
|
||||
buf.WriteString("\x1b[0K") // VT100 "Clear line from cursor right", see #38
|
||||
cfg := r.getConfig()
|
||||
if cfg.EnableMask && len(r.buf) > 0 {
|
||||
if cfg.MaskRune != 0 {
|
||||
buf.WriteString(strings.Repeat(string(cfg.MaskRune), len(r.buf)-1))
|
||||
}
|
||||
if r.buf[len(r.buf)-1] == '\n' {
|
||||
buf.WriteRune('\n')
|
||||
} else if cfg.MaskRune != 0 {
|
||||
buf.WriteRune(cfg.MaskRune)
|
||||
}
|
||||
} else {
|
||||
for _, e := range cfg.Painter(r.buf, r.idx) {
|
||||
if e == '\t' {
|
||||
buf.WriteString(strings.Repeat(" ", runes.TabWidth))
|
||||
} else {
|
||||
buf.WriteRune(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.isInLineEdge() {
|
||||
buf.WriteString(" \b")
|
||||
}
|
||||
// cursor position
|
||||
if len(r.buf) > r.idx {
|
||||
buf.Write(r.getBackspaceSequence())
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (r *runeBuffer) getBackspaceSequence() []byte {
|
||||
bcnt := len(r.buf) - r.idx // backwards count to index
|
||||
sp := r.getSplitByLine(r.buf, 1)
|
||||
|
||||
// Calculate how many lines up to the index line
|
||||
up := 0
|
||||
spi := len(sp) - 1
|
||||
for spi >= 0 {
|
||||
bcnt -= len(sp[spi])
|
||||
if bcnt <= 0 {
|
||||
break
|
||||
}
|
||||
up++
|
||||
spi--
|
||||
}
|
||||
|
||||
// Calculate what column the index should be set to
|
||||
column := 1
|
||||
if spi == 0 {
|
||||
column += r.ppos
|
||||
}
|
||||
for _, rune := range sp[spi] {
|
||||
if bcnt >= 0 {
|
||||
break
|
||||
}
|
||||
column += runes.Width(rune)
|
||||
bcnt++
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
if up > 0 {
|
||||
fmt.Fprintf(buf, "\033[%dA", up) // move cursor up to index line
|
||||
}
|
||||
fmt.Fprintf(buf, "\033[%dG", column) // move cursor to column
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (r *runeBuffer) CopyForUndo(prev []rune) (cur []rune, idx int, changed bool) {
|
||||
if runes.Equal(r.buf, prev) {
|
||||
return prev, r.idx, false
|
||||
} else {
|
||||
return runes.Copy(r.buf), r.idx, true
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Restore(buf []rune, idx int) {
|
||||
r.buf = buf
|
||||
r.idx = idx
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Reset() []rune {
|
||||
ret := runes.Copy(r.buf)
|
||||
r.buf = r.buf[:0]
|
||||
r.idx = 0
|
||||
return ret
|
||||
}
|
||||
|
||||
func (r *runeBuffer) calWidth(m int) int {
|
||||
if m > 0 {
|
||||
return runes.WidthAll(r.buf[r.idx : r.idx+m])
|
||||
}
|
||||
return runes.WidthAll(r.buf[r.idx+m : r.idx])
|
||||
}
|
||||
|
||||
func (r *runeBuffer) SetStyle(start, end int, style string) {
|
||||
if end < start {
|
||||
panic("end < start")
|
||||
}
|
||||
|
||||
// goto start
|
||||
move := start - r.idx
|
||||
if move > 0 {
|
||||
r.w.Write([]byte(string(r.buf[r.idx : r.idx+move])))
|
||||
} else {
|
||||
r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move)))
|
||||
}
|
||||
r.w.Write([]byte("\033[" + style + "m"))
|
||||
r.w.Write([]byte(string(r.buf[start:end])))
|
||||
r.w.Write([]byte("\033[0m"))
|
||||
// TODO: move back
|
||||
}
|
||||
|
||||
func (r *runeBuffer) SetWithIdx(idx int, buf []rune) {
|
||||
r.Refresh(func() {
|
||||
r.buf = buf
|
||||
r.idx = idx
|
||||
})
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Set(buf []rune) {
|
||||
r.SetWithIdx(len(buf), buf)
|
||||
}
|
||||
|
||||
func (r *runeBuffer) SetNoRefresh(buf []rune) {
|
||||
r.buf = buf
|
||||
r.idx = len(buf)
|
||||
}
|
||||
|
||||
func (r *runeBuffer) cleanOutput(w io.Writer, idxLine int) {
|
||||
buf := bufio.NewWriter(w)
|
||||
|
||||
tWidth, _ := r.w.GetWidthHeight()
|
||||
if tWidth == 0 {
|
||||
buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen()))
|
||||
buf.Write([]byte("\033[J"))
|
||||
} else {
|
||||
if idxLine > 0 {
|
||||
fmt.Fprintf(buf, "\033[%dA", idxLine) // move cursor up by idxLine
|
||||
}
|
||||
fmt.Fprintf(buf, "\033[%dG", r.ppos+1) // move cursor back to initial ppos position
|
||||
buf.Write([]byte("\033[J")) // clear from cursor to end of screen
|
||||
}
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *runeBuffer) Clean() {
|
||||
r.Lock()
|
||||
r.clean()
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *runeBuffer) clean() {
|
||||
tWidth, _ := r.w.GetWidthHeight()
|
||||
r.cleanWithIdxLine(r.idxLine(tWidth))
|
||||
}
|
||||
|
||||
func (r *runeBuffer) cleanWithIdxLine(idxLine int) {
|
||||
if !r.isInteractive() {
|
||||
return
|
||||
}
|
||||
r.cleanOutput(r.w, idxLine)
|
||||
}
|
189
vendor/github.com/ergochat/readline/search.go
generated
vendored
Normal file
189
vendor/github.com/ergochat/readline/search.go
generated
vendored
Normal file
|
@ -0,0 +1,189 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type searchState uint
|
||||
|
||||
const (
|
||||
searchStateFound searchState = iota
|
||||
searchStateFailing
|
||||
)
|
||||
|
||||
type searchDirection uint
|
||||
|
||||
const (
|
||||
searchDirectionForward searchDirection = iota
|
||||
searchDirectionBackward
|
||||
)
|
||||
|
||||
type opSearch struct {
|
||||
mutex sync.Mutex
|
||||
inMode bool
|
||||
state searchState
|
||||
dir searchDirection
|
||||
source *list.Element
|
||||
w *terminal
|
||||
buf *runeBuffer
|
||||
data []rune
|
||||
history *opHistory
|
||||
markStart int
|
||||
markEnd int
|
||||
}
|
||||
|
||||
func newOpSearch(w *terminal, buf *runeBuffer, history *opHistory) *opSearch {
|
||||
return &opSearch{
|
||||
w: w,
|
||||
buf: buf,
|
||||
history: history,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opSearch) IsSearchMode() bool {
|
||||
o.mutex.Lock()
|
||||
defer o.mutex.Unlock()
|
||||
return o.inMode
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchBackspace() {
|
||||
o.mutex.Lock()
|
||||
defer o.mutex.Unlock()
|
||||
if len(o.data) > 0 {
|
||||
o.data = o.data[:len(o.data)-1]
|
||||
o.search(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) {
|
||||
if o.dir == searchDirectionBackward {
|
||||
return o.history.FindBck(isNewSearch, o.data, o.buf.idx)
|
||||
}
|
||||
return o.history.FindFwd(isNewSearch, o.data, o.buf.idx)
|
||||
}
|
||||
|
||||
func (o *opSearch) search(isChange bool) bool {
|
||||
if len(o.data) == 0 {
|
||||
o.state = searchStateFound
|
||||
o.searchRefresh(-1)
|
||||
return true
|
||||
}
|
||||
idx, elem := o.findHistoryBy(isChange)
|
||||
if elem == nil {
|
||||
o.searchRefresh(-2)
|
||||
return false
|
||||
}
|
||||
o.history.current = elem
|
||||
|
||||
item := o.history.showItem(o.history.current.Value)
|
||||
start, end := 0, 0
|
||||
if o.dir == searchDirectionBackward {
|
||||
start, end = idx, idx+len(o.data)
|
||||
} else {
|
||||
start, end = idx, idx+len(o.data)
|
||||
idx += len(o.data)
|
||||
}
|
||||
o.buf.SetWithIdx(idx, item)
|
||||
o.markStart, o.markEnd = start, end
|
||||
o.searchRefresh(idx)
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchChar(r rune) {
|
||||
o.mutex.Lock()
|
||||
defer o.mutex.Unlock()
|
||||
|
||||
o.data = append(o.data, r)
|
||||
o.search(true)
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchMode(dir searchDirection) bool {
|
||||
o.mutex.Lock()
|
||||
defer o.mutex.Unlock()
|
||||
|
||||
tWidth, _ := o.w.GetWidthHeight()
|
||||
if tWidth == 0 {
|
||||
return false
|
||||
}
|
||||
alreadyInMode := o.inMode
|
||||
o.inMode = true
|
||||
o.dir = dir
|
||||
o.source = o.history.current
|
||||
if alreadyInMode {
|
||||
o.search(false)
|
||||
} else {
|
||||
o.searchRefresh(-1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *opSearch) ExitSearchMode(revert bool) {
|
||||
o.mutex.Lock()
|
||||
defer o.mutex.Unlock()
|
||||
|
||||
if revert {
|
||||
o.history.current = o.source
|
||||
var redrawValue []rune
|
||||
if o.history.current != nil {
|
||||
redrawValue = o.history.showItem(o.history.current.Value)
|
||||
}
|
||||
o.buf.Set(redrawValue)
|
||||
}
|
||||
o.markStart, o.markEnd = 0, 0
|
||||
o.state = searchStateFound
|
||||
o.inMode = false
|
||||
o.source = nil
|
||||
o.data = nil
|
||||
}
|
||||
|
||||
func (o *opSearch) searchRefresh(x int) {
|
||||
tWidth, _ := o.w.GetWidthHeight()
|
||||
if x == -2 {
|
||||
o.state = searchStateFailing
|
||||
} else if x >= 0 {
|
||||
o.state = searchStateFound
|
||||
}
|
||||
if x < 0 {
|
||||
x = o.buf.idx
|
||||
}
|
||||
x = o.buf.CurrentWidth(x)
|
||||
x += o.buf.PromptLen()
|
||||
x = x % tWidth
|
||||
|
||||
if o.markStart > 0 {
|
||||
o.buf.SetStyle(o.markStart, o.markEnd, "4")
|
||||
}
|
||||
|
||||
lineCnt := o.buf.CursorLineCount()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
|
||||
buf.WriteString("\033[J")
|
||||
if o.state == searchStateFailing {
|
||||
buf.WriteString("failing ")
|
||||
}
|
||||
if o.dir == searchDirectionBackward {
|
||||
buf.WriteString("bck")
|
||||
} else if o.dir == searchDirectionForward {
|
||||
buf.WriteString("fwd")
|
||||
}
|
||||
buf.WriteString("-i-search: ")
|
||||
buf.WriteString(string(o.data)) // keyword
|
||||
buf.WriteString("\033[4m \033[0m") // _
|
||||
fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev
|
||||
if x > 0 {
|
||||
fmt.Fprintf(buf, "\033[%dC", x) // move forward
|
||||
}
|
||||
o.w.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
func (o *opSearch) RefreshIfNeeded() {
|
||||
o.mutex.Lock()
|
||||
defer o.mutex.Unlock()
|
||||
|
||||
if o.inMode {
|
||||
o.searchRefresh(-1)
|
||||
}
|
||||
}
|
490
vendor/github.com/ergochat/readline/terminal.go
generated
vendored
Normal file
490
vendor/github.com/ergochat/readline/terminal.go
generated
vendored
Normal file
|
@ -0,0 +1,490 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ergochat/readline/internal/ansi"
|
||||
"github.com/ergochat/readline/internal/platform"
|
||||
)
|
||||
|
||||
const (
|
||||
// see waitForDSR
|
||||
dsrTimeout = 250 * time.Millisecond
|
||||
|
||||
maxAnsiLen = 32
|
||||
|
||||
// how many non-CPR reads to buffer while waiting for a CPR response
|
||||
maxCPRBufferLen = 128 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
deadlineExceeded = errors.New("deadline exceeded")
|
||||
concurrentReads = errors.New("concurrent read operations detected")
|
||||
invalidCPR = errors.New("invalid CPR response")
|
||||
)
|
||||
|
||||
/*
|
||||
terminal manages terminal input. The design constraints here are somewhat complex:
|
||||
|
||||
1. Calls to (*Instance).Readline() must always be preemptible by (*Instance).Close.
|
||||
This could be handled at the Operation layer instead; however, it's cleaner
|
||||
to provide an API in terminal itself that can interrupt attempts to read.
|
||||
2. In between calls to Readline(), or *after* a call to (*Instance).Close(),
|
||||
stdin must be available for code outside of this library to read from. The
|
||||
problem is that reads from stdin in Go are not preemptible (see, for example,
|
||||
https://github.com/golang/go/issues/24842 ). In the worst case, an
|
||||
interrupted read will leave (*terminal).ioloop() running, and it will
|
||||
consume one more user keystroke before it exits. However, it is a design goal
|
||||
to read as little as possible at a time.
|
||||
3. We have to handle the DSR ("device status report") query and the
|
||||
CPR ("cursor position report") response:
|
||||
https://vt100.net/docs/vt510-rm/DSR-CPR.html
|
||||
This involves writing an ANSI escape sequence to stdout, then waiting
|
||||
for the terminal to asynchronously write an ANSI escape sequence to stdin.
|
||||
We have to pick this value out of the stream and process it without
|
||||
disrupting the handling of actual user input. Moreover, concurrent Close()
|
||||
while a CPR query is in flight should ensure (if possible) that the
|
||||
response is actually read; otherwise the response may be printed to the
|
||||
screen, disrupting the user experience.
|
||||
|
||||
Accordingly, the concurrency design is as follows:
|
||||
|
||||
1. ioloop() runs asynchronously. It operates in lockstep with the read methods:
|
||||
each synchronous receive from kickChan is matched with a synchronous send to
|
||||
outChan. It does blocking reads from stdin, reading as little as possible at
|
||||
a time, and passing the results back over outChan.
|
||||
2. The read methods ("internal public API") GetRune() and GetCursorPosition()
|
||||
are not concurrency-safe and must be called in serial. They are backed by
|
||||
readFromStdin, which wakes ioloop() if necessary and waits for a response.
|
||||
If GetCursorPosition() reads non-CPR data, it will buffer it for GetRune()
|
||||
to read later.
|
||||
3. Close() can be called asynchronously. It interrupts ioloop() (unless ioloop()
|
||||
is actually reading from stdin, in which case it interrupts it after the next
|
||||
keystroke), and also interrupts any in-progress GetRune() call. If
|
||||
GetCursorPosition() is in progress, it tries to wait until the CPR response
|
||||
has been received. It is idempotent and can be called multiple times.
|
||||
*/
|
||||
|
||||
type terminal struct {
|
||||
cfg atomic.Pointer[Config]
|
||||
dimensions atomic.Pointer[termDimensions]
|
||||
closeOnce sync.Once
|
||||
closeErr error
|
||||
outChan chan readResult
|
||||
kickChan chan struct{}
|
||||
stopChan chan struct{}
|
||||
buffer []rune // actual input that we saw while waiting for the CPR
|
||||
inFlight bool // tracks whether we initiated a read and then gave up waiting
|
||||
sleeping int32
|
||||
|
||||
// asynchronously receive DSR messages from the terminal,
|
||||
// ensuring at most one query is in flight at a time
|
||||
dsrLock sync.Mutex
|
||||
dsrDone chan struct{} // nil if there is no DSR query in flight
|
||||
}
|
||||
|
||||
// termDimensions stores the terminal width and height (-1 means unknown)
|
||||
type termDimensions struct {
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
type cursorPosition struct {
|
||||
row int
|
||||
col int
|
||||
}
|
||||
|
||||
// readResult represents the result of a single "read operation" from the
|
||||
// perspective of terminal. it may be a pure no-op. the consumer needs to
|
||||
// read again if it didn't get what it wanted
|
||||
type readResult struct {
|
||||
r rune
|
||||
ok bool // is `r` valid user input? if not, we may need to read again
|
||||
// other data that can be conveyed in a single read operation;
|
||||
// currently only the CPR:
|
||||
pos *cursorPosition
|
||||
}
|
||||
|
||||
func newTerminal(cfg *Config) (*terminal, error) {
|
||||
if cfg.isInteractive {
|
||||
if ansiErr := ansi.EnableANSI(); ansiErr != nil {
|
||||
return nil, fmt.Errorf("Could not enable ANSI escapes: %w", ansiErr)
|
||||
}
|
||||
}
|
||||
t := &terminal{
|
||||
kickChan: make(chan struct{}),
|
||||
outChan: make(chan readResult),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
t.SetConfig(cfg)
|
||||
// Get and cache the current terminal size.
|
||||
t.OnSizeChange()
|
||||
|
||||
go t.ioloop()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// SleepToResume will sleep myself, and return only if I'm resumed.
|
||||
func (t *terminal) SleepToResume() {
|
||||
if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) {
|
||||
return
|
||||
}
|
||||
defer atomic.StoreInt32(&t.sleeping, 0)
|
||||
|
||||
t.ExitRawMode()
|
||||
platform.SuspendProcess()
|
||||
t.EnterRawMode()
|
||||
}
|
||||
|
||||
func (t *terminal) EnterRawMode() (err error) {
|
||||
return t.GetConfig().FuncMakeRaw()
|
||||
}
|
||||
|
||||
func (t *terminal) ExitRawMode() (err error) {
|
||||
return t.GetConfig().FuncExitRaw()
|
||||
}
|
||||
|
||||
func (t *terminal) Write(b []byte) (int, error) {
|
||||
return t.GetConfig().Stdout.Write(b)
|
||||
}
|
||||
|
||||
// getOffset sends a DSR query to get the current offset, then blocks
|
||||
// until the query returns.
|
||||
func (t *terminal) GetCursorPosition(deadline chan struct{}) (cursorPosition, error) {
|
||||
// ensure there is no in-flight query, set up a waiter
|
||||
ok := func() (ok bool) {
|
||||
t.dsrLock.Lock()
|
||||
defer t.dsrLock.Unlock()
|
||||
if t.dsrDone == nil {
|
||||
t.dsrDone = make(chan struct{})
|
||||
ok = true
|
||||
}
|
||||
return
|
||||
}()
|
||||
|
||||
if !ok {
|
||||
return cursorPosition{-1, -1}, concurrentReads
|
||||
}
|
||||
|
||||
defer func() {
|
||||
t.dsrLock.Lock()
|
||||
defer t.dsrLock.Unlock()
|
||||
close(t.dsrDone)
|
||||
t.dsrDone = nil
|
||||
}()
|
||||
|
||||
// send the DSR Cursor Position Report request to terminal stdout:
|
||||
// https://vt100.net/docs/vt510-rm/DSR-CPR.html
|
||||
_, err := t.Write([]byte("\x1b[6n"))
|
||||
if err != nil {
|
||||
return cursorPosition{-1, -1}, err
|
||||
}
|
||||
|
||||
for {
|
||||
result, err := t.readFromStdin(deadline)
|
||||
if err != nil {
|
||||
return cursorPosition{-1, -1}, err
|
||||
}
|
||||
if result.ok {
|
||||
// non-CPR input, save it to be read later:
|
||||
t.buffer = append(t.buffer, result.r)
|
||||
if len(t.buffer) > maxCPRBufferLen {
|
||||
panic("did not receive DSR CPR response")
|
||||
}
|
||||
}
|
||||
if result.pos != nil {
|
||||
return *result.pos, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// waitForDSR waits for any in-flight DSR query to complete. this prevents
|
||||
// garbage from being written to the terminal when Close() interrupts an
|
||||
// in-flight query.
|
||||
func (t *terminal) waitForDSR() {
|
||||
t.dsrLock.Lock()
|
||||
dsrDone := t.dsrDone
|
||||
t.dsrLock.Unlock()
|
||||
if dsrDone != nil {
|
||||
// tradeoffs: if the timeout is too high, we risk slowing down Close();
|
||||
// if it's too low, we risk writing the CPR to the terminal, which is bad UX,
|
||||
// but neither of these outcomes is catastrophic
|
||||
timer := time.NewTimer(dsrTimeout)
|
||||
select {
|
||||
case <-dsrDone:
|
||||
case <-timer.C:
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *terminal) GetRune(deadline chan struct{}) (rune, error) {
|
||||
if len(t.buffer) > 0 {
|
||||
result := t.buffer[0]
|
||||
t.buffer = t.buffer[1:]
|
||||
return result, nil
|
||||
}
|
||||
return t.getRuneFromStdin(deadline)
|
||||
}
|
||||
|
||||
func (t *terminal) getRuneFromStdin(deadline chan struct{}) (rune, error) {
|
||||
for {
|
||||
result, err := t.readFromStdin(deadline)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if result.ok {
|
||||
return result.r, nil
|
||||
} // else: CPR or something else we didn't understand, read again
|
||||
}
|
||||
}
|
||||
|
||||
func (t *terminal) readFromStdin(deadline chan struct{}) (result readResult, err error) {
|
||||
// we may have sent a kick previously and given up on the response;
|
||||
// if so, don't kick again (we will try again to read the pending response)
|
||||
if !t.inFlight {
|
||||
select {
|
||||
case t.kickChan <- struct{}{}:
|
||||
t.inFlight = true
|
||||
case <-t.stopChan:
|
||||
return result, io.EOF
|
||||
case <-deadline:
|
||||
return result, deadlineExceeded
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case result = <-t.outChan:
|
||||
t.inFlight = false
|
||||
return result, nil
|
||||
case <-t.stopChan:
|
||||
return result, io.EOF
|
||||
case <-deadline:
|
||||
return result, deadlineExceeded
|
||||
}
|
||||
}
|
||||
|
||||
func (t *terminal) ioloop() {
|
||||
// ensure close if we get an error from stdio
|
||||
defer t.Close()
|
||||
|
||||
buf := bufio.NewReader(t.GetConfig().Stdin)
|
||||
var ansiBuf bytes.Buffer
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.kickChan:
|
||||
case <-t.stopChan:
|
||||
return
|
||||
}
|
||||
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var result readResult
|
||||
if r == '\x1b' {
|
||||
// we're starting an ANSI escape sequence:
|
||||
// keep reading until we reach the end of the sequence
|
||||
result, err = t.consumeANSIEscape(buf, &ansiBuf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
result = readResult{r: r, ok: true}
|
||||
}
|
||||
|
||||
select {
|
||||
case t.outChan <- result:
|
||||
case <-t.stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *terminal) consumeANSIEscape(buf *bufio.Reader, ansiBuf *bytes.Buffer) (result readResult, err error) {
|
||||
ansiBuf.Reset()
|
||||
initial, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// we already read one \x1b. this can indicate either the start of an ANSI
|
||||
// escape sequence, or a keychord with Alt (e.g. Alt+f produces `\x1bf` in
|
||||
// a typical xterm).
|
||||
switch initial {
|
||||
case 'f':
|
||||
// Alt-f in xterm, or Option+RightArrow in iTerm2 with "Natural text editing"
|
||||
return readResult{r: MetaForward, ok: true}, nil // Alt-f
|
||||
case 'b':
|
||||
// Alt-b in xterm, or Option+LeftArrow in iTerm2 with "Natural text editing"
|
||||
return readResult{r: MetaBackward, ok: true}, nil // Alt-b
|
||||
case '[', 'O':
|
||||
// this is a real ANSI escape sequence, read the rest of the sequence below:
|
||||
case '\x1b':
|
||||
// Alt plus a real ANSI escape sequence. Handle this specially since
|
||||
// right now the only cases we want to handle are the arrow keys:
|
||||
return consumeAltSequence(buf)
|
||||
default:
|
||||
return // invalid, ignore
|
||||
}
|
||||
|
||||
// data consists of ; and 0-9 , anything else terminates the sequence
|
||||
var type_ rune
|
||||
for {
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if r == ';' || ('0' <= r && r <= '9') {
|
||||
ansiBuf.WriteRune(r)
|
||||
} else {
|
||||
type_ = r
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var r rune
|
||||
switch type_ {
|
||||
case 'R':
|
||||
if initial == '[' {
|
||||
// DSR CPR response; if we can't parse it, just ignore it
|
||||
// (do not return an error here because that would stop ioloop())
|
||||
if cpos, err := parseCPRResponse(ansiBuf.Bytes()); err == nil {
|
||||
return readResult{r: 0, ok: false, pos: &cpos}, nil
|
||||
}
|
||||
}
|
||||
case 'D':
|
||||
if altModifierEnabled(ansiBuf.Bytes()) {
|
||||
r = MetaBackward
|
||||
} else {
|
||||
r = CharBackward
|
||||
}
|
||||
case 'C':
|
||||
if altModifierEnabled(ansiBuf.Bytes()) {
|
||||
r = MetaForward
|
||||
} else {
|
||||
r = CharForward
|
||||
}
|
||||
case 'A':
|
||||
r = CharPrev
|
||||
case 'B':
|
||||
r = CharNext
|
||||
case 'H':
|
||||
r = CharLineStart
|
||||
case 'F':
|
||||
r = CharLineEnd
|
||||
case '~':
|
||||
if initial == '[' {
|
||||
switch string(ansiBuf.Bytes()) {
|
||||
case "3":
|
||||
r = MetaDeleteKey // this is the key typically labeled "Delete"
|
||||
case "1", "7":
|
||||
r = CharLineStart // "Home" key
|
||||
case "4", "8":
|
||||
r = CharLineEnd // "End" key
|
||||
}
|
||||
}
|
||||
case 'Z':
|
||||
if initial == '[' {
|
||||
r = MetaShiftTab
|
||||
}
|
||||
}
|
||||
|
||||
if r != 0 {
|
||||
return readResult{r: r, ok: true}, nil
|
||||
}
|
||||
return // default: no interpretable rune value
|
||||
}
|
||||
|
||||
func consumeAltSequence(buf *bufio.Reader) (result readResult, err error) {
|
||||
initial, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if initial != '[' {
|
||||
return
|
||||
}
|
||||
second, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch second {
|
||||
case 'D':
|
||||
return readResult{r: MetaBackward, ok: true}, nil
|
||||
case 'C':
|
||||
return readResult{r: MetaForward, ok: true}, nil
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func altModifierEnabled(payload []byte) bool {
|
||||
// https://www.xfree86.org/current/ctlseqs.html ; modifier keycodes
|
||||
// go after the semicolon, e.g. Alt-LeftArrow is `\x1b[1;3D` in VTE
|
||||
// terminals, where 3 indicates Alt
|
||||
if semicolonIdx := bytes.IndexByte(payload, ';'); semicolonIdx != -1 {
|
||||
if string(payload[semicolonIdx+1:]) == "3" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseCPRResponse(payload []byte) (cursorPosition, error) {
|
||||
if semicolonIdx := bytes.IndexByte(payload, ';'); semicolonIdx != -1 {
|
||||
if row, err := strconv.Atoi(string(payload[:semicolonIdx])); err == nil {
|
||||
if col, err := strconv.Atoi(string(payload[semicolonIdx+1:])); err == nil {
|
||||
return cursorPosition{row: row, col: col}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return cursorPosition{-1, -1}, invalidCPR
|
||||
}
|
||||
|
||||
func (t *terminal) Bell() {
|
||||
t.Write([]byte{CharBell})
|
||||
}
|
||||
|
||||
func (t *terminal) Close() error {
|
||||
t.closeOnce.Do(func() {
|
||||
t.waitForDSR()
|
||||
close(t.stopChan)
|
||||
// don't close outChan; outChan results should always be valid.
|
||||
// instead we always select on both outChan and stopChan
|
||||
t.closeErr = t.ExitRawMode()
|
||||
})
|
||||
return t.closeErr
|
||||
}
|
||||
|
||||
func (t *terminal) SetConfig(c *Config) error {
|
||||
t.cfg.Store(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *terminal) GetConfig() *Config {
|
||||
return t.cfg.Load()
|
||||
}
|
||||
|
||||
// OnSizeChange gets the current terminal size and caches it
|
||||
func (t *terminal) OnSizeChange() {
|
||||
cfg := t.GetConfig()
|
||||
width, height := cfg.FuncGetSize()
|
||||
t.dimensions.Store(&termDimensions{
|
||||
width: width,
|
||||
height: height,
|
||||
})
|
||||
}
|
||||
|
||||
// GetWidthHeight returns the cached width, height values from the terminal
|
||||
func (t *terminal) GetWidthHeight() (width, height int) {
|
||||
dimensions := t.dimensions.Load()
|
||||
return dimensions.width, dimensions.height
|
||||
}
|
68
vendor/github.com/ergochat/readline/undo.go
generated
vendored
Normal file
68
vendor/github.com/ergochat/readline/undo.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"github.com/ergochat/readline/internal/ringbuf"
|
||||
)
|
||||
|
||||
type undoEntry struct {
|
||||
pos int
|
||||
buf []rune
|
||||
}
|
||||
|
||||
// nil receiver is a valid no-op object
|
||||
type opUndo struct {
|
||||
op *operation
|
||||
stack ringbuf.Buffer[undoEntry]
|
||||
}
|
||||
|
||||
func newOpUndo(op *operation) *opUndo {
|
||||
o := &opUndo{op: op}
|
||||
o.stack.Initialize(32, 64)
|
||||
o.init()
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *opUndo) add() {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
|
||||
top, success := o.stack.Pop()
|
||||
buf, pos, changed := o.op.buf.CopyForUndo(top.buf) // if !success, top.buf is nil
|
||||
newEntry := undoEntry{pos: pos, buf: buf}
|
||||
if !success {
|
||||
o.stack.Add(newEntry)
|
||||
} else if !changed {
|
||||
o.stack.Add(newEntry) // update cursor position
|
||||
} else {
|
||||
o.stack.Add(top)
|
||||
o.stack.Add(newEntry)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opUndo) undo() {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
|
||||
top, success := o.stack.Pop()
|
||||
if !success {
|
||||
return
|
||||
}
|
||||
o.op.buf.Restore(top.buf, top.pos)
|
||||
o.op.buf.Refresh(nil)
|
||||
}
|
||||
|
||||
func (o *opUndo) init() {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, pos, _ := o.op.buf.CopyForUndo(nil)
|
||||
initialEntry := undoEntry{
|
||||
pos: pos,
|
||||
buf: buf,
|
||||
}
|
||||
o.stack.Clear()
|
||||
o.stack.Add(initialEntry)
|
||||
}
|
101
vendor/github.com/ergochat/readline/utils.go
generated
vendored
Normal file
101
vendor/github.com/ergochat/readline/utils.go
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
|||
package readline
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/ergochat/readline/internal/term"
|
||||
)
|
||||
|
||||
const (
|
||||
CharLineStart = 1
|
||||
CharBackward = 2
|
||||
CharInterrupt = 3
|
||||
CharEOT = 4
|
||||
CharLineEnd = 5
|
||||
CharForward = 6
|
||||
CharBell = 7
|
||||
CharCtrlH = 8
|
||||
CharTab = 9
|
||||
CharCtrlJ = 10
|
||||
CharKill = 11
|
||||
CharCtrlL = 12
|
||||
CharEnter = 13
|
||||
CharNext = 14
|
||||
CharPrev = 16
|
||||
CharBckSearch = 18
|
||||
CharFwdSearch = 19
|
||||
CharTranspose = 20
|
||||
CharCtrlU = 21
|
||||
CharCtrlW = 23
|
||||
CharCtrlY = 25
|
||||
CharCtrlZ = 26
|
||||
CharEsc = 27
|
||||
CharCtrl_ = 31
|
||||
CharO = 79
|
||||
CharEscapeEx = 91
|
||||
CharBackspace = 127
|
||||
)
|
||||
|
||||
const (
|
||||
MetaBackward rune = -iota - 1
|
||||
MetaForward
|
||||
MetaDelete
|
||||
MetaBackspace
|
||||
MetaTranspose
|
||||
MetaShiftTab
|
||||
MetaDeleteKey
|
||||
)
|
||||
|
||||
type rawModeHandler struct {
|
||||
sync.Mutex
|
||||
state *term.State
|
||||
}
|
||||
|
||||
func (r *rawModeHandler) Enter() (err error) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
r.state, err = term.MakeRaw(int(syscall.Stdin))
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *rawModeHandler) Exit() error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
if r.state == nil {
|
||||
return nil
|
||||
}
|
||||
err := term.Restore(int(syscall.Stdin), r.state)
|
||||
if err == nil {
|
||||
r.state = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func clearScreen(w io.Writer) error {
|
||||
_, err := w.Write([]byte("\x1b[H\x1b[J"))
|
||||
return err
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// print a linked list to Debug()
|
||||
func debugList(l *list.List) {
|
||||
idx := 0
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
debugPrint("%d %+v", idx, e.Value)
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
// append log info to another file
|
||||
func debugPrint(fmtStr string, o ...interface{}) {
|
||||
f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
fmt.Fprintf(f, fmtStr, o...)
|
||||
fmt.Fprintln(f)
|
||||
f.Close()
|
||||
}
|
162
vendor/github.com/ergochat/readline/vim.go
generated
vendored
Normal file
162
vendor/github.com/ergochat/readline/vim.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
package readline
|
||||
|
||||
const (
|
||||
vim_NORMAL = iota
|
||||
vim_INSERT
|
||||
vim_VISUAL
|
||||
)
|
||||
|
||||
type opVim struct {
|
||||
op *operation
|
||||
vimMode int
|
||||
}
|
||||
|
||||
func newVimMode(op *operation) *opVim {
|
||||
ov := &opVim{
|
||||
op: op,
|
||||
vimMode: vim_INSERT,
|
||||
}
|
||||
return ov
|
||||
}
|
||||
|
||||
func (o *opVim) IsEnableVimMode() bool {
|
||||
return o.op.GetConfig().VimMode
|
||||
}
|
||||
|
||||
func (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, handled bool) {
|
||||
rb := o.op.buf
|
||||
handled = true
|
||||
switch r {
|
||||
case 'h':
|
||||
t = CharBackward
|
||||
case 'j':
|
||||
t = CharNext
|
||||
case 'k':
|
||||
t = CharPrev
|
||||
case 'l':
|
||||
t = CharForward
|
||||
case '0', '^':
|
||||
rb.MoveToLineStart()
|
||||
case '$':
|
||||
rb.MoveToLineEnd()
|
||||
case 'x':
|
||||
rb.Delete()
|
||||
if rb.IsCursorInEnd() {
|
||||
rb.MoveBackward()
|
||||
}
|
||||
case 'r':
|
||||
rb.Replace(readNext())
|
||||
case 'd':
|
||||
next := readNext()
|
||||
switch next {
|
||||
case 'd':
|
||||
rb.Erase()
|
||||
case 'w':
|
||||
rb.DeleteWord()
|
||||
case 'h':
|
||||
rb.Backspace()
|
||||
case 'l':
|
||||
rb.Delete()
|
||||
}
|
||||
case 'p':
|
||||
rb.Yank()
|
||||
case 'b', 'B':
|
||||
rb.MoveToPrevWord()
|
||||
case 'w', 'W':
|
||||
rb.MoveToNextWord()
|
||||
case 'e', 'E':
|
||||
rb.MoveToEndWord()
|
||||
case 'f', 'F', 't', 'T':
|
||||
next := readNext()
|
||||
prevChar := r == 't' || r == 'T'
|
||||
reverse := r == 'F' || r == 'T'
|
||||
switch next {
|
||||
case CharEsc:
|
||||
default:
|
||||
rb.MoveTo(next, prevChar, reverse)
|
||||
}
|
||||
default:
|
||||
return r, false
|
||||
}
|
||||
return t, true
|
||||
}
|
||||
|
||||
func (o *opVim) handleVimNormalEnterInsert(r rune, readNext func() rune) (t rune, handled bool) {
|
||||
rb := o.op.buf
|
||||
handled = true
|
||||
switch r {
|
||||
case 'i':
|
||||
case 'I':
|
||||
rb.MoveToLineStart()
|
||||
case 'a':
|
||||
rb.MoveForward()
|
||||
case 'A':
|
||||
rb.MoveToLineEnd()
|
||||
case 's':
|
||||
rb.Delete()
|
||||
case 'S':
|
||||
rb.Erase()
|
||||
case 'c':
|
||||
next := readNext()
|
||||
switch next {
|
||||
case 'c':
|
||||
rb.Erase()
|
||||
case 'w':
|
||||
rb.DeleteWord()
|
||||
case 'h':
|
||||
rb.Backspace()
|
||||
case 'l':
|
||||
rb.Delete()
|
||||
}
|
||||
default:
|
||||
return r, false
|
||||
}
|
||||
|
||||
o.EnterVimInsertMode()
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune) {
|
||||
switch r {
|
||||
case CharEnter, CharInterrupt:
|
||||
o.vimMode = vim_INSERT // ???
|
||||
return r
|
||||
}
|
||||
|
||||
if r, handled := o.handleVimNormalMovement(r, readNext); handled {
|
||||
return r
|
||||
}
|
||||
|
||||
if r, handled := o.handleVimNormalEnterInsert(r, readNext); handled {
|
||||
return r
|
||||
}
|
||||
|
||||
// invalid operation
|
||||
o.op.t.Bell()
|
||||
return 0
|
||||
}
|
||||
|
||||
func (o *opVim) EnterVimInsertMode() {
|
||||
o.vimMode = vim_INSERT
|
||||
}
|
||||
|
||||
func (o *opVim) ExitVimInsertMode() {
|
||||
o.vimMode = vim_NORMAL
|
||||
}
|
||||
|
||||
func (o *opVim) HandleVim(r rune, readNext func() rune) rune {
|
||||
if o.vimMode == vim_NORMAL {
|
||||
return o.HandleVimNormal(r, readNext)
|
||||
}
|
||||
if r == CharEsc {
|
||||
o.ExitVimInsertMode()
|
||||
return 0
|
||||
}
|
||||
|
||||
switch o.vimMode {
|
||||
case vim_INSERT:
|
||||
return r
|
||||
case vim_VISUAL:
|
||||
}
|
||||
return r
|
||||
}
|
8
vendor/golang.org/x/sys/plan9/asm.s
generated
vendored
Normal file
8
vendor/golang.org/x/sys/plan9/asm.s
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT ·use(SB),NOSPLIT,$0
|
||||
RET
|
30
vendor/golang.org/x/sys/plan9/asm_plan9_386.s
generated
vendored
Normal file
30
vendor/golang.org/x/sys/plan9/asm_plan9_386.s
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
//
|
||||
// System call support for 386, Plan 9
|
||||
//
|
||||
|
||||
// Just jump to package syscall's implementation for all these functions.
|
||||
// The runtime may know about them.
|
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-32
|
||||
JMP syscall·Syscall(SB)
|
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-44
|
||||
JMP syscall·Syscall6(SB)
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||
JMP syscall·RawSyscall(SB)
|
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||
JMP syscall·RawSyscall6(SB)
|
||||
|
||||
TEXT ·seek(SB),NOSPLIT,$0-36
|
||||
JMP syscall·seek(SB)
|
||||
|
||||
TEXT ·exit(SB),NOSPLIT,$4-4
|
||||
JMP syscall·exit(SB)
|
30
vendor/golang.org/x/sys/plan9/asm_plan9_amd64.s
generated
vendored
Normal file
30
vendor/golang.org/x/sys/plan9/asm_plan9_amd64.s
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
//
|
||||
// System call support for amd64, Plan 9
|
||||
//
|
||||
|
||||
// Just jump to package syscall's implementation for all these functions.
|
||||
// The runtime may know about them.
|
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-64
|
||||
JMP syscall·Syscall(SB)
|
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-88
|
||||
JMP syscall·Syscall6(SB)
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||
JMP syscall·RawSyscall(SB)
|
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||
JMP syscall·RawSyscall6(SB)
|
||||
|
||||
TEXT ·seek(SB),NOSPLIT,$0-56
|
||||
JMP syscall·seek(SB)
|
||||
|
||||
TEXT ·exit(SB),NOSPLIT,$8-8
|
||||
JMP syscall·exit(SB)
|
25
vendor/golang.org/x/sys/plan9/asm_plan9_arm.s
generated
vendored
Normal file
25
vendor/golang.org/x/sys/plan9/asm_plan9_arm.s
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// System call support for plan9 on arm
|
||||
|
||||
// Just jump to package syscall's implementation for all these functions.
|
||||
// The runtime may know about them.
|
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-32
|
||||
JMP syscall·Syscall(SB)
|
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-44
|
||||
JMP syscall·Syscall6(SB)
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||
JMP syscall·RawSyscall(SB)
|
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||
JMP syscall·RawSyscall6(SB)
|
||||
|
||||
TEXT ·seek(SB),NOSPLIT,$0-36
|
||||
JMP syscall·exit(SB)
|
70
vendor/golang.org/x/sys/plan9/const_plan9.go
generated
vendored
Normal file
70
vendor/golang.org/x/sys/plan9/const_plan9.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
package plan9
|
||||
|
||||
// Plan 9 Constants
|
||||
|
||||
// Open modes
|
||||
const (
|
||||
O_RDONLY = 0
|
||||
O_WRONLY = 1
|
||||
O_RDWR = 2
|
||||
O_TRUNC = 16
|
||||
O_CLOEXEC = 32
|
||||
O_EXCL = 0x1000
|
||||
)
|
||||
|
||||
// Rfork flags
|
||||
const (
|
||||
RFNAMEG = 1 << 0
|
||||
RFENVG = 1 << 1
|
||||
RFFDG = 1 << 2
|
||||
RFNOTEG = 1 << 3
|
||||
RFPROC = 1 << 4
|
||||
RFMEM = 1 << 5
|
||||
RFNOWAIT = 1 << 6
|
||||
RFCNAMEG = 1 << 10
|
||||
RFCENVG = 1 << 11
|
||||
RFCFDG = 1 << 12
|
||||
RFREND = 1 << 13
|
||||
RFNOMNT = 1 << 14
|
||||
)
|
||||
|
||||
// Qid.Type bits
|
||||
const (
|
||||
QTDIR = 0x80
|
||||
QTAPPEND = 0x40
|
||||
QTEXCL = 0x20
|
||||
QTMOUNT = 0x10
|
||||
QTAUTH = 0x08
|
||||
QTTMP = 0x04
|
||||
QTFILE = 0x00
|
||||
)
|
||||
|
||||
// Dir.Mode bits
|
||||
const (
|
||||
DMDIR = 0x80000000
|
||||
DMAPPEND = 0x40000000
|
||||
DMEXCL = 0x20000000
|
||||
DMMOUNT = 0x10000000
|
||||
DMAUTH = 0x08000000
|
||||
DMTMP = 0x04000000
|
||||
DMREAD = 0x4
|
||||
DMWRITE = 0x2
|
||||
DMEXEC = 0x1
|
||||
)
|
||||
|
||||
const (
|
||||
STATMAX = 65535
|
||||
ERRMAX = 128
|
||||
STATFIXLEN = 49
|
||||
)
|
||||
|
||||
// Mount and bind flags
|
||||
const (
|
||||
MREPL = 0x0000
|
||||
MBEFORE = 0x0001
|
||||
MAFTER = 0x0002
|
||||
MORDER = 0x0003
|
||||
MCREATE = 0x0004
|
||||
MCACHE = 0x0010
|
||||
MMASK = 0x0017
|
||||
)
|
212
vendor/golang.org/x/sys/plan9/dir_plan9.go
generated
vendored
Normal file
212
vendor/golang.org/x/sys/plan9/dir_plan9.go
generated
vendored
Normal file
|
@ -0,0 +1,212 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Plan 9 directory marshalling. See intro(5).
|
||||
|
||||
package plan9
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrShortStat = errors.New("stat buffer too short")
|
||||
ErrBadStat = errors.New("malformed stat buffer")
|
||||
ErrBadName = errors.New("bad character in file name")
|
||||
)
|
||||
|
||||
// A Qid represents a 9P server's unique identification for a file.
|
||||
type Qid struct {
|
||||
Path uint64 // the file server's unique identification for the file
|
||||
Vers uint32 // version number for given Path
|
||||
Type uint8 // the type of the file (plan9.QTDIR for example)
|
||||
}
|
||||
|
||||
// A Dir contains the metadata for a file.
|
||||
type Dir struct {
|
||||
// system-modified data
|
||||
Type uint16 // server type
|
||||
Dev uint32 // server subtype
|
||||
|
||||
// file data
|
||||
Qid Qid // unique id from server
|
||||
Mode uint32 // permissions
|
||||
Atime uint32 // last read time
|
||||
Mtime uint32 // last write time
|
||||
Length int64 // file length
|
||||
Name string // last element of path
|
||||
Uid string // owner name
|
||||
Gid string // group name
|
||||
Muid string // last modifier name
|
||||
}
|
||||
|
||||
var nullDir = Dir{
|
||||
Type: ^uint16(0),
|
||||
Dev: ^uint32(0),
|
||||
Qid: Qid{
|
||||
Path: ^uint64(0),
|
||||
Vers: ^uint32(0),
|
||||
Type: ^uint8(0),
|
||||
},
|
||||
Mode: ^uint32(0),
|
||||
Atime: ^uint32(0),
|
||||
Mtime: ^uint32(0),
|
||||
Length: ^int64(0),
|
||||
}
|
||||
|
||||
// Null assigns special "don't touch" values to members of d to
|
||||
// avoid modifying them during plan9.Wstat.
|
||||
func (d *Dir) Null() { *d = nullDir }
|
||||
|
||||
// Marshal encodes a 9P stat message corresponding to d into b
|
||||
//
|
||||
// If there isn't enough space in b for a stat message, ErrShortStat is returned.
|
||||
func (d *Dir) Marshal(b []byte) (n int, err error) {
|
||||
n = STATFIXLEN + len(d.Name) + len(d.Uid) + len(d.Gid) + len(d.Muid)
|
||||
if n > len(b) {
|
||||
return n, ErrShortStat
|
||||
}
|
||||
|
||||
for _, c := range d.Name {
|
||||
if c == '/' {
|
||||
return n, ErrBadName
|
||||
}
|
||||
}
|
||||
|
||||
b = pbit16(b, uint16(n)-2)
|
||||
b = pbit16(b, d.Type)
|
||||
b = pbit32(b, d.Dev)
|
||||
b = pbit8(b, d.Qid.Type)
|
||||
b = pbit32(b, d.Qid.Vers)
|
||||
b = pbit64(b, d.Qid.Path)
|
||||
b = pbit32(b, d.Mode)
|
||||
b = pbit32(b, d.Atime)
|
||||
b = pbit32(b, d.Mtime)
|
||||
b = pbit64(b, uint64(d.Length))
|
||||
b = pstring(b, d.Name)
|
||||
b = pstring(b, d.Uid)
|
||||
b = pstring(b, d.Gid)
|
||||
b = pstring(b, d.Muid)
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// UnmarshalDir decodes a single 9P stat message from b and returns the resulting Dir.
|
||||
//
|
||||
// If b is too small to hold a valid stat message, ErrShortStat is returned.
|
||||
//
|
||||
// If the stat message itself is invalid, ErrBadStat is returned.
|
||||
func UnmarshalDir(b []byte) (*Dir, error) {
|
||||
if len(b) < STATFIXLEN {
|
||||
return nil, ErrShortStat
|
||||
}
|
||||
size, buf := gbit16(b)
|
||||
if len(b) != int(size)+2 {
|
||||
return nil, ErrBadStat
|
||||
}
|
||||
b = buf
|
||||
|
||||
var d Dir
|
||||
d.Type, b = gbit16(b)
|
||||
d.Dev, b = gbit32(b)
|
||||
d.Qid.Type, b = gbit8(b)
|
||||
d.Qid.Vers, b = gbit32(b)
|
||||
d.Qid.Path, b = gbit64(b)
|
||||
d.Mode, b = gbit32(b)
|
||||
d.Atime, b = gbit32(b)
|
||||
d.Mtime, b = gbit32(b)
|
||||
|
||||
n, b := gbit64(b)
|
||||
d.Length = int64(n)
|
||||
|
||||
var ok bool
|
||||
if d.Name, b, ok = gstring(b); !ok {
|
||||
return nil, ErrBadStat
|
||||
}
|
||||
if d.Uid, b, ok = gstring(b); !ok {
|
||||
return nil, ErrBadStat
|
||||
}
|
||||
if d.Gid, b, ok = gstring(b); !ok {
|
||||
return nil, ErrBadStat
|
||||
}
|
||||
if d.Muid, b, ok = gstring(b); !ok {
|
||||
return nil, ErrBadStat
|
||||
}
|
||||
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
// pbit8 copies the 8-bit number v to b and returns the remaining slice of b.
|
||||
func pbit8(b []byte, v uint8) []byte {
|
||||
b[0] = byte(v)
|
||||
return b[1:]
|
||||
}
|
||||
|
||||
// pbit16 copies the 16-bit number v to b in little-endian order and returns the remaining slice of b.
|
||||
func pbit16(b []byte, v uint16) []byte {
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
return b[2:]
|
||||
}
|
||||
|
||||
// pbit32 copies the 32-bit number v to b in little-endian order and returns the remaining slice of b.
|
||||
func pbit32(b []byte, v uint32) []byte {
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
return b[4:]
|
||||
}
|
||||
|
||||
// pbit64 copies the 64-bit number v to b in little-endian order and returns the remaining slice of b.
|
||||
func pbit64(b []byte, v uint64) []byte {
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
b[4] = byte(v >> 32)
|
||||
b[5] = byte(v >> 40)
|
||||
b[6] = byte(v >> 48)
|
||||
b[7] = byte(v >> 56)
|
||||
return b[8:]
|
||||
}
|
||||
|
||||
// pstring copies the string s to b, prepending it with a 16-bit length in little-endian order, and
|
||||
// returning the remaining slice of b..
|
||||
func pstring(b []byte, s string) []byte {
|
||||
b = pbit16(b, uint16(len(s)))
|
||||
n := copy(b, s)
|
||||
return b[n:]
|
||||
}
|
||||
|
||||
// gbit8 reads an 8-bit number from b and returns it with the remaining slice of b.
|
||||
func gbit8(b []byte) (uint8, []byte) {
|
||||
return uint8(b[0]), b[1:]
|
||||
}
|
||||
|
||||
// gbit16 reads a 16-bit number in little-endian order from b and returns it with the remaining slice of b.
|
||||
func gbit16(b []byte) (uint16, []byte) {
|
||||
return uint16(b[0]) | uint16(b[1])<<8, b[2:]
|
||||
}
|
||||
|
||||
// gbit32 reads a 32-bit number in little-endian order from b and returns it with the remaining slice of b.
|
||||
func gbit32(b []byte) (uint32, []byte) {
|
||||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24, b[4:]
|
||||
}
|
||||
|
||||
// gbit64 reads a 64-bit number in little-endian order from b and returns it with the remaining slice of b.
|
||||
func gbit64(b []byte) (uint64, []byte) {
|
||||
lo := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||
hi := uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16 | uint32(b[7])<<24
|
||||
return uint64(lo) | uint64(hi)<<32, b[8:]
|
||||
}
|
||||
|
||||
// gstring reads a string from b, prefixed with a 16-bit length in little-endian order.
|
||||
// It returns the string with the remaining slice of b and a boolean. If the length is
|
||||
// greater than the number of bytes in b, the boolean will be false.
|
||||
func gstring(b []byte) (string, []byte, bool) {
|
||||
n, b := gbit16(b)
|
||||
if int(n) > len(b) {
|
||||
return "", b, false
|
||||
}
|
||||
return string(b[:n]), b[n:], true
|
||||
}
|
31
vendor/golang.org/x/sys/plan9/env_plan9.go
generated
vendored
Normal file
31
vendor/golang.org/x/sys/plan9/env_plan9.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Plan 9 environment variables.
|
||||
|
||||
package plan9
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Getenv(key string) (value string, found bool) {
|
||||
return syscall.Getenv(key)
|
||||
}
|
||||
|
||||
func Setenv(key, value string) error {
|
||||
return syscall.Setenv(key, value)
|
||||
}
|
||||
|
||||
func Clearenv() {
|
||||
syscall.Clearenv()
|
||||
}
|
||||
|
||||
func Environ() []string {
|
||||
return syscall.Environ()
|
||||
}
|
||||
|
||||
func Unsetenv(key string) error {
|
||||
return syscall.Unsetenv(key)
|
||||
}
|
50
vendor/golang.org/x/sys/plan9/errors_plan9.go
generated
vendored
Normal file
50
vendor/golang.org/x/sys/plan9/errors_plan9.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2011 The Go 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 plan9
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Constants
|
||||
const (
|
||||
// Invented values to support what package os expects.
|
||||
O_CREAT = 0x02000
|
||||
O_APPEND = 0x00400
|
||||
O_NOCTTY = 0x00000
|
||||
O_NONBLOCK = 0x00000
|
||||
O_SYNC = 0x00000
|
||||
O_ASYNC = 0x00000
|
||||
|
||||
S_IFMT = 0x1f000
|
||||
S_IFIFO = 0x1000
|
||||
S_IFCHR = 0x2000
|
||||
S_IFDIR = 0x4000
|
||||
S_IFBLK = 0x6000
|
||||
S_IFREG = 0x8000
|
||||
S_IFLNK = 0xa000
|
||||
S_IFSOCK = 0xc000
|
||||
)
|
||||
|
||||
// Errors
|
||||
var (
|
||||
EINVAL = syscall.NewError("bad arg in system call")
|
||||
ENOTDIR = syscall.NewError("not a directory")
|
||||
EISDIR = syscall.NewError("file is a directory")
|
||||
ENOENT = syscall.NewError("file does not exist")
|
||||
EEXIST = syscall.NewError("file already exists")
|
||||
EMFILE = syscall.NewError("no free file descriptors")
|
||||
EIO = syscall.NewError("i/o error")
|
||||
ENAMETOOLONG = syscall.NewError("file name too long")
|
||||
EINTR = syscall.NewError("interrupted")
|
||||
EPERM = syscall.NewError("permission denied")
|
||||
EBUSY = syscall.NewError("no free devices")
|
||||
ETIMEDOUT = syscall.NewError("connection timed out")
|
||||
EPLAN9 = syscall.NewError("not supported by plan 9")
|
||||
|
||||
// The following errors do not correspond to any
|
||||
// Plan 9 system messages. Invented to support
|
||||
// what package os and others expect.
|
||||
EACCES = syscall.NewError("access permission denied")
|
||||
EAFNOSUPPORT = syscall.NewError("address family not supported by protocol")
|
||||
)
|
150
vendor/golang.org/x/sys/plan9/mkall.sh
generated
vendored
Normal file
150
vendor/golang.org/x/sys/plan9/mkall.sh
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright 2009 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# The plan9 package provides access to the raw system call
|
||||
# interface of the underlying operating system. Porting Go to
|
||||
# a new architecture/operating system combination requires
|
||||
# some manual effort, though there are tools that automate
|
||||
# much of the process. The auto-generated files have names
|
||||
# beginning with z.
|
||||
#
|
||||
# This script runs or (given -n) prints suggested commands to generate z files
|
||||
# for the current system. Running those commands is not automatic.
|
||||
# This script is documentation more than anything else.
|
||||
#
|
||||
# * asm_${GOOS}_${GOARCH}.s
|
||||
#
|
||||
# This hand-written assembly file implements system call dispatch.
|
||||
# There are three entry points:
|
||||
#
|
||||
# func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr);
|
||||
# func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr);
|
||||
# func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr);
|
||||
#
|
||||
# The first and second are the standard ones; they differ only in
|
||||
# how many arguments can be passed to the kernel.
|
||||
# The third is for low-level use by the ForkExec wrapper;
|
||||
# unlike the first two, it does not call into the scheduler to
|
||||
# let it know that a system call is running.
|
||||
#
|
||||
# * syscall_${GOOS}.go
|
||||
#
|
||||
# This hand-written Go file implements system calls that need
|
||||
# special handling and lists "//sys" comments giving prototypes
|
||||
# for ones that can be auto-generated. Mksyscall reads those
|
||||
# comments to generate the stubs.
|
||||
#
|
||||
# * syscall_${GOOS}_${GOARCH}.go
|
||||
#
|
||||
# Same as syscall_${GOOS}.go except that it contains code specific
|
||||
# to ${GOOS} on one particular architecture.
|
||||
#
|
||||
# * types_${GOOS}.c
|
||||
#
|
||||
# This hand-written C file includes standard C headers and then
|
||||
# creates typedef or enum names beginning with a dollar sign
|
||||
# (use of $ in variable names is a gcc extension). The hardest
|
||||
# part about preparing this file is figuring out which headers to
|
||||
# include and which symbols need to be #defined to get the
|
||||
# actual data structures that pass through to the kernel system calls.
|
||||
# Some C libraries present alternate versions for binary compatibility
|
||||
# and translate them on the way in and out of system calls, but
|
||||
# there is almost always a #define that can get the real ones.
|
||||
# See types_darwin.c and types_linux.c for examples.
|
||||
#
|
||||
# * zerror_${GOOS}_${GOARCH}.go
|
||||
#
|
||||
# This machine-generated file defines the system's error numbers,
|
||||
# error strings, and signal numbers. The generator is "mkerrors.sh".
|
||||
# Usually no arguments are needed, but mkerrors.sh will pass its
|
||||
# arguments on to godefs.
|
||||
#
|
||||
# * zsyscall_${GOOS}_${GOARCH}.go
|
||||
#
|
||||
# Generated by mksyscall.pl; see syscall_${GOOS}.go above.
|
||||
#
|
||||
# * zsysnum_${GOOS}_${GOARCH}.go
|
||||
#
|
||||
# Generated by mksysnum_${GOOS}.
|
||||
#
|
||||
# * ztypes_${GOOS}_${GOARCH}.go
|
||||
#
|
||||
# Generated by godefs; see types_${GOOS}.c above.
|
||||
|
||||
GOOSARCH="${GOOS}_${GOARCH}"
|
||||
|
||||
# defaults
|
||||
mksyscall="go run mksyscall.go"
|
||||
mkerrors="./mkerrors.sh"
|
||||
zerrors="zerrors_$GOOSARCH.go"
|
||||
mksysctl=""
|
||||
zsysctl="zsysctl_$GOOSARCH.go"
|
||||
mksysnum=
|
||||
mktypes=
|
||||
run="sh"
|
||||
|
||||
case "$1" in
|
||||
-syscalls)
|
||||
for i in zsyscall*go
|
||||
do
|
||||
sed 1q $i | sed 's;^// ;;' | sh > _$i && gofmt < _$i > $i
|
||||
rm _$i
|
||||
done
|
||||
exit 0
|
||||
;;
|
||||
-n)
|
||||
run="cat"
|
||||
shift
|
||||
esac
|
||||
|
||||
case "$#" in
|
||||
0)
|
||||
;;
|
||||
*)
|
||||
echo 'usage: mkall.sh [-n]' 1>&2
|
||||
exit 2
|
||||
esac
|
||||
|
||||
case "$GOOSARCH" in
|
||||
_* | *_ | _)
|
||||
echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
plan9_386)
|
||||
mkerrors=
|
||||
mksyscall="go run mksyscall.go -l32 -plan9 -tags plan9,386"
|
||||
mksysnum="./mksysnum_plan9.sh /n/sources/plan9/sys/src/libc/9syscall/sys.h"
|
||||
mktypes="XXX"
|
||||
;;
|
||||
plan9_amd64)
|
||||
mkerrors=
|
||||
mksyscall="go run mksyscall.go -l32 -plan9 -tags plan9,amd64"
|
||||
mksysnum="./mksysnum_plan9.sh /n/sources/plan9/sys/src/libc/9syscall/sys.h"
|
||||
mktypes="XXX"
|
||||
;;
|
||||
plan9_arm)
|
||||
mkerrors=
|
||||
mksyscall="go run mksyscall.go -l32 -plan9 -tags plan9,arm"
|
||||
mksysnum="./mksysnum_plan9.sh /n/sources/plan9/sys/src/libc/9syscall/sys.h"
|
||||
mktypes="XXX"
|
||||
;;
|
||||
*)
|
||||
echo 'unrecognized $GOOS_$GOARCH: ' "$GOOSARCH" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
(
|
||||
if [ -n "$mkerrors" ]; then echo "$mkerrors |gofmt >$zerrors"; fi
|
||||
case "$GOOS" in
|
||||
plan9)
|
||||
syscall_goos="syscall_$GOOS.go"
|
||||
if [ -n "$mksyscall" ]; then echo "$mksyscall $syscall_goos |gofmt >zsyscall_$GOOSARCH.go"; fi
|
||||
;;
|
||||
esac
|
||||
if [ -n "$mksysctl" ]; then echo "$mksysctl |gofmt >$zsysctl"; fi
|
||||
if [ -n "$mksysnum" ]; then echo "$mksysnum |gofmt >zsysnum_$GOOSARCH.go"; fi
|
||||
if [ -n "$mktypes" ]; then echo "$mktypes types_$GOOS.go |gofmt >ztypes_$GOOSARCH.go"; fi
|
||||
) | $run
|
246
vendor/golang.org/x/sys/plan9/mkerrors.sh
generated
vendored
Normal file
246
vendor/golang.org/x/sys/plan9/mkerrors.sh
generated
vendored
Normal file
|
@ -0,0 +1,246 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright 2009 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# Generate Go code listing errors and other #defined constant
|
||||
# values (ENAMETOOLONG etc.), by asking the preprocessor
|
||||
# about the definitions.
|
||||
|
||||
unset LANG
|
||||
export LC_ALL=C
|
||||
export LC_CTYPE=C
|
||||
|
||||
CC=${CC:-gcc}
|
||||
|
||||
uname=$(uname)
|
||||
|
||||
includes='
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip6.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <errno.h>
|
||||
#include <sys/signal.h>
|
||||
#include <signal.h>
|
||||
#include <sys/resource.h>
|
||||
'
|
||||
|
||||
ccflags="$@"
|
||||
|
||||
# Write go tool cgo -godefs input.
|
||||
(
|
||||
echo package plan9
|
||||
echo
|
||||
echo '/*'
|
||||
indirect="includes_$(uname)"
|
||||
echo "${!indirect} $includes"
|
||||
echo '*/'
|
||||
echo 'import "C"'
|
||||
echo
|
||||
echo 'const ('
|
||||
|
||||
# The gcc command line prints all the #defines
|
||||
# it encounters while processing the input
|
||||
echo "${!indirect} $includes" | $CC -x c - -E -dM $ccflags |
|
||||
awk '
|
||||
$1 != "#define" || $2 ~ /\(/ || $3 == "" {next}
|
||||
|
||||
$2 ~ /^E([ABCD]X|[BIS]P|[SD]I|S|FL)$/ {next} # 386 registers
|
||||
$2 ~ /^(SIGEV_|SIGSTKSZ|SIGRT(MIN|MAX))/ {next}
|
||||
$2 ~ /^(SCM_SRCRT)$/ {next}
|
||||
$2 ~ /^(MAP_FAILED)$/ {next}
|
||||
|
||||
$2 !~ /^ETH_/ &&
|
||||
$2 !~ /^EPROC_/ &&
|
||||
$2 !~ /^EQUIV_/ &&
|
||||
$2 !~ /^EXPR_/ &&
|
||||
$2 ~ /^E[A-Z0-9_]+$/ ||
|
||||
$2 ~ /^B[0-9_]+$/ ||
|
||||
$2 ~ /^V[A-Z0-9]+$/ ||
|
||||
$2 ~ /^CS[A-Z0-9]/ ||
|
||||
$2 ~ /^I(SIG|CANON|CRNL|EXTEN|MAXBEL|STRIP|UTF8)$/ ||
|
||||
$2 ~ /^IGN/ ||
|
||||
$2 ~ /^IX(ON|ANY|OFF)$/ ||
|
||||
$2 ~ /^IN(LCR|PCK)$/ ||
|
||||
$2 ~ /(^FLU?SH)|(FLU?SH$)/ ||
|
||||
$2 ~ /^C(LOCAL|READ)$/ ||
|
||||
$2 == "BRKINT" ||
|
||||
$2 == "HUPCL" ||
|
||||
$2 == "PENDIN" ||
|
||||
$2 == "TOSTOP" ||
|
||||
$2 ~ /^PAR/ ||
|
||||
$2 ~ /^SIG[^_]/ ||
|
||||
$2 ~ /^O[CNPFP][A-Z]+[^_][A-Z]+$/ ||
|
||||
$2 ~ /^IN_/ ||
|
||||
$2 ~ /^LOCK_(SH|EX|NB|UN)$/ ||
|
||||
$2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|ICMP6|TCP|EVFILT|NOTE|EV|SHUT|PROT|MAP|PACKET|MSG|SCM|MCL|DT|MADV|PR)_/ ||
|
||||
$2 == "ICMPV6_FILTER" ||
|
||||
$2 == "SOMAXCONN" ||
|
||||
$2 == "NAME_MAX" ||
|
||||
$2 == "IFNAMSIZ" ||
|
||||
$2 ~ /^CTL_(MAXNAME|NET|QUERY)$/ ||
|
||||
$2 ~ /^SYSCTL_VERS/ ||
|
||||
$2 ~ /^(MS|MNT)_/ ||
|
||||
$2 ~ /^TUN(SET|GET|ATTACH|DETACH)/ ||
|
||||
$2 ~ /^(O|F|FD|NAME|S|PTRACE|PT)_/ ||
|
||||
$2 ~ /^LINUX_REBOOT_CMD_/ ||
|
||||
$2 ~ /^LINUX_REBOOT_MAGIC[12]$/ ||
|
||||
$2 !~ "NLA_TYPE_MASK" &&
|
||||
$2 ~ /^(NETLINK|NLM|NLMSG|NLA|IFA|IFAN|RT|RTCF|RTN|RTPROT|RTNH|ARPHRD|ETH_P)_/ ||
|
||||
$2 ~ /^SIOC/ ||
|
||||
$2 ~ /^TIOC/ ||
|
||||
$2 !~ "RTF_BITS" &&
|
||||
$2 ~ /^(IFF|IFT|NET_RT|RTM|RTF|RTV|RTA|RTAX)_/ ||
|
||||
$2 ~ /^BIOC/ ||
|
||||
$2 ~ /^RUSAGE_(SELF|CHILDREN|THREAD)/ ||
|
||||
$2 ~ /^RLIMIT_(AS|CORE|CPU|DATA|FSIZE|NOFILE|STACK)|RLIM_INFINITY/ ||
|
||||
$2 ~ /^PRIO_(PROCESS|PGRP|USER)/ ||
|
||||
$2 ~ /^CLONE_[A-Z_]+/ ||
|
||||
$2 !~ /^(BPF_TIMEVAL)$/ &&
|
||||
$2 ~ /^(BPF|DLT)_/ ||
|
||||
$2 !~ "WMESGLEN" &&
|
||||
$2 ~ /^W[A-Z0-9]+$/ {printf("\t%s = C.%s\n", $2, $2)}
|
||||
$2 ~ /^__WCOREFLAG$/ {next}
|
||||
$2 ~ /^__W[A-Z0-9]+$/ {printf("\t%s = C.%s\n", substr($2,3), $2)}
|
||||
|
||||
{next}
|
||||
' | sort
|
||||
|
||||
echo ')'
|
||||
) >_const.go
|
||||
|
||||
# Pull out the error names for later.
|
||||
errors=$(
|
||||
echo '#include <errno.h>' | $CC -x c - -E -dM $ccflags |
|
||||
awk '$1=="#define" && $2 ~ /^E[A-Z0-9_]+$/ { print $2 }' |
|
||||
sort
|
||||
)
|
||||
|
||||
# Pull out the signal names for later.
|
||||
signals=$(
|
||||
echo '#include <signal.h>' | $CC -x c - -E -dM $ccflags |
|
||||
awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print $2 }' |
|
||||
grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT' |
|
||||
sort
|
||||
)
|
||||
|
||||
# Again, writing regexps to a file.
|
||||
echo '#include <errno.h>' | $CC -x c - -E -dM $ccflags |
|
||||
awk '$1=="#define" && $2 ~ /^E[A-Z0-9_]+$/ { print "^\t" $2 "[ \t]*=" }' |
|
||||
sort >_error.grep
|
||||
echo '#include <signal.h>' | $CC -x c - -E -dM $ccflags |
|
||||
awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print "^\t" $2 "[ \t]*=" }' |
|
||||
grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT' |
|
||||
sort >_signal.grep
|
||||
|
||||
echo '// mkerrors.sh' "$@"
|
||||
echo '// Code generated by the command above; DO NOT EDIT.'
|
||||
echo
|
||||
go tool cgo -godefs -- "$@" _const.go >_error.out
|
||||
cat _error.out | grep -vf _error.grep | grep -vf _signal.grep
|
||||
echo
|
||||
echo '// Errors'
|
||||
echo 'const ('
|
||||
cat _error.out | grep -f _error.grep | sed 's/=\(.*\)/= Errno(\1)/'
|
||||
echo ')'
|
||||
|
||||
echo
|
||||
echo '// Signals'
|
||||
echo 'const ('
|
||||
cat _error.out | grep -f _signal.grep | sed 's/=\(.*\)/= Signal(\1)/'
|
||||
echo ')'
|
||||
|
||||
# Run C program to print error and syscall strings.
|
||||
(
|
||||
echo -E "
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#define nelem(x) (sizeof(x)/sizeof((x)[0]))
|
||||
|
||||
enum { A = 'A', Z = 'Z', a = 'a', z = 'z' }; // avoid need for single quotes below
|
||||
|
||||
int errors[] = {
|
||||
"
|
||||
for i in $errors
|
||||
do
|
||||
echo -E ' '$i,
|
||||
done
|
||||
|
||||
echo -E "
|
||||
};
|
||||
|
||||
int signals[] = {
|
||||
"
|
||||
for i in $signals
|
||||
do
|
||||
echo -E ' '$i,
|
||||
done
|
||||
|
||||
# Use -E because on some systems bash builtin interprets \n itself.
|
||||
echo -E '
|
||||
};
|
||||
|
||||
static int
|
||||
intcmp(const void *a, const void *b)
|
||||
{
|
||||
return *(int*)a - *(int*)b;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
int i, j, e;
|
||||
char buf[1024], *p;
|
||||
|
||||
printf("\n\n// Error table\n");
|
||||
printf("var errors = [...]string {\n");
|
||||
qsort(errors, nelem(errors), sizeof errors[0], intcmp);
|
||||
for(i=0; i<nelem(errors); i++) {
|
||||
e = errors[i];
|
||||
if(i > 0 && errors[i-1] == e)
|
||||
continue;
|
||||
strcpy(buf, strerror(e));
|
||||
// lowercase first letter: Bad -> bad, but STREAM -> STREAM.
|
||||
if(A <= buf[0] && buf[0] <= Z && a <= buf[1] && buf[1] <= z)
|
||||
buf[0] += a - A;
|
||||
printf("\t%d: \"%s\",\n", e, buf);
|
||||
}
|
||||
printf("}\n\n");
|
||||
|
||||
printf("\n\n// Signal table\n");
|
||||
printf("var signals = [...]string {\n");
|
||||
qsort(signals, nelem(signals), sizeof signals[0], intcmp);
|
||||
for(i=0; i<nelem(signals); i++) {
|
||||
e = signals[i];
|
||||
if(i > 0 && signals[i-1] == e)
|
||||
continue;
|
||||
strcpy(buf, strsignal(e));
|
||||
// lowercase first letter: Bad -> bad, but STREAM -> STREAM.
|
||||
if(A <= buf[0] && buf[0] <= Z && a <= buf[1] && buf[1] <= z)
|
||||
buf[0] += a - A;
|
||||
// cut trailing : number.
|
||||
p = strrchr(buf, ":"[0]);
|
||||
if(p)
|
||||
*p = '\0';
|
||||
printf("\t%d: \"%s\",\n", e, buf);
|
||||
}
|
||||
printf("}\n\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
'
|
||||
) >_errors.c
|
||||
|
||||
$CC $ccflags -o _errors _errors.c && $GORUN ./_errors && rm -f _errors.c _errors _const.go _error.grep _signal.grep _error.out
|
23
vendor/golang.org/x/sys/plan9/mksysnum_plan9.sh
generated
vendored
Normal file
23
vendor/golang.org/x/sys/plan9/mksysnum_plan9.sh
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
#!/bin/sh
|
||||
# Copyright 2009 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
COMMAND="mksysnum_plan9.sh $@"
|
||||
|
||||
cat <<EOF
|
||||
// $COMMAND
|
||||
// MACHINE GENERATED BY THE ABOVE COMMAND; DO NOT EDIT
|
||||
|
||||
package plan9
|
||||
|
||||
const(
|
||||
EOF
|
||||
|
||||
SP='[ ]' # space or tab
|
||||
sed "s/^#define${SP}\\([A-Z0-9_][A-Z0-9_]*\\)${SP}${SP}*\\([0-9][0-9]*\\)/SYS_\\1=\\2/g" \
|
||||
< $1 | grep -v SYS__
|
||||
|
||||
cat <<EOF
|
||||
)
|
||||
EOF
|
21
vendor/golang.org/x/sys/plan9/pwd_go15_plan9.go
generated
vendored
Normal file
21
vendor/golang.org/x/sys/plan9/pwd_go15_plan9.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.5
|
||||
|
||||
package plan9
|
||||
|
||||
import "syscall"
|
||||
|
||||
func fixwd() {
|
||||
syscall.Fixwd()
|
||||
}
|
||||
|
||||
func Getwd() (wd string, err error) {
|
||||
return syscall.Getwd()
|
||||
}
|
||||
|
||||
func Chdir(path string) error {
|
||||
return syscall.Chdir(path)
|
||||
}
|
23
vendor/golang.org/x/sys/plan9/pwd_plan9.go
generated
vendored
Normal file
23
vendor/golang.org/x/sys/plan9/pwd_plan9.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.5
|
||||
|
||||
package plan9
|
||||
|
||||
func fixwd() {
|
||||
}
|
||||
|
||||
func Getwd() (wd string, err error) {
|
||||
fd, err := open(".", O_RDONLY)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer Close(fd)
|
||||
return Fd2path(fd)
|
||||
}
|
||||
|
||||
func Chdir(path string) error {
|
||||
return chdir(path)
|
||||
}
|
30
vendor/golang.org/x/sys/plan9/race.go
generated
vendored
Normal file
30
vendor/golang.org/x/sys/plan9/race.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build plan9 && race
|
||||
|
||||
package plan9
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const raceenabled = true
|
||||
|
||||
func raceAcquire(addr unsafe.Pointer) {
|
||||
runtime.RaceAcquire(addr)
|
||||
}
|
||||
|
||||
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||
runtime.RaceReleaseMerge(addr)
|
||||
}
|
||||
|
||||
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||
runtime.RaceReadRange(addr, len)
|
||||
}
|
||||
|
||||
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||
runtime.RaceWriteRange(addr, len)
|
||||
}
|
25
vendor/golang.org/x/sys/plan9/race0.go
generated
vendored
Normal file
25
vendor/golang.org/x/sys/plan9/race0.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build plan9 && !race
|
||||
|
||||
package plan9
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const raceenabled = false
|
||||
|
||||
func raceAcquire(addr unsafe.Pointer) {
|
||||
}
|
||||
|
||||
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||
}
|
||||
|
||||
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||
}
|
||||
|
||||
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||
}
|
22
vendor/golang.org/x/sys/plan9/str.go
generated
vendored
Normal file
22
vendor/golang.org/x/sys/plan9/str.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build plan9
|
||||
|
||||
package plan9
|
||||
|
||||
func itoa(val int) string { // do it here rather than with fmt to avoid dependency
|
||||
if val < 0 {
|
||||
return "-" + itoa(-val)
|
||||
}
|
||||
var buf [32]byte // big enough for int64
|
||||
i := len(buf) - 1
|
||||
for val >= 10 {
|
||||
buf[i] = byte(val%10 + '0')
|
||||
i--
|
||||
val /= 10
|
||||
}
|
||||
buf[i] = byte(val + '0')
|
||||
return string(buf[i:])
|
||||
}
|
109
vendor/golang.org/x/sys/plan9/syscall.go
generated
vendored
Normal file
109
vendor/golang.org/x/sys/plan9/syscall.go
generated
vendored
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build plan9
|
||||
|
||||
// Package plan9 contains an interface to the low-level operating system
|
||||
// primitives. OS details vary depending on the underlying system, and
|
||||
// by default, godoc will display the OS-specific documentation for the current
|
||||
// system. If you want godoc to display documentation for another
|
||||
// system, set $GOOS and $GOARCH to the desired system. For example, if
|
||||
// you want to view documentation for freebsd/arm on linux/amd64, set $GOOS
|
||||
// to freebsd and $GOARCH to arm.
|
||||
//
|
||||
// The primary use of this package is inside other packages that provide a more
|
||||
// portable interface to the system, such as "os", "time" and "net". Use
|
||||
// those packages rather than this one if you can.
|
||||
//
|
||||
// For details of the functions and data types in this package consult
|
||||
// the manuals for the appropriate operating system.
|
||||
//
|
||||
// These calls return err == nil to indicate success; otherwise
|
||||
// err represents an operating system error describing the failure and
|
||||
// holds a value of type syscall.ErrorString.
|
||||
package plan9 // import "golang.org/x/sys/plan9"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ByteSliceFromString returns a NUL-terminated slice of bytes
|
||||
// containing the text of s. If s contains a NUL byte at any
|
||||
// location, it returns (nil, EINVAL).
|
||||
func ByteSliceFromString(s string) ([]byte, error) {
|
||||
if strings.IndexByte(s, 0) != -1 {
|
||||
return nil, EINVAL
|
||||
}
|
||||
a := make([]byte, len(s)+1)
|
||||
copy(a, s)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// BytePtrFromString returns a pointer to a NUL-terminated array of
|
||||
// bytes containing the text of s. If s contains a NUL byte at any
|
||||
// location, it returns (nil, EINVAL).
|
||||
func BytePtrFromString(s string) (*byte, error) {
|
||||
a, err := ByteSliceFromString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &a[0], nil
|
||||
}
|
||||
|
||||
// ByteSliceToString returns a string form of the text represented by the slice s, with a terminating NUL and any
|
||||
// bytes after the NUL removed.
|
||||
func ByteSliceToString(s []byte) string {
|
||||
if i := bytes.IndexByte(s, 0); i != -1 {
|
||||
s = s[:i]
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// BytePtrToString takes a pointer to a sequence of text and returns the corresponding string.
|
||||
// If the pointer is nil, it returns the empty string. It assumes that the text sequence is terminated
|
||||
// at a zero byte; if the zero byte is not present, the program may crash.
|
||||
func BytePtrToString(p *byte) string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
if *p == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Find NUL terminator.
|
||||
n := 0
|
||||
for ptr := unsafe.Pointer(p); *(*byte)(ptr) != 0; n++ {
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + 1)
|
||||
}
|
||||
|
||||
return string(unsafe.Slice(p, n))
|
||||
}
|
||||
|
||||
// Single-word zero for use when we need a valid pointer to 0 bytes.
|
||||
// See mksyscall.pl.
|
||||
var _zero uintptr
|
||||
|
||||
func (ts *Timespec) Unix() (sec int64, nsec int64) {
|
||||
return int64(ts.Sec), int64(ts.Nsec)
|
||||
}
|
||||
|
||||
func (tv *Timeval) Unix() (sec int64, nsec int64) {
|
||||
return int64(tv.Sec), int64(tv.Usec) * 1000
|
||||
}
|
||||
|
||||
func (ts *Timespec) Nano() int64 {
|
||||
return int64(ts.Sec)*1e9 + int64(ts.Nsec)
|
||||
}
|
||||
|
||||
func (tv *Timeval) Nano() int64 {
|
||||
return int64(tv.Sec)*1e9 + int64(tv.Usec)*1000
|
||||
}
|
||||
|
||||
// use is a no-op, but the compiler cannot see that it is.
|
||||
// Calling use(p) ensures that p is kept live until that point.
|
||||
//
|
||||
//go:noescape
|
||||
func use(p unsafe.Pointer)
|
361
vendor/golang.org/x/sys/plan9/syscall_plan9.go
generated
vendored
Normal file
361
vendor/golang.org/x/sys/plan9/syscall_plan9.go
generated
vendored
Normal file
|
@ -0,0 +1,361 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Plan 9 system calls.
|
||||
// This file is compiled as ordinary Go code,
|
||||
// but it is also input to mksyscall,
|
||||
// which parses the //sys lines and generates system call stubs.
|
||||
// Note that sometimes we use a lowercase //sys name and
|
||||
// wrap it in our own nicer implementation.
|
||||
|
||||
package plan9
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// A Note is a string describing a process note.
|
||||
// It implements the os.Signal interface.
|
||||
type Note string
|
||||
|
||||
func (n Note) Signal() {}
|
||||
|
||||
func (n Note) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
var (
|
||||
Stdin = 0
|
||||
Stdout = 1
|
||||
Stderr = 2
|
||||
)
|
||||
|
||||
// For testing: clients can set this flag to force
|
||||
// creation of IPv6 sockets to return EAFNOSUPPORT.
|
||||
var SocketDisableIPv6 bool
|
||||
|
||||
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.ErrorString)
|
||||
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.ErrorString)
|
||||
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
|
||||
func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr)
|
||||
|
||||
func atoi(b []byte) (n uint) {
|
||||
n = 0
|
||||
for i := 0; i < len(b); i++ {
|
||||
n = n*10 + uint(b[i]-'0')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func cstring(s []byte) string {
|
||||
i := bytes.IndexByte(s, 0)
|
||||
if i == -1 {
|
||||
i = len(s)
|
||||
}
|
||||
return string(s[:i])
|
||||
}
|
||||
|
||||
func errstr() string {
|
||||
var buf [ERRMAX]byte
|
||||
|
||||
RawSyscall(SYS_ERRSTR, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0)
|
||||
|
||||
buf[len(buf)-1] = 0
|
||||
return cstring(buf[:])
|
||||
}
|
||||
|
||||
// Implemented in assembly to import from runtime.
|
||||
func exit(code int)
|
||||
|
||||
func Exit(code int) { exit(code) }
|
||||
|
||||
func readnum(path string) (uint, error) {
|
||||
var b [12]byte
|
||||
|
||||
fd, e := Open(path, O_RDONLY)
|
||||
if e != nil {
|
||||
return 0, e
|
||||
}
|
||||
defer Close(fd)
|
||||
|
||||
n, e := Pread(fd, b[:], 0)
|
||||
|
||||
if e != nil {
|
||||
return 0, e
|
||||
}
|
||||
|
||||
m := 0
|
||||
for ; m < n && b[m] == ' '; m++ {
|
||||
}
|
||||
|
||||
return atoi(b[m : n-1]), nil
|
||||
}
|
||||
|
||||
func Getpid() (pid int) {
|
||||
n, _ := readnum("#c/pid")
|
||||
return int(n)
|
||||
}
|
||||
|
||||
func Getppid() (ppid int) {
|
||||
n, _ := readnum("#c/ppid")
|
||||
return int(n)
|
||||
}
|
||||
|
||||
func Read(fd int, p []byte) (n int, err error) {
|
||||
return Pread(fd, p, -1)
|
||||
}
|
||||
|
||||
func Write(fd int, p []byte) (n int, err error) {
|
||||
return Pwrite(fd, p, -1)
|
||||
}
|
||||
|
||||
var ioSync int64
|
||||
|
||||
//sys fd2path(fd int, buf []byte) (err error)
|
||||
|
||||
func Fd2path(fd int) (path string, err error) {
|
||||
var buf [512]byte
|
||||
|
||||
e := fd2path(fd, buf[:])
|
||||
if e != nil {
|
||||
return "", e
|
||||
}
|
||||
return cstring(buf[:]), nil
|
||||
}
|
||||
|
||||
//sys pipe(p *[2]int32) (err error)
|
||||
|
||||
func Pipe(p []int) (err error) {
|
||||
if len(p) != 2 {
|
||||
return syscall.ErrorString("bad arg in system call")
|
||||
}
|
||||
var pp [2]int32
|
||||
err = pipe(&pp)
|
||||
if err == nil {
|
||||
p[0] = int(pp[0])
|
||||
p[1] = int(pp[1])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Underlying system call writes to newoffset via pointer.
|
||||
// Implemented in assembly to avoid allocation.
|
||||
func seek(placeholder uintptr, fd int, offset int64, whence int) (newoffset int64, err string)
|
||||
|
||||
func Seek(fd int, offset int64, whence int) (newoffset int64, err error) {
|
||||
newoffset, e := seek(0, fd, offset, whence)
|
||||
|
||||
if newoffset == -1 {
|
||||
err = syscall.ErrorString(e)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Mkdir(path string, mode uint32) (err error) {
|
||||
fd, err := Create(path, O_RDONLY, DMDIR|mode)
|
||||
|
||||
if fd != -1 {
|
||||
Close(fd)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Waitmsg struct {
|
||||
Pid int
|
||||
Time [3]uint32
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (w Waitmsg) Exited() bool { return true }
|
||||
func (w Waitmsg) Signaled() bool { return false }
|
||||
|
||||
func (w Waitmsg) ExitStatus() int {
|
||||
if len(w.Msg) == 0 {
|
||||
// a normal exit returns no message
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
//sys await(s []byte) (n int, err error)
|
||||
|
||||
func Await(w *Waitmsg) (err error) {
|
||||
var buf [512]byte
|
||||
var f [5][]byte
|
||||
|
||||
n, err := await(buf[:])
|
||||
|
||||
if err != nil || w == nil {
|
||||
return
|
||||
}
|
||||
|
||||
nf := 0
|
||||
p := 0
|
||||
for i := 0; i < n && nf < len(f)-1; i++ {
|
||||
if buf[i] == ' ' {
|
||||
f[nf] = buf[p:i]
|
||||
p = i + 1
|
||||
nf++
|
||||
}
|
||||
}
|
||||
f[nf] = buf[p:]
|
||||
nf++
|
||||
|
||||
if nf != len(f) {
|
||||
return syscall.ErrorString("invalid wait message")
|
||||
}
|
||||
w.Pid = int(atoi(f[0]))
|
||||
w.Time[0] = uint32(atoi(f[1]))
|
||||
w.Time[1] = uint32(atoi(f[2]))
|
||||
w.Time[2] = uint32(atoi(f[3]))
|
||||
w.Msg = cstring(f[4])
|
||||
if w.Msg == "''" {
|
||||
// await() returns '' for no error
|
||||
w.Msg = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Unmount(name, old string) (err error) {
|
||||
fixwd()
|
||||
oldp, err := BytePtrFromString(old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldptr := uintptr(unsafe.Pointer(oldp))
|
||||
|
||||
var r0 uintptr
|
||||
var e syscall.ErrorString
|
||||
|
||||
// bind(2) man page: If name is zero, everything bound or mounted upon old is unbound or unmounted.
|
||||
if name == "" {
|
||||
r0, _, e = Syscall(SYS_UNMOUNT, _zero, oldptr, 0)
|
||||
} else {
|
||||
namep, err := BytePtrFromString(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r0, _, e = Syscall(SYS_UNMOUNT, uintptr(unsafe.Pointer(namep)), oldptr, 0)
|
||||
}
|
||||
|
||||
if int32(r0) == -1 {
|
||||
err = e
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Fchdir(fd int) (err error) {
|
||||
path, err := Fd2path(fd)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return Chdir(path)
|
||||
}
|
||||
|
||||
type Timespec struct {
|
||||
Sec int32
|
||||
Nsec int32
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int32
|
||||
Usec int32
|
||||
}
|
||||
|
||||
func NsecToTimeval(nsec int64) (tv Timeval) {
|
||||
nsec += 999 // round up to microsecond
|
||||
tv.Usec = int32(nsec % 1e9 / 1e3)
|
||||
tv.Sec = int32(nsec / 1e9)
|
||||
return
|
||||
}
|
||||
|
||||
func nsec() int64 {
|
||||
var scratch int64
|
||||
|
||||
r0, _, _ := Syscall(SYS_NSEC, uintptr(unsafe.Pointer(&scratch)), 0, 0)
|
||||
// TODO(aram): remove hack after I fix _nsec in the pc64 kernel.
|
||||
if r0 == 0 {
|
||||
return scratch
|
||||
}
|
||||
return int64(r0)
|
||||
}
|
||||
|
||||
func Gettimeofday(tv *Timeval) error {
|
||||
nsec := nsec()
|
||||
*tv = NsecToTimeval(nsec)
|
||||
return nil
|
||||
}
|
||||
|
||||
func Getpagesize() int { return 0x1000 }
|
||||
|
||||
func Getegid() (egid int) { return -1 }
|
||||
func Geteuid() (euid int) { return -1 }
|
||||
func Getgid() (gid int) { return -1 }
|
||||
func Getuid() (uid int) { return -1 }
|
||||
|
||||
func Getgroups() (gids []int, err error) {
|
||||
return make([]int, 0), nil
|
||||
}
|
||||
|
||||
//sys open(path string, mode int) (fd int, err error)
|
||||
|
||||
func Open(path string, mode int) (fd int, err error) {
|
||||
fixwd()
|
||||
return open(path, mode)
|
||||
}
|
||||
|
||||
//sys create(path string, mode int, perm uint32) (fd int, err error)
|
||||
|
||||
func Create(path string, mode int, perm uint32) (fd int, err error) {
|
||||
fixwd()
|
||||
return create(path, mode, perm)
|
||||
}
|
||||
|
||||
//sys remove(path string) (err error)
|
||||
|
||||
func Remove(path string) error {
|
||||
fixwd()
|
||||
return remove(path)
|
||||
}
|
||||
|
||||
//sys stat(path string, edir []byte) (n int, err error)
|
||||
|
||||
func Stat(path string, edir []byte) (n int, err error) {
|
||||
fixwd()
|
||||
return stat(path, edir)
|
||||
}
|
||||
|
||||
//sys bind(name string, old string, flag int) (err error)
|
||||
|
||||
func Bind(name string, old string, flag int) (err error) {
|
||||
fixwd()
|
||||
return bind(name, old, flag)
|
||||
}
|
||||
|
||||
//sys mount(fd int, afd int, old string, flag int, aname string) (err error)
|
||||
|
||||
func Mount(fd int, afd int, old string, flag int, aname string) (err error) {
|
||||
fixwd()
|
||||
return mount(fd, afd, old, flag, aname)
|
||||
}
|
||||
|
||||
//sys wstat(path string, edir []byte) (err error)
|
||||
|
||||
func Wstat(path string, edir []byte) (err error) {
|
||||
fixwd()
|
||||
return wstat(path, edir)
|
||||
}
|
||||
|
||||
//sys chdir(path string) (err error)
|
||||
//sys Dup(oldfd int, newfd int) (fd int, err error)
|
||||
//sys Pread(fd int, p []byte, offset int64) (n int, err error)
|
||||
//sys Pwrite(fd int, p []byte, offset int64) (n int, err error)
|
||||
//sys Close(fd int) (err error)
|
||||
//sys Fstat(fd int, edir []byte) (n int, err error)
|
||||
//sys Fwstat(fd int, edir []byte) (err error)
|
284
vendor/golang.org/x/sys/plan9/zsyscall_plan9_386.go
generated
vendored
Normal file
284
vendor/golang.org/x/sys/plan9/zsyscall_plan9_386.go
generated
vendored
Normal file
|
@ -0,0 +1,284 @@
|
|||
// go run mksyscall.go -l32 -plan9 -tags plan9,386 syscall_plan9.go
|
||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||
|
||||
//go:build plan9 && 386
|
||||
|
||||
package plan9
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func fd2path(fd int, buf []byte) (err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(buf) > 0 {
|
||||
_p0 = unsafe.Pointer(&buf[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_FD2PATH, uintptr(fd), uintptr(_p0), uintptr(len(buf)))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func pipe(p *[2]int32) (err error) {
|
||||
r0, _, e1 := Syscall(SYS_PIPE, uintptr(unsafe.Pointer(p)), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func await(s []byte) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(s) > 0 {
|
||||
_p0 = unsafe.Pointer(&s[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_AWAIT, uintptr(_p0), uintptr(len(s)), 0)
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func open(path string, mode int) (fd int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0)
|
||||
fd = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func create(path string, mode int, perm uint32) (fd int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_CREATE, uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(perm))
|
||||
fd = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func remove(path string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_REMOVE, uintptr(unsafe.Pointer(_p0)), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func stat(path string, edir []byte) (n int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p1 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p1 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_STAT, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(edir)))
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func bind(name string, old string, flag int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = BytePtrFromString(old)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_BIND, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flag))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func mount(fd int, afd int, old string, flag int, aname string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(old)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = BytePtrFromString(aname)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_MOUNT, uintptr(fd), uintptr(afd), uintptr(unsafe.Pointer(_p0)), uintptr(flag), uintptr(unsafe.Pointer(_p1)), 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func wstat(path string, edir []byte) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p1 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p1 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_WSTAT, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(edir)))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func chdir(path string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_CHDIR, uintptr(unsafe.Pointer(_p0)), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Dup(oldfd int, newfd int) (fd int, err error) {
|
||||
r0, _, e1 := Syscall(SYS_DUP, uintptr(oldfd), uintptr(newfd), 0)
|
||||
fd = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Pread(fd int, p []byte, offset int64) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(p) > 0 {
|
||||
_p0 = unsafe.Pointer(&p[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_PREAD, uintptr(fd), uintptr(_p0), uintptr(len(p)), uintptr(offset), uintptr(offset>>32), 0)
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Pwrite(fd int, p []byte, offset int64) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(p) > 0 {
|
||||
_p0 = unsafe.Pointer(&p[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_PWRITE, uintptr(fd), uintptr(_p0), uintptr(len(p)), uintptr(offset), uintptr(offset>>32), 0)
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Close(fd int) (err error) {
|
||||
r0, _, e1 := Syscall(SYS_CLOSE, uintptr(fd), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fstat(fd int, edir []byte) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p0 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_FSTAT, uintptr(fd), uintptr(_p0), uintptr(len(edir)))
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fwstat(fd int, edir []byte) (err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p0 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_FWSTAT, uintptr(fd), uintptr(_p0), uintptr(len(edir)))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
284
vendor/golang.org/x/sys/plan9/zsyscall_plan9_amd64.go
generated
vendored
Normal file
284
vendor/golang.org/x/sys/plan9/zsyscall_plan9_amd64.go
generated
vendored
Normal file
|
@ -0,0 +1,284 @@
|
|||
// go run mksyscall.go -l32 -plan9 -tags plan9,amd64 syscall_plan9.go
|
||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||
|
||||
//go:build plan9 && amd64
|
||||
|
||||
package plan9
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func fd2path(fd int, buf []byte) (err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(buf) > 0 {
|
||||
_p0 = unsafe.Pointer(&buf[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_FD2PATH, uintptr(fd), uintptr(_p0), uintptr(len(buf)))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func pipe(p *[2]int32) (err error) {
|
||||
r0, _, e1 := Syscall(SYS_PIPE, uintptr(unsafe.Pointer(p)), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func await(s []byte) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(s) > 0 {
|
||||
_p0 = unsafe.Pointer(&s[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_AWAIT, uintptr(_p0), uintptr(len(s)), 0)
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func open(path string, mode int) (fd int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0)
|
||||
fd = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func create(path string, mode int, perm uint32) (fd int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_CREATE, uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(perm))
|
||||
fd = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func remove(path string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_REMOVE, uintptr(unsafe.Pointer(_p0)), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func stat(path string, edir []byte) (n int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p1 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p1 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_STAT, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(edir)))
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func bind(name string, old string, flag int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = BytePtrFromString(old)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_BIND, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flag))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func mount(fd int, afd int, old string, flag int, aname string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(old)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = BytePtrFromString(aname)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_MOUNT, uintptr(fd), uintptr(afd), uintptr(unsafe.Pointer(_p0)), uintptr(flag), uintptr(unsafe.Pointer(_p1)), 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func wstat(path string, edir []byte) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p1 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p1 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_WSTAT, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(edir)))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func chdir(path string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_CHDIR, uintptr(unsafe.Pointer(_p0)), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Dup(oldfd int, newfd int) (fd int, err error) {
|
||||
r0, _, e1 := Syscall(SYS_DUP, uintptr(oldfd), uintptr(newfd), 0)
|
||||
fd = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Pread(fd int, p []byte, offset int64) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(p) > 0 {
|
||||
_p0 = unsafe.Pointer(&p[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_PREAD, uintptr(fd), uintptr(_p0), uintptr(len(p)), uintptr(offset), uintptr(offset>>32), 0)
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Pwrite(fd int, p []byte, offset int64) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(p) > 0 {
|
||||
_p0 = unsafe.Pointer(&p[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_PWRITE, uintptr(fd), uintptr(_p0), uintptr(len(p)), uintptr(offset), uintptr(offset>>32), 0)
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Close(fd int) (err error) {
|
||||
r0, _, e1 := Syscall(SYS_CLOSE, uintptr(fd), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fstat(fd int, edir []byte) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p0 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_FSTAT, uintptr(fd), uintptr(_p0), uintptr(len(edir)))
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fwstat(fd int, edir []byte) (err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p0 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_FWSTAT, uintptr(fd), uintptr(_p0), uintptr(len(edir)))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
284
vendor/golang.org/x/sys/plan9/zsyscall_plan9_arm.go
generated
vendored
Normal file
284
vendor/golang.org/x/sys/plan9/zsyscall_plan9_arm.go
generated
vendored
Normal file
|
@ -0,0 +1,284 @@
|
|||
// go run mksyscall.go -l32 -plan9 -tags plan9,arm syscall_plan9.go
|
||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||
|
||||
//go:build plan9 && arm
|
||||
|
||||
package plan9
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func fd2path(fd int, buf []byte) (err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(buf) > 0 {
|
||||
_p0 = unsafe.Pointer(&buf[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_FD2PATH, uintptr(fd), uintptr(_p0), uintptr(len(buf)))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func pipe(p *[2]int32) (err error) {
|
||||
r0, _, e1 := Syscall(SYS_PIPE, uintptr(unsafe.Pointer(p)), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func await(s []byte) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(s) > 0 {
|
||||
_p0 = unsafe.Pointer(&s[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_AWAIT, uintptr(_p0), uintptr(len(s)), 0)
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func open(path string, mode int) (fd int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0)
|
||||
fd = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func create(path string, mode int, perm uint32) (fd int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_CREATE, uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(perm))
|
||||
fd = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func remove(path string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_REMOVE, uintptr(unsafe.Pointer(_p0)), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func stat(path string, edir []byte) (n int, err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p1 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p1 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_STAT, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(edir)))
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func bind(name string, old string, flag int) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = BytePtrFromString(old)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_BIND, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flag))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func mount(fd int, afd int, old string, flag int, aname string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(old)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 *byte
|
||||
_p1, err = BytePtrFromString(aname)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_MOUNT, uintptr(fd), uintptr(afd), uintptr(unsafe.Pointer(_p0)), uintptr(flag), uintptr(unsafe.Pointer(_p1)), 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func wstat(path string, edir []byte) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var _p1 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p1 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p1 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_WSTAT, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(edir)))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func chdir(path string) (err error) {
|
||||
var _p0 *byte
|
||||
_p0, err = BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_CHDIR, uintptr(unsafe.Pointer(_p0)), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Dup(oldfd int, newfd int) (fd int, err error) {
|
||||
r0, _, e1 := Syscall(SYS_DUP, uintptr(oldfd), uintptr(newfd), 0)
|
||||
fd = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Pread(fd int, p []byte, offset int64) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(p) > 0 {
|
||||
_p0 = unsafe.Pointer(&p[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_PREAD, uintptr(fd), uintptr(_p0), uintptr(len(p)), uintptr(offset), uintptr(offset>>32), 0)
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Pwrite(fd int, p []byte, offset int64) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(p) > 0 {
|
||||
_p0 = unsafe.Pointer(&p[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_PWRITE, uintptr(fd), uintptr(_p0), uintptr(len(p)), uintptr(offset), uintptr(offset>>32), 0)
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Close(fd int) (err error) {
|
||||
r0, _, e1 := Syscall(SYS_CLOSE, uintptr(fd), 0, 0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fstat(fd int, edir []byte) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p0 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_FSTAT, uintptr(fd), uintptr(_p0), uintptr(len(edir)))
|
||||
n = int(r0)
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fwstat(fd int, edir []byte) (err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(edir) > 0 {
|
||||
_p0 = unsafe.Pointer(&edir[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall(SYS_FWSTAT, uintptr(fd), uintptr(_p0), uintptr(len(edir)))
|
||||
if int32(r0) == -1 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
49
vendor/golang.org/x/sys/plan9/zsysnum_plan9.go
generated
vendored
Normal file
49
vendor/golang.org/x/sys/plan9/zsysnum_plan9.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
// mksysnum_plan9.sh /opt/plan9/sys/src/libc/9syscall/sys.h
|
||||
// MACHINE GENERATED BY THE ABOVE COMMAND; DO NOT EDIT
|
||||
|
||||
package plan9
|
||||
|
||||
const (
|
||||
SYS_SYSR1 = 0
|
||||
SYS_BIND = 2
|
||||
SYS_CHDIR = 3
|
||||
SYS_CLOSE = 4
|
||||
SYS_DUP = 5
|
||||
SYS_ALARM = 6
|
||||
SYS_EXEC = 7
|
||||
SYS_EXITS = 8
|
||||
SYS_FAUTH = 10
|
||||
SYS_SEGBRK = 12
|
||||
SYS_OPEN = 14
|
||||
SYS_OSEEK = 16
|
||||
SYS_SLEEP = 17
|
||||
SYS_RFORK = 19
|
||||
SYS_PIPE = 21
|
||||
SYS_CREATE = 22
|
||||
SYS_FD2PATH = 23
|
||||
SYS_BRK_ = 24
|
||||
SYS_REMOVE = 25
|
||||
SYS_NOTIFY = 28
|
||||
SYS_NOTED = 29
|
||||
SYS_SEGATTACH = 30
|
||||
SYS_SEGDETACH = 31
|
||||
SYS_SEGFREE = 32
|
||||
SYS_SEGFLUSH = 33
|
||||
SYS_RENDEZVOUS = 34
|
||||
SYS_UNMOUNT = 35
|
||||
SYS_SEMACQUIRE = 37
|
||||
SYS_SEMRELEASE = 38
|
||||
SYS_SEEK = 39
|
||||
SYS_FVERSION = 40
|
||||
SYS_ERRSTR = 41
|
||||
SYS_STAT = 42
|
||||
SYS_FSTAT = 43
|
||||
SYS_WSTAT = 44
|
||||
SYS_FWSTAT = 45
|
||||
SYS_MOUNT = 46
|
||||
SYS_AWAIT = 47
|
||||
SYS_PREAD = 50
|
||||
SYS_PWRITE = 51
|
||||
SYS_TSEMACQUIRE = 52
|
||||
SYS_NSEC = 53
|
||||
)
|
28
vendor/golang.org/x/text/width/kind_string.go
generated
vendored
Normal file
28
vendor/golang.org/x/text/width/kind_string.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Code generated by "stringer -type=Kind"; DO NOT EDIT.
|
||||
|
||||
package width
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Neutral-0]
|
||||
_ = x[EastAsianAmbiguous-1]
|
||||
_ = x[EastAsianWide-2]
|
||||
_ = x[EastAsianNarrow-3]
|
||||
_ = x[EastAsianFullwidth-4]
|
||||
_ = x[EastAsianHalfwidth-5]
|
||||
}
|
||||
|
||||
const _Kind_name = "NeutralEastAsianAmbiguousEastAsianWideEastAsianNarrowEastAsianFullwidthEastAsianHalfwidth"
|
||||
|
||||
var _Kind_index = [...]uint8{0, 7, 25, 38, 53, 71, 89}
|
||||
|
||||
func (i Kind) String() string {
|
||||
if i < 0 || i >= Kind(len(_Kind_index)-1) {
|
||||
return "Kind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Kind_name[_Kind_index[i]:_Kind_index[i+1]]
|
||||
}
|
1328
vendor/golang.org/x/text/width/tables10.0.0.go
generated
vendored
Normal file
1328
vendor/golang.org/x/text/width/tables10.0.0.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1340
vendor/golang.org/x/text/width/tables11.0.0.go
generated
vendored
Normal file
1340
vendor/golang.org/x/text/width/tables11.0.0.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1360
vendor/golang.org/x/text/width/tables12.0.0.go
generated
vendored
Normal file
1360
vendor/golang.org/x/text/width/tables12.0.0.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1361
vendor/golang.org/x/text/width/tables13.0.0.go
generated
vendored
Normal file
1361
vendor/golang.org/x/text/width/tables13.0.0.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1367
vendor/golang.org/x/text/width/tables15.0.0.go
generated
vendored
Normal file
1367
vendor/golang.org/x/text/width/tables15.0.0.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1296
vendor/golang.org/x/text/width/tables9.0.0.go
generated
vendored
Normal file
1296
vendor/golang.org/x/text/width/tables9.0.0.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
239
vendor/golang.org/x/text/width/transform.go
generated
vendored
Normal file
239
vendor/golang.org/x/text/width/transform.go
generated
vendored
Normal file
|
@ -0,0 +1,239 @@
|
|||
// Copyright 2015 The Go 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 width
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
type foldTransform struct {
|
||||
transform.NopResetter
|
||||
}
|
||||
|
||||
func (foldTransform) Span(src []byte, atEOF bool) (n int, err error) {
|
||||
for n < len(src) {
|
||||
if src[n] < utf8.RuneSelf {
|
||||
// ASCII fast path.
|
||||
for n++; n < len(src) && src[n] < utf8.RuneSelf; n++ {
|
||||
}
|
||||
continue
|
||||
}
|
||||
v, size := trie.lookup(src[n:])
|
||||
if size == 0 { // incomplete UTF-8 encoding
|
||||
if !atEOF {
|
||||
err = transform.ErrShortSrc
|
||||
} else {
|
||||
n = len(src)
|
||||
}
|
||||
break
|
||||
}
|
||||
if elem(v)&tagNeedsFold != 0 {
|
||||
err = transform.ErrEndOfSpan
|
||||
break
|
||||
}
|
||||
n += size
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (foldTransform) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
for nSrc < len(src) {
|
||||
if src[nSrc] < utf8.RuneSelf {
|
||||
// ASCII fast path.
|
||||
start, end := nSrc, len(src)
|
||||
if d := len(dst) - nDst; d < end-start {
|
||||
end = nSrc + d
|
||||
}
|
||||
for nSrc++; nSrc < end && src[nSrc] < utf8.RuneSelf; nSrc++ {
|
||||
}
|
||||
n := copy(dst[nDst:], src[start:nSrc])
|
||||
if nDst += n; nDst == len(dst) {
|
||||
nSrc = start + n
|
||||
if nSrc == len(src) {
|
||||
return nDst, nSrc, nil
|
||||
}
|
||||
if src[nSrc] < utf8.RuneSelf {
|
||||
return nDst, nSrc, transform.ErrShortDst
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
v, size := trie.lookup(src[nSrc:])
|
||||
if size == 0 { // incomplete UTF-8 encoding
|
||||
if !atEOF {
|
||||
return nDst, nSrc, transform.ErrShortSrc
|
||||
}
|
||||
size = 1 // gobble 1 byte
|
||||
}
|
||||
if elem(v)&tagNeedsFold == 0 {
|
||||
if size != copy(dst[nDst:], src[nSrc:nSrc+size]) {
|
||||
return nDst, nSrc, transform.ErrShortDst
|
||||
}
|
||||
nDst += size
|
||||
} else {
|
||||
data := inverseData[byte(v)]
|
||||
if len(dst)-nDst < int(data[0]) {
|
||||
return nDst, nSrc, transform.ErrShortDst
|
||||
}
|
||||
i := 1
|
||||
for end := int(data[0]); i < end; i++ {
|
||||
dst[nDst] = data[i]
|
||||
nDst++
|
||||
}
|
||||
dst[nDst] = data[i] ^ src[nSrc+size-1]
|
||||
nDst++
|
||||
}
|
||||
nSrc += size
|
||||
}
|
||||
return nDst, nSrc, nil
|
||||
}
|
||||
|
||||
type narrowTransform struct {
|
||||
transform.NopResetter
|
||||
}
|
||||
|
||||
func (narrowTransform) Span(src []byte, atEOF bool) (n int, err error) {
|
||||
for n < len(src) {
|
||||
if src[n] < utf8.RuneSelf {
|
||||
// ASCII fast path.
|
||||
for n++; n < len(src) && src[n] < utf8.RuneSelf; n++ {
|
||||
}
|
||||
continue
|
||||
}
|
||||
v, size := trie.lookup(src[n:])
|
||||
if size == 0 { // incomplete UTF-8 encoding
|
||||
if !atEOF {
|
||||
err = transform.ErrShortSrc
|
||||
} else {
|
||||
n = len(src)
|
||||
}
|
||||
break
|
||||
}
|
||||
if k := elem(v).kind(); byte(v) == 0 || k != EastAsianFullwidth && k != EastAsianWide && k != EastAsianAmbiguous {
|
||||
} else {
|
||||
err = transform.ErrEndOfSpan
|
||||
break
|
||||
}
|
||||
n += size
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (narrowTransform) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
for nSrc < len(src) {
|
||||
if src[nSrc] < utf8.RuneSelf {
|
||||
// ASCII fast path.
|
||||
start, end := nSrc, len(src)
|
||||
if d := len(dst) - nDst; d < end-start {
|
||||
end = nSrc + d
|
||||
}
|
||||
for nSrc++; nSrc < end && src[nSrc] < utf8.RuneSelf; nSrc++ {
|
||||
}
|
||||
n := copy(dst[nDst:], src[start:nSrc])
|
||||
if nDst += n; nDst == len(dst) {
|
||||
nSrc = start + n
|
||||
if nSrc == len(src) {
|
||||
return nDst, nSrc, nil
|
||||
}
|
||||
if src[nSrc] < utf8.RuneSelf {
|
||||
return nDst, nSrc, transform.ErrShortDst
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
v, size := trie.lookup(src[nSrc:])
|
||||
if size == 0 { // incomplete UTF-8 encoding
|
||||
if !atEOF {
|
||||
return nDst, nSrc, transform.ErrShortSrc
|
||||
}
|
||||
size = 1 // gobble 1 byte
|
||||
}
|
||||
if k := elem(v).kind(); byte(v) == 0 || k != EastAsianFullwidth && k != EastAsianWide && k != EastAsianAmbiguous {
|
||||
if size != copy(dst[nDst:], src[nSrc:nSrc+size]) {
|
||||
return nDst, nSrc, transform.ErrShortDst
|
||||
}
|
||||
nDst += size
|
||||
} else {
|
||||
data := inverseData[byte(v)]
|
||||
if len(dst)-nDst < int(data[0]) {
|
||||
return nDst, nSrc, transform.ErrShortDst
|
||||
}
|
||||
i := 1
|
||||
for end := int(data[0]); i < end; i++ {
|
||||
dst[nDst] = data[i]
|
||||
nDst++
|
||||
}
|
||||
dst[nDst] = data[i] ^ src[nSrc+size-1]
|
||||
nDst++
|
||||
}
|
||||
nSrc += size
|
||||
}
|
||||
return nDst, nSrc, nil
|
||||
}
|
||||
|
||||
type wideTransform struct {
|
||||
transform.NopResetter
|
||||
}
|
||||
|
||||
func (wideTransform) Span(src []byte, atEOF bool) (n int, err error) {
|
||||
for n < len(src) {
|
||||
// TODO: Consider ASCII fast path. Special-casing ASCII handling can
|
||||
// reduce the ns/op of BenchmarkWideASCII by about 30%. This is probably
|
||||
// not enough to warrant the extra code and complexity.
|
||||
v, size := trie.lookup(src[n:])
|
||||
if size == 0 { // incomplete UTF-8 encoding
|
||||
if !atEOF {
|
||||
err = transform.ErrShortSrc
|
||||
} else {
|
||||
n = len(src)
|
||||
}
|
||||
break
|
||||
}
|
||||
if k := elem(v).kind(); byte(v) == 0 || k != EastAsianHalfwidth && k != EastAsianNarrow {
|
||||
} else {
|
||||
err = transform.ErrEndOfSpan
|
||||
break
|
||||
}
|
||||
n += size
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (wideTransform) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
for nSrc < len(src) {
|
||||
// TODO: Consider ASCII fast path. Special-casing ASCII handling can
|
||||
// reduce the ns/op of BenchmarkWideASCII by about 30%. This is probably
|
||||
// not enough to warrant the extra code and complexity.
|
||||
v, size := trie.lookup(src[nSrc:])
|
||||
if size == 0 { // incomplete UTF-8 encoding
|
||||
if !atEOF {
|
||||
return nDst, nSrc, transform.ErrShortSrc
|
||||
}
|
||||
size = 1 // gobble 1 byte
|
||||
}
|
||||
if k := elem(v).kind(); byte(v) == 0 || k != EastAsianHalfwidth && k != EastAsianNarrow {
|
||||
if size != copy(dst[nDst:], src[nSrc:nSrc+size]) {
|
||||
return nDst, nSrc, transform.ErrShortDst
|
||||
}
|
||||
nDst += size
|
||||
} else {
|
||||
data := inverseData[byte(v)]
|
||||
if len(dst)-nDst < int(data[0]) {
|
||||
return nDst, nSrc, transform.ErrShortDst
|
||||
}
|
||||
i := 1
|
||||
for end := int(data[0]); i < end; i++ {
|
||||
dst[nDst] = data[i]
|
||||
nDst++
|
||||
}
|
||||
dst[nDst] = data[i] ^ src[nSrc+size-1]
|
||||
nDst++
|
||||
}
|
||||
nSrc += size
|
||||
}
|
||||
return nDst, nSrc, nil
|
||||
}
|
30
vendor/golang.org/x/text/width/trieval.go
generated
vendored
Normal file
30
vendor/golang.org/x/text/width/trieval.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
package width
|
||||
|
||||
// elem is an entry of the width trie. The high byte is used to encode the type
|
||||
// of the rune. The low byte is used to store the index to a mapping entry in
|
||||
// the inverseData array.
|
||||
type elem uint16
|
||||
|
||||
const (
|
||||
tagNeutral elem = iota << typeShift
|
||||
tagAmbiguous
|
||||
tagWide
|
||||
tagNarrow
|
||||
tagFullwidth
|
||||
tagHalfwidth
|
||||
)
|
||||
|
||||
const (
|
||||
numTypeBits = 3
|
||||
typeShift = 16 - numTypeBits
|
||||
|
||||
// tagNeedsFold is true for all fullwidth and halfwidth runes except for
|
||||
// the Won sign U+20A9.
|
||||
tagNeedsFold = 0x1000
|
||||
|
||||
// The Korean Won sign is halfwidth, but SHOULD NOT be mapped to a wide
|
||||
// variant.
|
||||
wonSign rune = 0x20A9
|
||||
)
|
206
vendor/golang.org/x/text/width/width.go
generated
vendored
Normal file
206
vendor/golang.org/x/text/width/width.go
generated
vendored
Normal file
|
@ -0,0 +1,206 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:generate stringer -type=Kind
|
||||
//go:generate go run gen.go gen_common.go gen_trieval.go
|
||||
|
||||
// Package width provides functionality for handling different widths in text.
|
||||
//
|
||||
// Wide characters behave like ideographs; they tend to allow line breaks after
|
||||
// each character and remain upright in vertical text layout. Narrow characters
|
||||
// are kept together in words or runs that are rotated sideways in vertical text
|
||||
// layout.
|
||||
//
|
||||
// For more information, see https://unicode.org/reports/tr11/.
|
||||
package width // import "golang.org/x/text/width"
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
// TODO
|
||||
// 1) Reduce table size by compressing blocks.
|
||||
// 2) API proposition for computing display length
|
||||
// (approximation, fixed pitch only).
|
||||
// 3) Implement display length.
|
||||
|
||||
// Kind indicates the type of width property as defined in https://unicode.org/reports/tr11/.
|
||||
type Kind int
|
||||
|
||||
const (
|
||||
// Neutral characters do not occur in legacy East Asian character sets.
|
||||
Neutral Kind = iota
|
||||
|
||||
// EastAsianAmbiguous characters that can be sometimes wide and sometimes
|
||||
// narrow and require additional information not contained in the character
|
||||
// code to further resolve their width.
|
||||
EastAsianAmbiguous
|
||||
|
||||
// EastAsianWide characters are wide in its usual form. They occur only in
|
||||
// the context of East Asian typography. These runes may have explicit
|
||||
// halfwidth counterparts.
|
||||
EastAsianWide
|
||||
|
||||
// EastAsianNarrow characters are narrow in its usual form. They often have
|
||||
// fullwidth counterparts.
|
||||
EastAsianNarrow
|
||||
|
||||
// Note: there exist Narrow runes that do not have fullwidth or wide
|
||||
// counterparts, despite what the definition says (e.g. U+27E6).
|
||||
|
||||
// EastAsianFullwidth characters have a compatibility decompositions of type
|
||||
// wide that map to a narrow counterpart.
|
||||
EastAsianFullwidth
|
||||
|
||||
// EastAsianHalfwidth characters have a compatibility decomposition of type
|
||||
// narrow that map to a wide or ambiguous counterpart, plus U+20A9 ₩ WON
|
||||
// SIGN.
|
||||
EastAsianHalfwidth
|
||||
|
||||
// Note: there exist runes that have a halfwidth counterparts but that are
|
||||
// classified as Ambiguous, rather than wide (e.g. U+2190).
|
||||
)
|
||||
|
||||
// TODO: the generated tries need to return size 1 for invalid runes for the
|
||||
// width to be computed correctly (each byte should render width 1)
|
||||
|
||||
var trie = newWidthTrie(0)
|
||||
|
||||
// Lookup reports the Properties of the first rune in b and the number of bytes
|
||||
// of its UTF-8 encoding.
|
||||
func Lookup(b []byte) (p Properties, size int) {
|
||||
v, sz := trie.lookup(b)
|
||||
return Properties{elem(v), b[sz-1]}, sz
|
||||
}
|
||||
|
||||
// LookupString reports the Properties of the first rune in s and the number of
|
||||
// bytes of its UTF-8 encoding.
|
||||
func LookupString(s string) (p Properties, size int) {
|
||||
v, sz := trie.lookupString(s)
|
||||
return Properties{elem(v), s[sz-1]}, sz
|
||||
}
|
||||
|
||||
// LookupRune reports the Properties of rune r.
|
||||
func LookupRune(r rune) Properties {
|
||||
var buf [4]byte
|
||||
n := utf8.EncodeRune(buf[:], r)
|
||||
v, _ := trie.lookup(buf[:n])
|
||||
last := byte(r)
|
||||
if r >= utf8.RuneSelf {
|
||||
last = 0x80 + byte(r&0x3f)
|
||||
}
|
||||
return Properties{elem(v), last}
|
||||
}
|
||||
|
||||
// Properties provides access to width properties of a rune.
|
||||
type Properties struct {
|
||||
elem elem
|
||||
last byte
|
||||
}
|
||||
|
||||
func (e elem) kind() Kind {
|
||||
return Kind(e >> typeShift)
|
||||
}
|
||||
|
||||
// Kind returns the Kind of a rune as defined in Unicode TR #11.
|
||||
// See https://unicode.org/reports/tr11/ for more details.
|
||||
func (p Properties) Kind() Kind {
|
||||
return p.elem.kind()
|
||||
}
|
||||
|
||||
// Folded returns the folded variant of a rune or 0 if the rune is canonical.
|
||||
func (p Properties) Folded() rune {
|
||||
if p.elem&tagNeedsFold != 0 {
|
||||
buf := inverseData[byte(p.elem)]
|
||||
buf[buf[0]] ^= p.last
|
||||
r, _ := utf8.DecodeRune(buf[1 : 1+buf[0]])
|
||||
return r
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Narrow returns the narrow variant of a rune or 0 if the rune is already
|
||||
// narrow or doesn't have a narrow variant.
|
||||
func (p Properties) Narrow() rune {
|
||||
if k := p.elem.kind(); byte(p.elem) != 0 && (k == EastAsianFullwidth || k == EastAsianWide || k == EastAsianAmbiguous) {
|
||||
buf := inverseData[byte(p.elem)]
|
||||
buf[buf[0]] ^= p.last
|
||||
r, _ := utf8.DecodeRune(buf[1 : 1+buf[0]])
|
||||
return r
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Wide returns the wide variant of a rune or 0 if the rune is already
|
||||
// wide or doesn't have a wide variant.
|
||||
func (p Properties) Wide() rune {
|
||||
if k := p.elem.kind(); byte(p.elem) != 0 && (k == EastAsianHalfwidth || k == EastAsianNarrow) {
|
||||
buf := inverseData[byte(p.elem)]
|
||||
buf[buf[0]] ^= p.last
|
||||
r, _ := utf8.DecodeRune(buf[1 : 1+buf[0]])
|
||||
return r
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// TODO for Properties:
|
||||
// - Add Fullwidth/Halfwidth or Inverted methods for computing variants
|
||||
// mapping.
|
||||
// - Add width information (including information on non-spacing runes).
|
||||
|
||||
// Transformer implements the transform.Transformer interface.
|
||||
type Transformer struct {
|
||||
t transform.SpanningTransformer
|
||||
}
|
||||
|
||||
// Reset implements the transform.Transformer interface.
|
||||
func (t Transformer) Reset() { t.t.Reset() }
|
||||
|
||||
// Transform implements the transform.Transformer interface.
|
||||
func (t Transformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
return t.t.Transform(dst, src, atEOF)
|
||||
}
|
||||
|
||||
// Span implements the transform.SpanningTransformer interface.
|
||||
func (t Transformer) Span(src []byte, atEOF bool) (n int, err error) {
|
||||
return t.t.Span(src, atEOF)
|
||||
}
|
||||
|
||||
// Bytes returns a new byte slice with the result of applying t to b.
|
||||
func (t Transformer) Bytes(b []byte) []byte {
|
||||
b, _, _ = transform.Bytes(t, b)
|
||||
return b
|
||||
}
|
||||
|
||||
// String returns a string with the result of applying t to s.
|
||||
func (t Transformer) String(s string) string {
|
||||
s, _, _ = transform.String(t, s)
|
||||
return s
|
||||
}
|
||||
|
||||
var (
|
||||
// Fold is a transform that maps all runes to their canonical width.
|
||||
//
|
||||
// Note that the NFKC and NFKD transforms in golang.org/x/text/unicode/norm
|
||||
// provide a more generic folding mechanism.
|
||||
Fold Transformer = Transformer{foldTransform{}}
|
||||
|
||||
// Widen is a transform that maps runes to their wide variant, if
|
||||
// available.
|
||||
Widen Transformer = Transformer{wideTransform{}}
|
||||
|
||||
// Narrow is a transform that maps runes to their narrow variant, if
|
||||
// available.
|
||||
Narrow Transformer = Transformer{narrowTransform{}}
|
||||
)
|
||||
|
||||
// TODO: Consider the following options:
|
||||
// - Treat Ambiguous runes that have a halfwidth counterpart as wide, or some
|
||||
// generalized variant of this.
|
||||
// - Consider a wide Won character to be the default width (or some generalized
|
||||
// variant of this).
|
||||
// - Filter the set of characters that gets converted (the preferred approach is
|
||||
// to allow applying filters to transforms).
|
10
vendor/modules.txt
vendored
10
vendor/modules.txt
vendored
|
@ -329,6 +329,14 @@ github.com/davecgh/go-spew/spew
|
|||
# github.com/dennwc/varint v1.0.0
|
||||
## explicit; go 1.12
|
||||
github.com/dennwc/varint
|
||||
# github.com/ergochat/readline v0.1.3
|
||||
## explicit; go 1.19
|
||||
github.com/ergochat/readline
|
||||
github.com/ergochat/readline/internal/ansi
|
||||
github.com/ergochat/readline/internal/platform
|
||||
github.com/ergochat/readline/internal/ringbuf
|
||||
github.com/ergochat/readline/internal/runes
|
||||
github.com/ergochat/readline/internal/term
|
||||
# github.com/fatih/color v1.17.0
|
||||
## explicit; go 1.17
|
||||
github.com/fatih/color
|
||||
|
@ -714,6 +722,7 @@ golang.org/x/sync/semaphore
|
|||
# golang.org/x/sys v0.25.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/sys/cpu
|
||||
golang.org/x/sys/plan9
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
golang.org/x/sys/windows/registry
|
||||
|
@ -723,6 +732,7 @@ golang.org/x/text/secure/bidirule
|
|||
golang.org/x/text/transform
|
||||
golang.org/x/text/unicode/bidi
|
||||
golang.org/x/text/unicode/norm
|
||||
golang.org/x/text/width
|
||||
# golang.org/x/time v0.6.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/time/rate
|
||||
|
|
Loading…
Reference in a new issue