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: \
|
all: \
|
||||||
victoria-metrics-prod \
|
victoria-metrics-prod \
|
||||||
victoria-logs-prod \
|
victoria-logs-prod \
|
||||||
|
vlogscli-prod \
|
||||||
vmagent-prod \
|
vmagent-prod \
|
||||||
vmalert-prod \
|
vmalert-prod \
|
||||||
vmalert-tool-prod \
|
vmalert-tool-prod \
|
||||||
|
@ -51,6 +52,7 @@ publish: \
|
||||||
package: \
|
package: \
|
||||||
package-victoria-metrics \
|
package-victoria-metrics \
|
||||||
package-victoria-logs \
|
package-victoria-logs \
|
||||||
|
package-vlogscli \
|
||||||
package-vmagent \
|
package-vmagent \
|
||||||
package-vmalert \
|
package-vmalert \
|
||||||
package-vmalert-tool \
|
package-vmalert-tool \
|
||||||
|
@ -320,6 +322,63 @@ release-victoria-logs-windows-goarch: victoria-logs-windows-$(GOARCH)-prod
|
||||||
cd bin && rm -rf \
|
cd bin && rm -rf \
|
||||||
victoria-logs-windows-$(GOARCH)-prod.exe
|
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: \
|
||||||
release-vmutils-linux-386 \
|
release-vmutils-linux-386 \
|
||||||
release-vmutils-linux-amd64 \
|
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
|
## 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)
|
## [v0.32.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.32.1-victorialogs)
|
||||||
|
|
||||||
Released at 2024-09-30
|
Released at 2024-09-30
|
||||||
|
|
|
@ -3,25 +3,27 @@ from [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics/).
|
||||||
|
|
||||||
VictoriaLogs provides the following features:
|
VictoriaLogs provides the following features:
|
||||||
|
|
||||||
- VictoriaLogs can accept logs from popular log collectors. See [these docs](https://docs.victoriametrics.com/victorialogs/data-ingestion/).
|
- It 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 is much easier to set up and operate compared to Elasticsearch and Grafana Loki.
|
||||||
See [these docs](https://docs.victoriametrics.com/victorialogs/quickstart/).
|
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).
|
all the [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
|
||||||
See [LogsQL docs](https://docs.victoriametrics.com/victorialogs/logsql/).
|
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.
|
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).
|
- 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.
|
It runs smoothly on Raspberry PI and on servers 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.
|
- It can handle up to 30x bigger data volumes than Elasticsearch and Grafana Loki when running on the same hardware.
|
||||||
See [these docs](#benchmarks).
|
See [these docs](#benchmarks).
|
||||||
- VictoriaLogs supports fast full-text search over high-cardinality [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
|
- It provides fast full-text search out of the box for [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
|
||||||
such as `trace_id`, `user_id` and `ip`.
|
with high cardinality (e.g. high number of unique values) such as `trace_id`, `user_id` and `ip`.
|
||||||
- VictoriaLogs supports multitenancy - see [these docs](#multitenancy).
|
- It supports multitenancy - see [these docs](#multitenancy).
|
||||||
- VictoriaLogs supports out-of-order logs' ingestion aka backfilling.
|
- It 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).
|
- It 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).
|
- It 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 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/).
|
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/),
|
Also feel free asking any questions at [VictoriaMetrics community Slack chat](https://victoriametrics.slack.com/),
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
via the following ways:
|
via the following ways:
|
||||||
|
|
||||||
- [Web UI](#web-ui) - a web-based UI for querying logs
|
- [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)
|
- [HTTP API](#http-api)
|
||||||
- [Command-line interface](#command-line)
|
- [Command-line interface](#command-line)
|
||||||
|
|
||||||
|
@ -800,7 +800,10 @@ See also [command line interface](#command-line).
|
||||||
|
|
||||||
## 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.
|
- 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))
|
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/bmatcuk/doublestar/v4 v4.6.1
|
||||||
github.com/cespare/xxhash/v2 v2.3.0
|
github.com/cespare/xxhash/v2 v2.3.0
|
||||||
github.com/cheggaaa/pb/v3 v3.1.5
|
github.com/cheggaaa/pb/v3 v3.1.5
|
||||||
|
github.com/ergochat/readline v0.1.3
|
||||||
github.com/gogo/protobuf v1.3.2
|
github.com/gogo/protobuf v1.3.2
|
||||||
github.com/golang/snappy v0.0.4
|
github.com/golang/snappy v0.0.4
|
||||||
github.com/googleapis/gax-go/v2 v2.13.0
|
github.com/googleapis/gax-go/v2 v2.13.0
|
||||||
github.com/influxdata/influxdb v1.11.6
|
github.com/influxdata/influxdb v1.11.6
|
||||||
github.com/klauspost/compress v1.17.10
|
github.com/klauspost/compress v1.17.10
|
||||||
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/prometheus/prometheus v0.54.1
|
github.com/prometheus/prometheus v0.54.1
|
||||||
github.com/urfave/cli/v2 v2.27.4
|
github.com/urfave/cli/v2 v2.27.4
|
||||||
github.com/valyala/fastjson v1.6.4
|
github.com/valyala/fastjson v1.6.4
|
||||||
|
@ -87,7 +89,6 @@ require (
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // 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/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // 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 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 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
|
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 h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
|
||||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
|
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
|
||||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||||
|
|
|
@ -37,14 +37,28 @@ var (
|
||||||
//
|
//
|
||||||
// There is no need in calling Init from tests.
|
// There is no need in calling Init from tests.
|
||||||
func Init() {
|
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()
|
setLoggerJSONFields()
|
||||||
setLoggerOutput()
|
setLoggerOutput()
|
||||||
validateLoggerLevel()
|
validateLoggerLevel()
|
||||||
validateLoggerFormat()
|
validateLoggerFormat()
|
||||||
initTimezone()
|
initTimezone()
|
||||||
go logLimiterCleaner()
|
go logLimiterCleaner()
|
||||||
|
|
||||||
|
if logFlags {
|
||||||
logAllFlags()
|
logAllFlags()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func initTimezone() {
|
func initTimezone() {
|
||||||
tz, err := time.LoadLocation(*loggerTimezone)
|
tz, err := time.LoadLocation(*loggerTimezone)
|
||||||
|
|
|
@ -841,7 +841,7 @@ func parseGenericFilter(lex *lexer, fieldName string) (filter, error) {
|
||||||
return f, nil
|
return f, nil
|
||||||
case lex.isKeyword("("):
|
case lex.isKeyword("("):
|
||||||
if !lex.isSkippedSpace && !lex.isPrevToken("", ":", "(", "!", "-", "not") {
|
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)
|
return parseParensFilter(lex, fieldName)
|
||||||
case lex.isKeyword(">"):
|
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
|
# github.com/dennwc/varint v1.0.0
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/dennwc/varint
|
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
|
# github.com/fatih/color v1.17.0
|
||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
github.com/fatih/color
|
github.com/fatih/color
|
||||||
|
@ -714,6 +722,7 @@ golang.org/x/sync/semaphore
|
||||||
# golang.org/x/sys v0.25.0
|
# golang.org/x/sys v0.25.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
golang.org/x/sys/cpu
|
golang.org/x/sys/cpu
|
||||||
|
golang.org/x/sys/plan9
|
||||||
golang.org/x/sys/unix
|
golang.org/x/sys/unix
|
||||||
golang.org/x/sys/windows
|
golang.org/x/sys/windows
|
||||||
golang.org/x/sys/windows/registry
|
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/transform
|
||||||
golang.org/x/text/unicode/bidi
|
golang.org/x/text/unicode/bidi
|
||||||
golang.org/x/text/unicode/norm
|
golang.org/x/text/unicode/norm
|
||||||
|
golang.org/x/text/width
|
||||||
# golang.org/x/time v0.6.0
|
# golang.org/x/time v0.6.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
golang.org/x/time/rate
|
golang.org/x/time/rate
|
||||||
|
|
Loading…
Reference in a new issue