app/vlogscli: add interactive command-line tool for querying VictoriaLogs

This commit is contained in:
Aliaksandr Valialkin 2024-10-01 12:08:17 +02:00
parent 61ae077e43
commit 630211cfed
No known key found for this signature in database
GPG key ID: 52C003EE2BCDB9EB
79 changed files with 17406 additions and 19 deletions

View file

@ -27,6 +27,7 @@ include package/release/Makefile
all: \
victoria-metrics-prod \
victoria-logs-prod \
vlogscli-prod \
vmagent-prod \
vmalert-prod \
vmalert-tool-prod \
@ -51,6 +52,7 @@ publish: \
package: \
package-victoria-metrics \
package-victoria-logs \
package-vlogscli \
package-vmagent \
package-vmalert \
package-vmalert-tool \
@ -320,6 +322,63 @@ release-victoria-logs-windows-goarch: victoria-logs-windows-$(GOARCH)-prod
cd bin && rm -rf \
victoria-logs-windows-$(GOARCH)-prod.exe
release-vlogscli:
$(MAKE_PARALLEL) release-vlogscli-linux-386 \
release-vlogscli-linux-amd64 \
release-vlogscli-linux-arm \
release-vlogscli-linux-arm64 \
release-vlogscli-darwin-amd64 \
release-vlogscli-darwin-arm64 \
release-vlogscli-freebsd-amd64 \
release-vlogscli-openbsd-amd64 \
release-vlogscli-windows-amd64
release-vlogscli-linux-386:
GOOS=linux GOARCH=386 $(MAKE) release-vlogscli-goos-goarch
release-vlogscli-linux-amd64:
GOOS=linux GOARCH=amd64 $(MAKE) release-vlogscli-goos-goarch
release-vlogscli-linux-arm:
GOOS=linux GOARCH=arm $(MAKE) release-vlogscli-goos-goarch
release-vlogscli-linux-arm64:
GOOS=linux GOARCH=arm64 $(MAKE) release-vlogscli-goos-goarch
release-vlogscli-darwin-amd64:
GOOS=darwin GOARCH=amd64 $(MAKE) release-vlogscli-goos-goarch
release-vlogscli-darwin-arm64:
GOOS=darwin GOARCH=arm64 $(MAKE) release-vlogscli-goos-goarch
release-vlogscli-freebsd-amd64:
GOOS=freebsd GOARCH=amd64 $(MAKE) release-vlogscli-goos-goarch
release-vlogscli-openbsd-amd64:
GOOS=openbsd GOARCH=amd64 $(MAKE) release-vlogscli-goos-goarch
release-vlogscli-windows-amd64:
GOARCH=amd64 $(MAKE) release-vlogscli-windows-goarch
release-vlogscli-goos-goarch: vlogscli-$(GOOS)-$(GOARCH)-prod
cd bin && \
tar $(TAR_OWNERSHIP) --transform="flags=r;s|-$(GOOS)-$(GOARCH)||" -czf vlogscli-$(GOOS)-$(GOARCH)-$(PKG_TAG).tar.gz \
vlogscli-$(GOOS)-$(GOARCH)-prod \
&& sha256sum vlogscli-$(GOOS)-$(GOARCH)-$(PKG_TAG).tar.gz \
vlogscli-$(GOOS)-$(GOARCH)-prod \
| sed s/-$(GOOS)-$(GOARCH)-prod/-prod/ > vlogscli-$(GOOS)-$(GOARCH)-$(PKG_TAG)_checksums.txt
cd bin && rm -rf vlogscli-$(GOOS)-$(GOARCH)-prod
release-vlogscli-windows-goarch: vlogscli-windows-$(GOARCH)-prod
cd bin && \
zip vlogscli-windows-$(GOARCH)-$(PKG_TAG).zip \
vlogscli-windows-$(GOARCH)-prod.exe \
&& sha256sum vlogscli-windows-$(GOARCH)-$(PKG_TAG).zip \
vlogscli-windows-$(GOARCH)-prod.exe \
> vlogscli-windows-$(GOARCH)-$(PKG_TAG)_checksums.txt
cd bin && rm -rf \
vlogscli-windows-$(GOARCH)-prod.exe
release-vmutils: \
release-vmutils-linux-386 \
release-vmutils-linux-amd64 \

109
app/vlogscli/Makefile Normal file
View 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
View 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/).

View 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

View 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)
}

View 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
View 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)
}

View 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

View file

@ -15,6 +15,8 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
## tip
* FEATURE: add interactive command-line tool for querying VictoriaLogs - [`vlogscli`](https://docs.victoriametrics.com/victorialogs/querying/vlogscli/).
## [v0.32.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.32.1-victorialogs)
Released at 2024-09-30

View file

@ -3,25 +3,27 @@ from [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics/).
VictoriaLogs provides the following features:
- VictoriaLogs can accept logs from popular log collectors. See [these docs](https://docs.victoriametrics.com/victorialogs/data-ingestion/).
- VictoriaLogs is much easier to set up and operate compared to Elasticsearch and Grafana Loki.
- It can accept logs from popular log collectors. See [these docs](https://docs.victoriametrics.com/victorialogs/data-ingestion/).
- It is much easier to set up and operate compared to Elasticsearch and Grafana Loki.
See [these docs](https://docs.victoriametrics.com/victorialogs/quickstart/).
- VictoriaLogs provides easy yet powerful query language with full-text search across
- It provides easy yet powerful query language with full-text search across
all the [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
See [LogsQL docs](https://docs.victoriametrics.com/victorialogs/logsql/).
- VictoriaLogs can be seamlessly combined with good old Unix tools for log analysis such as `grep`, `less`, `sort`, `jq`, etc.
- It provides interactive command-line tool for querying VictoriaLogs - [vlogscli](https://docs.victoriametrics.com/victorialogs/querying/vlogscli/).
- It can be seamlessly combined with good old Unix tools for log analysis such as `grep`, `less`, `sort`, `jq`, etc.
See [these docs](https://docs.victoriametrics.com/victorialogs/querying/#command-line) for details.
- VictoriaLogs capacity and performance scales linearly with the available resources (CPU, RAM, disk IO, disk space).
It runs smoothly on both Raspberry PI and a server with hundreds of CPU cores and terabytes of RAM.
- VictoriaLogs can handle up to 30x bigger data volumes than Elasticsearch and Grafana Loki when running on the same hardware.
- VictoriaLogs' capacity and performance scales linearly with the available resources (CPU, RAM, disk IO, disk space).
It runs smoothly on Raspberry PI and on servers with hundreds of CPU cores and terabytes of RAM.
- It can handle up to 30x bigger data volumes than Elasticsearch and Grafana Loki when running on the same hardware.
See [these docs](#benchmarks).
- VictoriaLogs supports fast full-text search over high-cardinality [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
such as `trace_id`, `user_id` and `ip`.
- VictoriaLogs supports multitenancy - see [these docs](#multitenancy).
- VictoriaLogs supports out-of-order logs' ingestion aka backfilling.
- VictoriaLogs supports live tailing for newly ingested logs. See [these docs](https://docs.victoriametrics.com/victorialogs/querying/#live-tailing).
- VictoriaLogs supports selecting surrounding logs in front and after the selected logs. See [these docs](https://docs.victoriametrics.com/victorialogs/logsql/#stream_context-pipe).
- VictoriaLogs provides web UI for querying logs - see [these docs](https://docs.victoriametrics.com/victorialogs/querying/#web-ui).
- It provides fast full-text search out of the box for [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
with high cardinality (e.g. high number of unique values) such as `trace_id`, `user_id` and `ip`.
- It supports multitenancy - see [these docs](#multitenancy).
- It supports out-of-order logs' ingestion aka backfilling.
- It supports live tailing for newly ingested logs. See [these docs](https://docs.victoriametrics.com/victorialogs/querying/#live-tailing).
- It supports selecting surrounding logs in front and after the selected logs. See [these docs](https://docs.victoriametrics.com/victorialogs/logsql/#stream_context-pipe).
- It provides web UI for querying logs - see [these docs](https://docs.victoriametrics.com/victorialogs/querying/#web-ui).
- It provides [Grafana plugin for querying logs](https://docs.victoriametrics.com/victorialogs/victorialogs-datasource/).
If you have questions about VictoriaLogs, then read [this FAQ](https://docs.victoriametrics.com/victorialogs/faq/).
Also feel free asking any questions at [VictoriaMetrics community Slack chat](https://victoriametrics.slack.com/),

View file

@ -2,7 +2,7 @@
via the following ways:
- [Web UI](#web-ui) - a web-based UI for querying logs
- [Visualization in Grafana](#visualization-in-grafana)
- [Grafana plugin](#visualization-in-grafana)
- [HTTP API](#http-api)
- [Command-line interface](#command-line)
@ -800,7 +800,10 @@ See also [command line interface](#command-line).
## Command-line
VictoriaLogs integrates well with `curl` and other command-line tools during querying because of the following features:
VictoriaLogs provides `vlogsqcli` interactive command-line tool for querying logs. See [these docs](https://docs.victoriametrics.com/victorialogs/querying/vlogscli/).
VictoriaLogs [querying API](https://docs.victoriametrics.com/victorialogs/querying/#querying-logs) integrates well with `curl`
and other Unix command-line tools because of the following features:
- Matching log entries are sent to the response stream as soon as they are found.
This allows forwarding the response stream to arbitrary [Unix pipes](https://en.wikipedia.org/wiki/Pipeline_(Unix))

View 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
View file

@ -18,11 +18,13 @@ require (
github.com/bmatcuk/doublestar/v4 v4.6.1
github.com/cespare/xxhash/v2 v2.3.0
github.com/cheggaaa/pb/v3 v3.1.5
github.com/ergochat/readline v0.1.3
github.com/gogo/protobuf v1.3.2
github.com/golang/snappy v0.0.4
github.com/googleapis/gax-go/v2 v2.13.0
github.com/influxdata/influxdb v1.11.6
github.com/klauspost/compress v1.17.10
github.com/mattn/go-isatty v0.0.20
github.com/prometheus/prometheus v0.54.1
github.com/urfave/cli/v2 v2.27.4
github.com/valyala/fastjson v1.6.4
@ -87,7 +89,6 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect

2
go.sum
View file

@ -182,6 +182,8 @@ github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnv
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
github.com/ergochat/readline v0.1.3 h1:/DytGTmwdUJcLAe3k3VJgowh5vNnsdifYT6uVaf4pSo=
github.com/ergochat/readline v0.1.3/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=

View file

@ -37,13 +37,27 @@ var (
//
// There is no need in calling Init from tests.
func Init() {
initInternal(true)
}
// InitNoLogFlags initializes the logger without writing flags to stdout
//
// InitNoLogFlags must be called after flag.Parse()
func InitNoLogFlags() {
initInternal(false)
}
func initInternal(logFlags bool) {
setLoggerJSONFields()
setLoggerOutput()
validateLoggerLevel()
validateLoggerFormat()
initTimezone()
go logLimiterCleaner()
logAllFlags()
if logFlags {
logAllFlags()
}
}
func initTimezone() {

View file

@ -841,7 +841,7 @@ func parseGenericFilter(lex *lexer, fieldName string) (filter, error) {
return f, nil
case lex.isKeyword("("):
if !lex.isSkippedSpace && !lex.isPrevToken("", ":", "(", "!", "-", "not") {
return nil, fmt.Errorf("missing whitespace before the search word %q", lex.prevToken)
return nil, fmt.Errorf("missing whitespace after the search word %q", lex.prevToken)
}
return parseParensFilter(lex, fieldName)
case lex.isKeyword(">"):

14
vendor/github.com/ergochat/readline/.check-gofmt.sh generated vendored Normal file
View 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
View file

@ -0,0 +1,2 @@
.vscode/*
*.swp

22
vendor/github.com/ergochat/readline/LICENSE generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
}

View file

@ -0,0 +1,7 @@
//go:build !windows
package ansi
func EnableANSI() error {
return nil
}

View 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
}

View 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()
}
}()
})
}

View 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?
}

View 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
}

View 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
}

View 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)
}

View 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)
}

View 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))
}

View 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

View 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

View 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)
}

View 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)
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

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

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

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

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

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

File diff suppressed because it is too large Load diff

239
vendor/golang.org/x/text/width/transform.go generated vendored Normal file
View 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
View 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
View 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
View file

@ -329,6 +329,14 @@ github.com/davecgh/go-spew/spew
# github.com/dennwc/varint v1.0.0
## explicit; go 1.12
github.com/dennwc/varint
# github.com/ergochat/readline v0.1.3
## explicit; go 1.19
github.com/ergochat/readline
github.com/ergochat/readline/internal/ansi
github.com/ergochat/readline/internal/platform
github.com/ergochat/readline/internal/ringbuf
github.com/ergochat/readline/internal/runes
github.com/ergochat/readline/internal/term
# github.com/fatih/color v1.17.0
## explicit; go 1.17
github.com/fatih/color
@ -714,6 +722,7 @@ golang.org/x/sync/semaphore
# golang.org/x/sys v0.25.0
## explicit; go 1.18
golang.org/x/sys/cpu
golang.org/x/sys/plan9
golang.org/x/sys/unix
golang.org/x/sys/windows
golang.org/x/sys/windows/registry
@ -723,6 +732,7 @@ golang.org/x/text/secure/bidirule
golang.org/x/text/transform
golang.org/x/text/unicode/bidi
golang.org/x/text/unicode/norm
golang.org/x/text/width
# golang.org/x/time v0.6.0
## explicit; go 1.18
golang.org/x/time/rate