pax_global_header00006660000000000000000000000064132300001370014477gustar00rootroot0000000000000052 comment=d7bac842de8bda33dc3b780158700068d07c8522 elvish-0.11+ds1/000077500000000000000000000000001323000013700133755ustar00rootroot00000000000000elvish-0.11+ds1/.appveyor.yml000066400000000000000000000005441323000013700160460ustar00rootroot00000000000000platform: x64 branches: only: - master clone_folder: c:\gopath\src\github.com\elves\elvish environment: GOPATH: c:\gopath install: - echo %PATH% - echo %GOPATH% - set PATH=%GOPATH%\bin;c:\go\bin;C:\msys64\usr\bin;%PATH% - go version - go env - choco install codecov - go get github.com/mattn/goveralls build_script: - make appveyor elvish-0.11+ds1/.gitattributes000066400000000000000000000000261323000013700162660ustar00rootroot00000000000000*.go filter=goimports elvish-0.11+ds1/.gitignore000066400000000000000000000005451323000013700153710ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe # Project specific /t/ # Small applications for manual testing go inside this directory /cover/ /elvish elvish-0.11+ds1/.travis.yml000066400000000000000000000002041323000013700155020ustar00rootroot00000000000000language: go go: 1.9 env: CGO_ENABLED=0 script: make travis sudo: false os: - linux - osx matrix: include: - go: 1.8 elvish-0.11+ds1/.vsts.sh000066400000000000000000000004111323000013700150020ustar00rootroot00000000000000mkdir ~/bin ~/go export PATH=$HOME/bin:$PATH curl -sL -o ~/bin/gimme https://raw.githubusercontent.com/travis-ci/gimme/master/gimme chmod +x ~/bin/gimme eval "$(gimme 1.9)" export GOPATH=$HOME/go go get github.com/elves/elvish go test github.com/elves/elvish/... elvish-0.11+ds1/CONTRIBUTING.md000066400000000000000000000030521323000013700156260ustar00rootroot00000000000000# Notes for Contributors ## Code 1. Always run unit tests before committing. `make` will take care of this. 2. Some files are generated from other files. They should be committed into the repository for this package to be go-gettable. Run `go generate ./...` to regenerate them in case you modified the source. Dependencies of the generation rules: * The `stringer` tool: Install with `go get -u golang.org/x/tools/cmd/stringer`; * An installed `elvish` in your PATH; * Python 2.7 at `/usr/bin/python2.7` (Python scripts should be rewritten in either Go or Elvish). 3. Always format the code with `goimports` before committing. Run `go get golang.org/x/tools/cmd/goimports` to install `goimports`, and `goimports -w .` to format all golang sources. To automate this you can set up a `goimports` filter for Git by putting this in `~/.gitconfig`: [filter "goimports"] clean = goimports smudge = cat Git will then always run `goimports` for you before committing, since `.gitattributes` in this repository refers to this filter. More about Git attributes and filters [here](https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html). ## Human Communication If you are making significant changes or any user-visible changes (e.g. changes to the language, the UI or the elv script API), please discuss on the developer channel before starting to work. ## Licensing By contributing, you agree to license your code under the same license as existing source code of elvish. See the LICENSE file. elvish-0.11+ds1/Dockerfile000066400000000000000000000000241323000013700153630ustar00rootroot00000000000000FROM golang:onbuild elvish-0.11+ds1/Gopkg.lock000066400000000000000000000020241323000013700153140ustar00rootroot00000000000000# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. [[projects]] name = "github.com/boltdb/bolt" packages = ["."] revision = "2f1ce7a837dcb8da3ec595b1dac9d0632f0f99e8" version = "v1.3.1" [[projects]] name = "github.com/kr/pty" packages = ["."] revision = "95d05c1eef33a45bd58676b6ce28d105839b8d0b" version = "v1.0.1" [[projects]] name = "github.com/mattn/go-isatty" packages = ["."] revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" version = "v0.0.3" [[projects]] branch = "master" name = "github.com/xiaq/persistent" packages = ["hash","hashmap","types","vector"] revision = "221501f424668419263ca5ee4e26637fdb05e229" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["unix","windows"] revision = "a3f2cbd54cf5dfe3fbaccf76375fdb12f67654c8" [solve-meta] analyzer-name = "dep" analyzer-version = 1 inputs-digest = "af3f22523d6ecfba4dccfb0f57e353923b401495cd7cf2e4a295062fc8d3028b" solver-name = "gps-cdcl" solver-version = 1 elvish-0.11+ds1/Gopkg.toml000066400000000000000000000005341323000013700153430ustar00rootroot00000000000000[[constraint]] name = "golang.org/x/sys" branch = "master" [[constraint]] name = "github.com/boltdb/bolt" version = "v1.3.1" [[constraint]] name = "github.com/xiaq/persistent" branch = "master" [[constraint]] name = "github.com/mattn/go-isatty" version = "0.0.3" [[constraint]] name = "github.com/kr/pty" version = "v1.0.1" elvish-0.11+ds1/LICENSE000066400000000000000000000024241323000013700144040ustar00rootroot00000000000000Copyright (c) elvish developers and contributors, All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. elvish-0.11+ds1/Makefile000066400000000000000000000046401323000013700150410ustar00rootroot00000000000000PKG_BASE := github.com/elves/elvish PKGS := $(shell go list ./... | sed 's|^$(PKG_BASE)|.|' | grep -v '^./vendor') PKG_COVERS := $(shell go list ./... | sed 's|^$(PKG_BASE)|.|' | grep -v '^\./vendor' | grep -v '^\.$$' | sed 's/^\./cover/' | sed 's/$$/.cover/') COVER_MODE := set VERSION := $(shell git describe --tags --always) GOVERALLS := github.com/mattn/goveralls default: test get get: go get -ldflags "-X github.com/elves/elvish/build.Version=$(VERSION) -X github.com/elves/elvish/build.Builder=$(shell id -un)@$(shell hostname)" . generate: go generate ./... test: go test $(PKGS) testmain: go test . cover/%.cover: % mkdir -p $(dir $@) go test -coverprofile=$@ -covermode=$(COVER_MODE) ./$< cover/all: $(PKG_COVERS) echo mode: $(COVER_MODE) > $@ for f in $(PKG_COVERS); do test -f $$f && sed 1d $$f >> $@ || true; done # Disable coverage reports for pull requests. The general testability of the # code is pretty bad and it is premature to require contributors to maintain # code coverage. upload-codecov-travis: cover/all test "$(TRAVIS_PULL_REQUEST)" = false \ && echo "$(TRAVIS_GO_VERSION)" | grep -q '^1.9' \ && curl -s https://codecov.io/bash -o codecov.bash \ && bash codecov.bash -f $< \ || echo "not sending to codecov.io" upload-coveralls-travis: cover/all test "$(TRAVIS_PULL_REQUEST)" = false \ && echo "$(TRAVIS_GO_VERSION)" | grep -q '^1.9' \ && go get -d $(GOVERALLS) \ && go build -o goveralls $(GOVERALLS) \ && ./goveralls -coverprofile $< -service=travis-ci \ || echo "not sending to coveralls.io" upload-codecov-appveyor: cover/all test -z "$(APPVEYOR_PULL_REQUEST_NUMBER)" \ && codecov -f $< \ || echo "not sending to codecov.io" upload-coveralls-appveyor: cover/all test -z "$(APPVEYOR_PULL_REQUEST_NUMBER)" \ && goveralls -coverprofile $< -service=appveyor-ci \ || echo "not sending to coveralls.io" upload-bin: test "$(TRAVIS_OS_NAME)" = linux \ && echo "$(TRAVIS_GO_VERSION)" | grep -q '^1.9' \ && test "$(TRAVIS_PULL_REQUEST)" = false \ && test -n "$(TRAVIS_TAG)" -o "$(TRAVIS_BRANCH)" = master \ && go build -o ./elvish \ && ./elvish build-and-upload.elv \ || echo "not build-and-uploading" travis: testmain upload-codecov-travis upload-coveralls-travis upload-bin appveyor: testmain upload-codecov-appveyor .PHONY: default get generate test testmain upload-codecov-travis upload-coveralls-travis upload-codecov-appveyor upload-coveralls-appveyor upload-bin travis elvish-0.11+ds1/README.md000066400000000000000000000107201323000013700146540ustar00rootroot00000000000000# Elvish: Friendly and Expressive Shell [![logo](https://elvish.io/assets/logo.svg)](https://elvish.io/) Elvish is a cross-platform shell, supporting Linux, BSDs and Windows. It features an expressive programming language, with features like namespacing and anonymous functions, and a fully programmable user interface with friendly defaults. It is suitable for both interactive use and scripting. ... which is not 100% true yet. Elvish is already suitable for most daily interactive use, but it is neither complete nor stablized. Contributions are more than welcome! This README documents the development aspect of Elvish. Other information is to be found on the [website](https://elvish.io). [![Build Status on Travis](https://img.shields.io/travis/elves/elvish.svg?label=linux%20%26%20macOS)](https://travis-ci.org/elves/elvish) [![Build status on AppVeyor](https://img.shields.io/appveyor/ci/xiaq/elvish.svg?logo=appveyor&label=windows)](https://ci.appveyor.com/project/xiaq/elvish) [![Build Status on VSTS](https://img.shields.io/vso/build/xiaq/13c48a6c-b2dc-472e-af6c-169bf448f8e6/1.svg?label=macOS)](https://xiaq.visualstudio.com/elvish/_build) [![Code Coverage on codecov.io](https://img.shields.io/codecov/c/github/elves/elvish.svg?label=codecov)](https://codecov.io/gh/elves/elvish) [![Code Coverage on coveralls.io](https://img.shields.io/coveralls/github/elves/elvish.svg?label=coveralls)](https://coveralls.io/github/elves/elvish) [![Go Report Card](https://goreportcard.com/badge/github.com/elves/elvish)](https://goreportcard.com/report/github.com/elves/elvish) [![GoDoc](https://img.shields.io/badge/godoc-api-blue.svg)](http://godoc.org/github.com/elves/elvish) [![License](https://img.shields.io/badge/license-BSD%202--clause-blue.svg)](https://github.com/elves/elvish/blob/master/LICENSE) [![Gitter](https://img.shields.io/badge/gitter-elvish--public-blue.svg?logo=gitter-white)](https://gitter.im/elves/elvish-public) [![Telegram Group](https://img.shields.io/badge/telegram-@elvish-blue.svg)](https://telegram.me/elvish) [![#elvish on freenode](https://img.shields.io/badge/freenode-%23elvish-blue.svg)](https://webchat.freenode.net/?channels=elvish) [![Gitter for Developers](https://img.shields.io/badge/gitter-elvish--dev-000000.svg?logo=gitter-white)](https://gitter.im/elves/elvish-dev) [![Telegram Group for Developers](https://img.shields.io/badge/telegram-@elvish__dev-000000.svg)](https://telegram.me/elvish_dev) [![#elvish-dev on freenode](https://img.shields.io/badge/freenode-%23elvish--dev-000000.svg)](https://webchat.freenode.net/?channels=elvish-dev) [![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/RealElvishShell) ## Building Elvish To build Elvish, you need * Go >= 1.8. * Linux, {Free,Net,Open}BSD, macOS, or Windows (Windows support is experimental). If you would like to contribute code to Elvish, please read [CONTRIBUTING.md](CONTRIBUTING.md). ### The Correct Way Elvish is a go-gettable package. To build Elvish, first set up your Go workspace according to [How To Write Go Code](http://golang.org/doc/code.html), and then run ```sh go get github.com/elves/elvish ``` ### The Lazy Way Here is something you can copy-paste into your terminal: ```sh export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin mkdir -p $GOPATH go get github.com/elves/elvish for f in ~/.bashrc ~/.zshrc; do printf 'export %s=%s\n' GOPATH '$HOME/go' PATH '$PATH:$GOPATH/bin' >> $f done ``` The scripts sets up the Go workspace and runs `go get` for you. It assumes that you have a working Go installation and currently use `bash` or `zsh`. ### The Homebrew Way Users of macOS can build Elvish using [Homebrew](http://brew.sh): ```sh brew install --HEAD elvish ``` ## Name In [roguelikes](https://en.wikipedia.org/wiki/Roguelike), items made by the elves have a reputation of high quality. These are usually called *elven* items, but I chose "elvish" because it ends with "sh", a long tradition of Unix shells. It also rhymes with [fish](https://fishshell.com), one of the shells that influenced the philosophy of Elvish. The word "Elvish" should be capitalized like a proper noun. However, when referring to the `elvish` command, use it in lower case with fixed-width font. Whoever practices the Elvish way by either contributing to it or simply using it is called an **Elf**. (You might have guessed this from the name of the GitHub organization.) The official adjective for Elvish (as in "Pythonic" for Python, "Rubyesque" for Ruby) is **Elven**. elvish-0.11+ds1/build-and-upload.elv000066400000000000000000000014721323000013700172320ustar00rootroot00000000000000if (eq $E:UPLOAD_TOKEN '') { echo 'UPLOAD_TOKEN must be set' exit 2 } version = (git describe --tags --always) fname-suffix = '' if (not-eq $E:TRAVIS_TAG '') { fname-suffix = -$E:TRAVIS_TAG } fn build [os arch]{ bin = elvish-$os'-'$arch$fname-suffix archive = $bin.tar.gz echo 'Going to build '$bin E:GOOS=$os E:GOARCH=$arch go build -ldflags "-X main.Version="$version -o $bin if (eq $os windows) { archive = $bin.zip cp $bin $bin.exe zip $archive $bin.exe } else { tar cfz $archive $bin } curl https://ul.elvish.io/ -F name=$archive -F token=$E:UPLOAD_TOKEN -F file=@$archive echo 'Built '$bin' and uploaded '$archive } build darwin amd64 for arch [386 amd64] { build windows $arch } for arch [386 amd64 arm64] { build linux $arch } elvish-0.11+ds1/build/000077500000000000000000000000001323000013700144745ustar00rootroot00000000000000elvish-0.11+ds1/build/build.go000066400000000000000000000004111323000013700161160ustar00rootroot00000000000000// Package build contains build information. // // Build information should be set during compilation by passing // -ldflags "-X github.com/elves/elvish/build.Var=value" to "go build" or // "go get". package build var ( Version = "unknown" Builder = "unknown" ) elvish-0.11+ds1/daemon/000077500000000000000000000000001323000013700146405ustar00rootroot00000000000000elvish-0.11+ds1/daemon/api.go000066400000000000000000000030141323000013700157360ustar00rootroot00000000000000package daemon import ( "github.com/elves/elvish/store/storedefs" ) const ( // ServiceName is the name of the RPC service exposed by the daemon. ServiceName = "Daemon" // Version is the API version. It should be bumped any time the API changes. Version = -97 ) // Basic requests. type VersionRequest struct{} type VersionResponse struct { Version int } type PidRequest struct{} type PidResponse struct { Pid int } // Cmd requests. type NextCmdSeqRequest struct{} type NextCmdSeqResponse struct { Seq int } type AddCmdRequest struct { Text string } type AddCmdResponse struct { Seq int } type CmdRequest struct { Seq int } type CmdResponse struct { Text string } type CmdsRequest struct { From int Upto int } type CmdsResponse struct { Cmds []string } type NextCmdRequest struct { From int Prefix string } type NextCmdResponse struct { Seq int Text string } type PrevCmdRequest struct { Upto int Prefix string } type PrevCmdResponse struct { Seq int Text string } // Dir requests. type AddDirRequest struct { Dir string IncFactor float64 } type AddDirResponse struct{} type DirsRequest struct { Blacklist map[string]struct{} } type DirsResponse struct { Dirs []storedefs.Dir } // SharedVar requests. type SharedVarRequest struct { Name string } type SharedVarResponse struct { Value string } type SetSharedVarRequest struct { Name string Value string } type SetSharedVarResponse struct{} type DelSharedVarRequest struct { Name string } type DelSharedVarResponse struct{} elvish-0.11+ds1/daemon/client.go000066400000000000000000000110331323000013700164430ustar00rootroot00000000000000package daemon import ( "errors" "net/rpc" "sync" "github.com/elves/elvish/store/storedefs" ) const retriesOnShutdown = 3 var ( // ErrClientNotInitialized is returned when the Client is not initialized. ErrClientNotInitialized = errors.New("client not initialized") // ErrDaemonUnreachable is returned when the daemon cannot be reached after // several retries. ErrDaemonUnreachable = errors.New("daemon offline") ) // Client is a client to the Elvish daemon. A nil *Client is safe to use. type Client struct { sockPath string rpcClient *rpc.Client waits sync.WaitGroup } var _ storedefs.Store = (*Client)(nil) // NewClient creates a new Client instance that talks to the socket. Connection // creation is deferred to the first request. func NewClient(sockPath string) *Client { return &Client{sockPath, nil, sync.WaitGroup{}} } // SockPath returns the socket path that the Client talks to. If the client is // nil, it returns an empty string. func (c *Client) SockPath() string { if c == nil { return "" } return c.sockPath } // ResetConn resets the current connection. A new connection will be established // the next time a request is made. If the client is nil, it does nothing. func (c *Client) ResetConn() error { if c == nil || c.rpcClient == nil { return nil } rc := c.rpcClient c.rpcClient = nil return rc.Close() } // Close waits for all outstanding requests to finish and close the connection. // If the client is nil, it does nothing and returns nil. func (c *Client) Close() error { if c == nil { return nil } c.waits.Wait() return c.ResetConn() } func (c *Client) call(f string, req, res interface{}) error { if c == nil { return ErrClientNotInitialized } c.waits.Add(1) defer c.waits.Done() for attempt := 0; attempt < retriesOnShutdown; attempt++ { if c.rpcClient == nil { conn, err := dial(c.sockPath) if err != nil { return err } c.rpcClient = rpc.NewClient(conn) } err := c.rpcClient.Call(ServiceName+"."+f, req, res) if err == rpc.ErrShutdown { // Clear rpcClient so as to reconnect next time c.rpcClient = nil continue } else { return err } } return ErrDaemonUnreachable } // Convenience methods for RPC methods. These are quite repetitive; when the // number of RPC calls grow above some threshold, a code generator should be // written to generate them. func (c *Client) Version() (int, error) { req := &VersionRequest{} res := &VersionResponse{} err := c.call("Version", req, res) return res.Version, err } func (c *Client) Pid() (int, error) { req := &PidRequest{} res := &PidResponse{} err := c.call("Pid", req, res) return res.Pid, err } func (c *Client) NextCmdSeq() (int, error) { req := &NextCmdRequest{} res := &NextCmdSeqResponse{} err := c.call("NextCmdSeq", req, res) return res.Seq, err } func (c *Client) AddCmd(text string) (int, error) { req := &AddCmdRequest{text} res := &AddCmdResponse{} err := c.call("AddCmd", req, res) return res.Seq, err } func (c *Client) Cmd(seq int) (string, error) { req := &CmdRequest{seq} res := &CmdResponse{} err := c.call("Cmd", req, res) return res.Text, err } func (c *Client) Cmds(from, upto int) ([]string, error) { req := &CmdsRequest{from, upto} res := &CmdsResponse{} err := c.call("Cmds", req, res) return res.Cmds, err } func (c *Client) NextCmd(from int, prefix string) (int, string, error) { req := &NextCmdRequest{from, prefix} res := &NextCmdResponse{} err := c.call("NextCmd", req, res) return res.Seq, res.Text, err } func (c *Client) PrevCmd(upto int, prefix string) (int, string, error) { req := &PrevCmdRequest{upto, prefix} res := &PrevCmdResponse{} err := c.call("PrevCmd", req, res) return res.Seq, res.Text, err } func (c *Client) AddDir(dir string, incFactor float64) error { req := &AddDirRequest{dir, incFactor} res := &AddDirResponse{} err := c.call("AddDir", req, res) return err } func (c *Client) Dirs(blacklist map[string]struct{}) ([]storedefs.Dir, error) { req := &DirsRequest{blacklist} res := &DirsResponse{} err := c.call("Dirs", req, res) return res.Dirs, err } func (c *Client) SharedVar(name string) (string, error) { req := &SharedVarRequest{name} res := &SharedVarResponse{} err := c.call("SharedVar", req, res) return res.Value, err } func (c *Client) SetSharedVar(name, value string) error { req := &SetSharedVarRequest{name, value} res := &SetSharedVarResponse{} return c.call("SetSharedVar", req, res) } func (c *Client) DelSharedVar(name string) error { req := &DelSharedVarRequest{} res := &DelSharedVarResponse{} return c.call("DelSharedVar", req, res) } elvish-0.11+ds1/daemon/daemon.go000066400000000000000000000005011323000013700164260ustar00rootroot00000000000000// Package daemon implements a service for mediating access to the data store, // and its client. // // Most RPCs exposed by the service correspond to the methods of Store in the // store package and are not documented here. package daemon import "github.com/elves/elvish/util" var logger = util.GetLogger("[daemon] ") elvish-0.11+ds1/daemon/daemon_test.go000066400000000000000000000012561323000013700174750ustar00rootroot00000000000000package daemon import ( "testing" "time" "github.com/elves/elvish/util" ) func TestDaemon(t *testing.T) { util.InTempDir(func(string) { serverDone := make(chan struct{}) go func() { Serve("sock", "db") close(serverDone) }() client := NewClient("sock") for i := 0; i < 10; i++ { client.ResetConn() _, err := client.Version() if err == nil { break } else if i == 9 { t.Fatal("Failed to connect after 100ms") } time.Sleep(10 * time.Millisecond) } _, err := client.AddCmd("test cmd") if err != nil { t.Errorf("client.AddCmd -> error %v", err) } client.Close() // Wait for server to quit before returning <-serverDone }) } elvish-0.11+ds1/daemon/service.go000066400000000000000000000110531323000013700166270ustar00rootroot00000000000000package daemon import ( "net/rpc" "os" "os/signal" "sync" "syscall" "github.com/elves/elvish/store" "github.com/elves/elvish/store/storedefs" ) // Serve runs the daemon service, listening on the socket specified by sockpath // and serving data from dbpath. It quits upon receiving SIGTERM, SIGINT or when // all active clients have disconnected. func Serve(sockpath, dbpath string) { logger.Println("pid is", syscall.Getpid()) logger.Println("going to listen", sockpath) listener, err := listen(sockpath) if err != nil { logger.Printf("failed to listen on %s: %v", sockpath, err) logger.Println("aborting") os.Exit(2) } st, err := store.NewStore(dbpath) if err != nil { logger.Printf("failed to create storage: %v", err) logger.Printf("serving anyway") } quitSignals := make(chan os.Signal) quitChan := make(chan struct{}) signal.Notify(quitSignals, syscall.SIGTERM, syscall.SIGINT) go func() { select { case sig := <-quitSignals: logger.Printf("received signal %s", sig) case <-quitChan: logger.Printf("No active client, daemon exit") } err := os.Remove(sockpath) if err != nil { logger.Printf("failed to remove socket %s: %v", sockpath, err) } err = st.Close() if err != nil { logger.Printf("failed to close storage: %v", err) } err = listener.Close() if err != nil { logger.Printf("failed to close listener: %v", err) } logger.Println("listener closed, waiting to exit") }() service := &Service{st, err} rpc.RegisterName(ServiceName, service) logger.Println("starting to serve RPC calls") firstClient := true activeClient := sync.WaitGroup{} // prevent daemon exit before serving first client activeClient.Add(1) go func() { activeClient.Wait() close(quitChan) }() for { conn, err := listener.Accept() if err != nil { logger.Printf("Failed to accept: %#v", err) break } if firstClient { firstClient = false } else { activeClient.Add(1) } go func() { rpc.DefaultServer.ServeConn(conn) activeClient.Done() }() } logger.Println("exiting") } // Service provides the daemon RPC service. It is suitable as a service for // net/rpc. type Service struct { store storedefs.Store err error } // Implementations of RPC methods. // Version returns the API version number. func (s *Service) Version(req *VersionRequest, res *VersionResponse) error { if s.err != nil { return s.err } res.Version = Version return nil } // Pid returns the process ID of the daemon. func (s *Service) Pid(req *PidRequest, res *PidResponse) error { res.Pid = syscall.Getpid() return nil } func (s *Service) NextCmdSeq(req *NextCmdSeqRequest, res *NextCmdSeqResponse) error { if s.err != nil { return s.err } seq, err := s.store.NextCmdSeq() res.Seq = seq return err } func (s *Service) AddCmd(req *AddCmdRequest, res *AddCmdResponse) error { if s.err != nil { return s.err } seq, err := s.store.AddCmd(req.Text) res.Seq = seq return err } func (s *Service) Cmd(req *CmdRequest, res *CmdResponse) error { if s.err != nil { return s.err } text, err := s.store.Cmd(req.Seq) res.Text = text return err } func (s *Service) Cmds(req *CmdsRequest, res *CmdsResponse) error { if s.err != nil { return s.err } cmds, err := s.store.Cmds(req.From, req.Upto) res.Cmds = cmds return err } func (s *Service) NextCmd(req *NextCmdRequest, res *NextCmdResponse) error { if s.err != nil { return s.err } seq, text, err := s.store.NextCmd(req.From, req.Prefix) res.Seq, res.Text = seq, text return err } func (s *Service) PrevCmd(req *PrevCmdRequest, res *PrevCmdResponse) error { if s.err != nil { return s.err } seq, text, err := s.store.PrevCmd(req.Upto, req.Prefix) res.Seq, res.Text = seq, text return err } func (s *Service) AddDir(req *AddDirRequest, res *AddDirResponse) error { if s.err != nil { return s.err } return s.store.AddDir(req.Dir, req.IncFactor) } func (s *Service) Dirs(req *DirsRequest, res *DirsResponse) error { if s.err != nil { return s.err } dirs, err := s.store.Dirs(req.Blacklist) res.Dirs = dirs return err } func (s *Service) SharedVar(req *SharedVarRequest, res *SharedVarResponse) error { if s.err != nil { return s.err } value, err := s.store.SharedVar(req.Name) res.Value = value return err } func (s *Service) SetSharedVar(req *SetSharedVarRequest, res *SetSharedVarResponse) error { if s.err != nil { return s.err } return s.store.SetSharedVar(req.Name, req.Value) } func (s *Service) DelSharedVar(req *DelSharedVarRequest, res *DelSharedVarResponse) error { if s.err != nil { return s.err } return s.store.DelSharedVar(req.Name) } elvish-0.11+ds1/daemon/sock_unix.go000066400000000000000000000003321323000013700171670ustar00rootroot00000000000000// +build !windows,!plan9 package daemon import "net" func listen(path string) (net.Listener, error) { return net.Listen("unix", path) } func dial(path string) (net.Conn, error) { return net.Dial("unix", path) } elvish-0.11+ds1/daemon/sock_windows.go000066400000000000000000000017741323000013700177110ustar00rootroot00000000000000package daemon import ( "errors" "fmt" "io/ioutil" "net" "os" ) var errSockExists = errors.New("socket file already exists") func listen(path string) (net.Listener, error) { file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0006) if err != nil { return nil, err } listener, err := net.Listen("tcp", "127.0.0.1:") if err != nil { file.Close() err2 := os.Remove(path) if err2 != nil { logger.Println("Failed to remove sock file after failure to listen", err2) } return nil, err } _, err = fmt.Fprint(file, listener.Addr()) if err != nil { logger.Println("Failed to write to sock file after listening", err) listener.Close() file.Close() err2 := os.Remove(path) if err2 != nil { logger.Println("Failed to remove sock file after failure to listen", err2) } return nil, err } file.Close() return listener, nil } func dial(path string) (net.Conn, error) { buf, err := ioutil.ReadFile(path) if err != nil { return nil, err } return net.Dial("tcp", string(buf)) } elvish-0.11+ds1/edit/000077500000000000000000000000001323000013700143225ustar00rootroot00000000000000elvish-0.11+ds1/edit/abbr.go000066400000000000000000000013031323000013700155540ustar00rootroot00000000000000package edit import ( "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/xiaq/persistent/hashmap" ) var _ = RegisterVariable("abbr", func() vartypes.Variable { return vartypes.NewValidatedPtr( types.NewMap(hashmap.Empty), vartypes.ShouldBeMap) }) func (ed *Editor) abbr() types.Map { return ed.variables["abbr"].Get().(types.Map) } func (ed *Editor) abbrIterate(cb func(abbr, full string) bool) { m := ed.abbr() m.IteratePair(func(abbrValue, fullValue types.Value) bool { abbr, ok := abbrValue.(types.String) if !ok { return true } full, ok := fullValue.(types.String) if !ok { return true } return cb(string(abbr), string(full)) }) } elvish-0.11+ds1/edit/action.go000066400000000000000000000011551323000013700161300ustar00rootroot00000000000000package edit // action is used in the nextAction field of editorState to schedule a special // action after a keybinding has been executed. type action int const ( noAction action = iota // reprocessKey makes the editor to reprocess the keybinding. reprocessKey // commitLine makes the editor return with the current line. commitLine // commitEOF makes the editor return with an EOF. commitEOF ) func (ed *Editor) setAction(action action) { if ed.nextAction == noAction { ed.nextAction = action } } func (ed *Editor) popAction() action { action := ed.nextAction ed.nextAction = noAction return action } elvish-0.11+ds1/edit/api.go000066400000000000000000000126751323000013700154350ustar00rootroot00000000000000package edit import ( "errors" "strconv" "unicode/utf8" "unsafe" "github.com/elves/elvish/edit/history" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/xiaq/persistent/hash" ) // This file implements types and functions for interactions with the // Elvishscript runtime. var ( errNotNav = errors.New("not in navigation mode") errLineMustBeString = errors.New("line must be string") errDotMustBeString = errors.New("dot must be string") errDotMustBeInt = errors.New("dot must be integer") errDotOutOfRange = errors.New("dot out of range") errDotInsideCodepoint = errors.New("dot cannot be inside a codepoint") errEditorInvalid = errors.New("internal error: editor not set up") errEditorInactive = errors.New("editor inactive") ) // BuiltinFn represents an editor builtin. type BuiltinFn struct { name string impl func(ed *Editor) } var _ eval.Fn = &BuiltinFn{} // Kind returns "fn". func (*BuiltinFn) Kind() string { return "fn" } // Equal compares based on identity. func (bf *BuiltinFn) Equal(a interface{}) bool { return bf == a } func (bf *BuiltinFn) Hash() uint32 { return hash.Pointer(unsafe.Pointer(bf)) } // Repr returns the representation of a builtin function as a variable name. func (bf *BuiltinFn) Repr(int) string { return "$" + bf.name } // Call calls a builtin function. func (bf *BuiltinFn) Call(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { eval.TakeNoOpt(opts) eval.TakeNoArg(args) ed, ok := ec.Editor.(*Editor) if !ok { throw(errEditorInvalid) } if !ed.active { throw(errEditorInactive) } bf.impl(ed) } // installModules installs edit: and edit:* modules. func installModules(builtin eval.Ns, ed *Editor) { // Construct the edit: module, starting with builtins. ns := makeNsFromBuiltins(builtinMaps[""]) // TODO(xiaq): Everything here should be registered to some registry instead // of centralized here. // Editor configurations. for name, variable := range ed.variables { ns[name] = variable } // Internal states. ns["history"] = vartypes.NewRo(history.List{&ed.historyMutex, ed.daemon}) ns["current-command"] = vartypes.NewCallback( func(v types.Value) error { if !ed.active { return errEditorInactive } if s, ok := v.(types.String); ok { ed.buffer = string(s) ed.dot = len(ed.buffer) } else { return errLineMustBeString } return nil }, func() types.Value { return types.String(ed.buffer) }, ) ns["-dot"] = vartypes.NewCallback( func(v types.Value) error { s, ok := v.(types.String) if !ok { return errDotMustBeString } i, err := strconv.Atoi(string(s)) if err != nil { if err.(*strconv.NumError).Err == strconv.ErrRange { return errDotOutOfRange } else { return errDotMustBeInt } } if i < 0 || i > len(ed.buffer) { return errDotOutOfRange } if i < len(ed.buffer) { r, _ := utf8.DecodeRuneInString(ed.buffer[i:]) if r == utf8.RuneError { return errDotInsideCodepoint } } ed.dot = i return nil }, func() types.Value { return types.String(strconv.Itoa(ed.dot)) }, ) ns["selected-file"] = vartypes.NewRoCallback( func() types.Value { if !ed.active { throw(errEditorInactive) } nav, ok := ed.mode.(*navigation) if !ok { throw(errNotNav) } return types.String(nav.current.selectedName()) }, ) // Completers. for _, bac := range argCompletersData { ns[bac.name+eval.FnSuffix] = vartypes.NewRo(bac) } // Matchers. eval.AddBuiltinFns(ns, matchers...) // Functions. eval.AddBuiltinFns(ns, &eval.BuiltinFn{"edit:binding-table", makeBindingTable}, &eval.BuiltinFn{"edit:command-history", CommandHistory}, &eval.BuiltinFn{"edit:complete-getopt", complGetopt}, &eval.BuiltinFn{"edit:complex-candidate", outputComplexCandidate}, &eval.BuiltinFn{"edit:insert-at-dot", InsertAtDot}, &eval.BuiltinFn{"edit:replace-input", ReplaceInput}, &eval.BuiltinFn{"edit:styled", styled}, &eval.BuiltinFn{"edit:key", ui.KeyBuiltin}, &eval.BuiltinFn{"edit:wordify", Wordify}, &eval.BuiltinFn{"edit:-dump-buf", _dumpBuf}, &eval.BuiltinFn{"edit:-narrow-read", NarrowRead}, ) builtin["edit"+eval.NsSuffix] = vartypes.NewValidatedPtr(ns, eval.ShouldBeNs) submods := make(map[string]eval.Ns) // Install other modules. for module, builtins := range builtinMaps { if module != "" { submods[module] = makeNsFromBuiltins(builtins) } } // Add $edit:{mode}:binding variables. for mode, bindingVar := range ed.bindings { submod, ok := submods[mode] if !ok { submod = make(eval.Ns) submods[mode] = submod } submod["binding"] = bindingVar } for name, ns := range submods { builtin["edit:"+name+eval.NsSuffix] = vartypes.NewValidatedPtr(ns, eval.ShouldBeNs) } } // CallFn calls an Fn, displaying its outputs and possible errors as editor // notifications. It is the preferred way to call a Fn while the editor is // active. func (ed *Editor) CallFn(fn eval.Fn, args ...types.Value) { if b, ok := fn.(*BuiltinFn); ok { // Builtin function: quick path. b.impl(ed) return } ports := []*eval.Port{ eval.DevNullClosedChan, ed.notifyPort, ed.notifyPort, } // XXX There is no source to pass to NewTopEvalCtx. ec := eval.NewTopFrame(ed.evaler, eval.NewInternalSource("[editor]"), ports) ex := ec.PCall(fn, args, eval.NoOpts) if ex != nil { ed.Notify("function error: %s", ex.Error()) } ed.refresh(true, true) } elvish-0.11+ds1/edit/api_test.go000066400000000000000000000027271323000013700164710ustar00rootroot00000000000000package edit import ( "testing" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/util" ) func TestBuiltinFn(t *testing.T) { called := false builtinFn := &BuiltinFn{"foobar", func(*Editor) { if called { t.Errorf("builtin impl called multiple times, called not reset") } called = true }} if kind := builtinFn.Kind(); kind != "fn" { t.Errorf("Kind of BuiltinFn should be fn, is %q", kind) } if repr := builtinFn.Repr(10); repr != "$foobar" { t.Errorf("Repr of BuiltinFn should be $foobar, is %q", repr) } ec := &eval.Frame{Evaler: &eval.Evaler{}} if !util.Throws(func() { builtinFn.Call(ec, nil, nil) }, errEditorInvalid) { t.Errorf("BuiltinFn should error when Editor is nil, didn't") } ec.Editor = &Editor{active: false} if !util.Throws(func() { builtinFn.Call(ec, nil, nil) }, errEditorInactive) { t.Errorf("BuiltinFn should error when Editor is inactive, didn't") } ec.Editor = &Editor{active: true} if !util.Throws(func() { builtinFn.Call(ec, []types.Value{types.String("2")}, nil) }, eval.ErrNoArgAccepted) { t.Errorf("BuiltinFn should error when argument was supplied, didn't") } if !util.Throws(func() { builtinFn.Call(ec, nil, map[string]types.Value{"a": types.String("b")}) }, eval.ErrNoOptAccepted) { t.Errorf("BuiltinFn should error when option was supplied, didn't") } builtinFn.Call(ec, nil, nil) if !called { t.Errorf("BuiltinFn should call its implementation, didn't") } } elvish-0.11+ds1/edit/api_utils.go000066400000000000000000000004671323000013700166510ustar00rootroot00000000000000package edit // Trivial utilities for the elvishscript API. import ( "fmt" "github.com/elves/elvish/util" ) func throw(e error) { util.Throw(e) } func maybeThrow(e error) { if e != nil { util.Throw(e) } } func throwf(format string, args ...interface{}) { util.Throw(fmt.Errorf(format, args...)) } elvish-0.11+ds1/edit/arg_completers.go000066400000000000000000000174661323000013700176750ustar00rootroot00000000000000package edit import ( "bufio" "errors" "io" "os" "strings" "unsafe" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/xiaq/persistent/hash" "github.com/xiaq/persistent/hashmap" ) // For an overview of completion, see the comment in completers.go. // // When completing arguments of commands (as opposed to variable names, map // indicies, etc.), the list of candidates often depends on the command in // question; e.g. "ls " and "apt " should yield different results // because the commands "ls" and "apt" accept different arguments. To reflec // this, Elvish has a map of "argument completers", with the key being the name // of the command, and the value being the argument completer itself, accessible // to script as $edit:arg-completer. The one entry with an empty string as the // key is the fallback completer, and is used when an argument completer for the // current command has not been defined. // // When completing an argument, Elvish first finds out the name of the command // (e.g. "ls" or "apt") can tries to evaluate its arguments. It then calls the // suitable completer with the name of the command and the arguments. The // arguments are in evaluated forms: e.g. if an argument is 'foo' (with quotes), // the argument is its value foo, not a literal 'foo'. The last argument is what // needs to be completed; if the user is starting a new argument, e.g. by typing // "ls a " (without quotes), the last argument passed to the argument completer // will be an empty string. // // The argument completer should then return a list of what can replace the last // argument. The results are of type rawCandidate, which basically means that // argument completers do not need to worry about quoting of candidates; the raw // candidates will be "cooked" into actual candidates that appear in the // interface, which includes quoting. // // There are a few finer points in this process: // // 1. If some of the arguments cannot be evaluated statically (for instance, // consider this: echo (uname)), it will be an empty string. There needs // probably be a better way to distinguish empty arguments and unknown // arguments, but normally there is not much argument completers can do for // unknown arguments. // // 2. The argument completer normally **should not** perform filtering. For // instance, if the user has typed "ls x", the argument completer for "ls" // should return **all** files, not just those whose names start with x. This // is to make it possible for user to specify a different matching algorithm // than the default prefix matching. // // However, argument completers **should** look at the argument to decide // which **type** of candidates to generate. For instance, if the user has // typed "ls --x", the argument completer should generate all long options // for "ls", but not only those starting with "x". var ( // ErrCompleterMustBeFn is thrown if the user has put a non-function entry // in $edit:completer, and that entry needs to be used for completion. // TODO(xiaq): Detect the type violation when the user modifies // $edit:completer. ErrCompleterMustBeFn = errors.New("completer must be fn") // ErrCompleterArgMustBeString is thrown when a builtin argument completer // is called with non-string arguments. ErrCompleterArgMustBeString = errors.New("arguments to arg completers must be string") // ErrTooFewArguments is thrown when a builtin argument completer is called // with too few arguments. ErrTooFewArguments = errors.New("too few arguments") ) var ( argCompletersData = map[string]*builtinArgCompleter{ "": {"complete-filename", complFilename}, "sudo": {"complete-sudo", complSudo}, } ) var _ = RegisterVariable("arg-completer", argCompleterVariable) func argCompleterVariable() vartypes.Variable { m := hashmap.Empty for k, v := range argCompletersData { m = m.Assoc(types.String(k), v) } return vartypes.NewValidatedPtr(types.NewMap(m), vartypes.ShouldBeMap) } func (ed *Editor) argCompleter() types.Map { return ed.variables["arg-completer"].Get().(types.Map) } // completeArg calls the correct argument completers according to the command // name. It is used by complArg and can also be useful when further dispatching // based on command name is needed -- e.g. in the argument completer for "sudo". func completeArg(words []string, ev *eval.Evaler, rawCands chan<- rawCandidate) error { logger.Printf("completing argument: %q", words) // XXX(xiaq): not the best way to get argCompleter. m := ev.Editor.(*Editor).argCompleter() var v types.Value if m.HasKey(types.String(words[0])) { v = m.IndexOne(types.String(words[0])) } else { v = m.IndexOne(types.String("")) } fn, ok := v.(eval.Fn) if !ok { return ErrCompleterMustBeFn } return callArgCompleter(fn, ev, words, rawCands) } type builtinArgCompleter struct { name string impl func([]string, *eval.Evaler, chan<- rawCandidate) error } var _ eval.Fn = &builtinArgCompleter{} func (bac *builtinArgCompleter) Kind() string { return "fn" } // Equal compares by identity. func (bac *builtinArgCompleter) Equal(a interface{}) bool { return bac == a } func (bac *builtinArgCompleter) Hash() uint32 { return hash.Pointer(unsafe.Pointer(bac)) } func (bac *builtinArgCompleter) Repr(int) string { return "$edit:" + bac.name + eval.FnSuffix } func (bac *builtinArgCompleter) Call(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { eval.TakeNoOpt(opts) words := make([]string, len(args)) for i, arg := range args { s, ok := arg.(types.String) if !ok { throw(ErrCompleterArgMustBeString) } words[i] = string(s) } rawCands := make(chan rawCandidate) var err error go func() { defer close(rawCands) err = bac.impl(words, ec.Evaler, rawCands) }() output := ec.OutputChan() for rc := range rawCands { output <- rc } maybeThrow(err) } func complFilename(words []string, ev *eval.Evaler, rawCands chan<- rawCandidate) error { if len(words) < 1 { return ErrTooFewArguments } return complFilenameInner(words[len(words)-1], false, rawCands) } func complSudo(words []string, ev *eval.Evaler, rawCands chan<- rawCandidate) error { if len(words) < 2 { return ErrTooFewArguments } if len(words) == 2 { return complFormHeadInner(words[1], ev, rawCands) } return completeArg(words[1:], ev, rawCands) } // callArgCompleter calls a Fn, assuming that it is an arg completer. It calls // the Fn with specified arguments and closed input, and converts its output to // candidate objects. func callArgCompleter(fn eval.Fn, ev *eval.Evaler, words []string, rawCands chan<- rawCandidate) error { // Quick path for builtin arg completers. if builtin, ok := fn.(*builtinArgCompleter); ok { return builtin.impl(words, ev, rawCands) } args := make([]types.Value, len(words)) for i, word := range words { args[i] = types.String(word) } ports := []*eval.Port{ eval.DevNullClosedChan, {}, // Will be replaced when capturing output {File: os.Stderr}, } valuesCb := func(ch <-chan types.Value) { for v := range ch { switch v := v.(type) { case rawCandidate: rawCands <- v case types.String: rawCands <- plainCandidate(v) default: logger.Printf("completer must output string or candidate") } } } bytesCb := func(r *os.File) { buffered := bufio.NewReader(r) for { line, err := buffered.ReadString('\n') if line != "" { rawCands <- plainCandidate(strings.TrimSuffix(line, "\n")) } if err != nil { if err != io.EOF { logger.Println("error on reading:", err) } break } } } // XXX There is no source to pass to NewTopEvalCtx. ec := eval.NewTopFrame(ev, eval.NewInternalSource("[editor completer]"), ports) err := ec.PCaptureOutputInner(fn, args, eval.NoOpts, valuesCb, bytesCb) if err != nil { err = errors.New("completer error: " + err.Error()) } return err } elvish-0.11+ds1/edit/binding_table.go000066400000000000000000000044071323000013700174370ustar00rootroot00000000000000package edit import ( "errors" "sort" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/parse" ) var errValueShouldBeFn = errors.New("value should be function") func getBinding(bindingVar vartypes.Variable, k ui.Key) eval.Fn { binding := bindingVar.Get().(BindingTable) switch { case binding.HasKey(k): return binding.IndexOne(k).(eval.Fn) case binding.HasKey(ui.Default): return binding.IndexOne(ui.Default).(eval.Fn) default: return nil } } // BindingTable is a special Map that converts its key to ui.Key and ensures // that its values satisfy eval.CallableValue. type BindingTable struct { types.Map } // Repr returns the representation of the binding table as if it were an // ordinary map keyed by strings. func (bt BindingTable) Repr(indent int) string { var builder types.MapReprBuilder builder.Indent = indent var keys ui.Keys bt.Map.IterateKey(func(k types.Value) bool { keys = append(keys, k.(ui.Key)) return true }) sort.Sort(keys) for _, k := range keys { v := bt.Map.IndexOne(k) builder.WritePair(parse.Quote(k.String()), indent+2, v.Repr(indent+2)) } return builder.String() } // IndexOne converts the index to ui.Key and uses the IndexOne of the inner Map. func (bt BindingTable) IndexOne(idx types.Value) types.Value { return bt.Map.IndexOne(ui.ToKey(idx)) } func (bt BindingTable) get(k ui.Key) eval.Fn { return bt.Map.IndexOne(k).(eval.Fn) } // Assoc converts the index to ui.Key, ensures that the value is CallableValue, // uses the Assoc of the inner Map and converts the result to a BindingTable. func (bt BindingTable) Assoc(k, v types.Value) types.Value { key := ui.ToKey(k) f, ok := v.(eval.Fn) if !ok { throw(errValueShouldBeFn) } return BindingTable{bt.Map.Assoc(key, f).(types.Map)} } func makeBindingTable(f *eval.Frame, args []types.Value, opts map[string]types.Value) { var raw types.Map eval.ScanArgs(args, &raw) eval.TakeNoOpt(opts) converted := types.EmptyMap raw.IteratePair(func(k, v types.Value) bool { f, ok := v.(eval.Fn) if !ok { throw(errValueShouldBeFn) } converted = converted.Assoc(ui.ToKey(k), f).(types.Map) return true }) f.OutputChan() <- BindingTable{converted} } elvish-0.11+ds1/edit/candidate.go000066400000000000000000000121141323000013700165640ustar00rootroot00000000000000package edit import ( "fmt" "os" "sort" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" "github.com/xiaq/persistent/hash" ) type candidate struct { code string // This is what will be substituted on the command line. menu ui.Styled // This is what is displayed in the completion menu. } // rawCandidate is what can be converted to a candidate. type rawCandidate interface { types.Value text() string cook(q parse.PrimaryType) *candidate } type rawCandidates []rawCandidate func (cs rawCandidates) Len() int { return len(cs) } func (cs rawCandidates) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } func (cs rawCandidates) Less(i, j int) bool { return cs[i].text() < cs[j].text() } // plainCandidate is a minimal implementation of rawCandidate. type plainCandidate types.String func (plainCandidate) Kind() string { return "string" } func (p plainCandidate) Equal(a interface{}) bool { return p == a } func (p plainCandidate) Hash() uint32 { return hash.String(string(p)) } func (p plainCandidate) Repr(l int) string { return types.String(p).Repr(l) } func (p plainCandidate) text() string { return string(p) } func (p plainCandidate) cook(q parse.PrimaryType) *candidate { s := string(p) quoted, _ := parse.QuoteAs(s, q) return &candidate{code: quoted, menu: ui.Unstyled(s)} } // noQuoteCandidate is a rawCandidate that does not quote when cooked. type noQuoteCandidate string func (noQuoteCandidate) Kind() string { return "string" } func (nq noQuoteCandidate) Equal(a interface{}) bool { return nq == a } func (nq noQuoteCandidate) Hash() uint32 { return hash.String(string(nq)) } func (nq noQuoteCandidate) Repr(l int) string { return types.String(nq).Repr(l) } func (nq noQuoteCandidate) text() string { return string(nq) } func (nq noQuoteCandidate) cook(parse.PrimaryType) *candidate { s := string(nq) return &candidate{code: s, menu: ui.Unstyled(s)} } // complexCandidate is an implementation of rawCandidate that offers // customization options. type complexCandidate struct { stem string // Used in the code and the menu. codeSuffix string // Appended to the code. displaySuffix string // Appended to the display. style ui.Styles // Used in the menu. } func (c *complexCandidate) Kind() string { return "map" } func (c *complexCandidate) Equal(a interface{}) bool { rhs, ok := a.(*complexCandidate) return ok && c.stem == rhs.stem && c.codeSuffix == rhs.codeSuffix && c.displaySuffix == rhs.displaySuffix && c.style.Eq(rhs.style) } func (c *complexCandidate) Hash() uint32 { h := hash.DJBInit h = hash.DJBCombine(h, hash.String(c.stem)) h = hash.DJBCombine(h, hash.String(c.codeSuffix)) h = hash.DJBCombine(h, hash.String(c.displaySuffix)) h = hash.DJBCombine(h, c.style.Hash()) return h } func (c *complexCandidate) Repr(indent int) string { // TODO(xiaq): Pretty-print when indent >= 0 return fmt.Sprintf("(edit:complex-candidate %s &code-suffix=%s &display-suffix=%s style=%s)", parse.Quote(c.stem), parse.Quote(c.codeSuffix), parse.Quote(c.displaySuffix), parse.Quote(c.style.String())) } func (c *complexCandidate) text() string { return c.stem } func (c *complexCandidate) cook(q parse.PrimaryType) *candidate { quoted, _ := parse.QuoteAs(c.stem, q) return &candidate{ code: quoted + c.codeSuffix, menu: ui.Styled{c.stem + c.displaySuffix, c.style}, } } // outputComplexCandidate composes a complexCandidate. func outputComplexCandidate(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { var style string c := &complexCandidate{} eval.ScanArgs(args, &c.stem) eval.ScanOpts(opts, eval.OptToScan{"code-suffix", &c.codeSuffix, types.String("")}, eval.OptToScan{"display-suffix", &c.displaySuffix, types.String("")}, eval.OptToScan{"style", &style, types.String("")}, ) if style != "" { c.style = ui.StylesFromString(style) } ec.OutputChan() <- c } func filterRawCandidates(ev *eval.Evaler, matcher eval.Fn, seed string, chanRawCandidate <-chan rawCandidate) ([]rawCandidate, error) { matcherInput := make(chan types.Value) stopCollector := make(chan struct{}) var collected []rawCandidate go func() { defer close(matcherInput) for rc := range chanRawCandidate { collected = append(collected, rc) select { case matcherInput <- types.String(rc.text()): case <-stopCollector: return } } }() defer close(stopCollector) ports := []*eval.Port{ {Chan: matcherInput, File: eval.DevNull}, {File: os.Stdout}, {File: os.Stderr}} ec := eval.NewTopFrame(ev, eval.NewInternalSource("[editor matcher]"), ports) args := []types.Value{types.String(seed)} values, err := ec.PCaptureOutput(matcher, args, eval.NoOpts) if err != nil { return nil, err } else if len(values) != len(collected) { return nil, errIncorrectNumOfResults } var filtered []rawCandidate for i, value := range values { if types.ToBool(value) { filtered = append(filtered, collected[i]) } } sort.Sort(rawCandidates(filtered)) return filtered, nil } elvish-0.11+ds1/edit/compl_getopt.go000066400000000000000000000072461323000013700173560ustar00rootroot00000000000000package edit import ( "strings" "unicode/utf8" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/getopt" "github.com/elves/elvish/parse" ) func complGetopt(ec *eval.Frame, a []types.Value, o map[string]types.Value) { var elemsv, optsv, argsv types.IteratorValue eval.ScanArgs(a, &elemsv, &optsv, &argsv) eval.TakeNoOpt(o) var ( elems []string opts []*getopt.Option args []eval.Fn variadic bool ) desc := make(map[*getopt.Option]string) // Convert arguments. elemsv.Iterate(func(v types.Value) bool { elem, ok := v.(types.String) if !ok { throwf("arg should be string, got %s", v.Kind()) } elems = append(elems, string(elem)) return true }) optsv.Iterate(func(v types.Value) bool { m, ok := v.(types.MapLike) if !ok { throwf("opt should be map-like, got %s", v.Kind()) } get := func(ks string) (string, bool) { kv := types.String(ks) if !m.HasKey(kv) { return "", false } vv := m.IndexOne(kv) if vs, ok := vv.(types.String); ok { return string(vs), true } else { throwf("%s should be string, got %s", ks, vs.Kind()) panic("unreachable") } } opt := &getopt.Option{} if s, ok := get("short"); ok { r, size := utf8.DecodeRuneInString(s) if r == utf8.RuneError || size != len(s) { throwf("short option should be exactly one rune, got %v", parse.Quote(s)) } opt.Short = r } if s, ok := get("long"); ok { opt.Long = s } if opt.Short == 0 && opt.Long == "" { throwf("opt should have at least one of short and long forms") } if s, ok := get("desc"); ok { desc[opt] = s } opts = append(opts, opt) return true }) argsv.Iterate(func(v types.Value) bool { sv, ok := v.(types.String) if ok { if string(sv) == "..." { variadic = true return true } throwf("string except for ... not allowed as argument handler, got %s", parse.Quote(string(sv))) } arg, ok := v.(eval.Fn) if !ok { throwf("argument handler should be fn, got %s", v.Kind()) } args = append(args, arg) return true }) // TODO Configurable config g := getopt.Getopt{opts, getopt.GNUGetoptLong} _, parsedArgs, ctx := g.Parse(elems) out := ec.OutputChan() putShortOpt := func(opt *getopt.Option) { c := &complexCandidate{stem: "-" + string(opt.Short)} if d, ok := desc[opt]; ok { c.displaySuffix = " (" + d + ")" } out <- c } putLongOpt := func(opt *getopt.Option) { c := &complexCandidate{stem: "--" + string(opt.Long)} if d, ok := desc[opt]; ok { c.displaySuffix = " (" + d + ")" } out <- c } switch ctx.Type { case getopt.NewOptionOrArgument, getopt.Argument: // Find argument completer var argCompl eval.Fn if len(parsedArgs) < len(args) { argCompl = args[len(parsedArgs)] } else if variadic { argCompl = args[len(args)-1] } if argCompl != nil { rawCands := make(chan rawCandidate) defer close(rawCands) go func() { for rc := range rawCands { out <- rc } }() err := callArgCompleter(argCompl, ec.Evaler, []string{ctx.Text}, rawCands) maybeThrow(err) } // TODO Notify that there is no suitable argument completer case getopt.NewOption: for _, opt := range opts { if opt.Short != 0 { putShortOpt(opt) } if opt.Long != "" { putLongOpt(opt) } } case getopt.NewLongOption: for _, opt := range opts { if opt.Long != "" { putLongOpt(opt) } } case getopt.LongOption: for _, opt := range opts { if strings.HasPrefix(opt.Long, ctx.Text) { putLongOpt(opt) } } case getopt.ChainShortOption: for _, opt := range opts { if opt.Short != 0 { // XXX loses chained options putShortOpt(opt) } } case getopt.OptionArgument: } } elvish-0.11+ds1/edit/complete_arg.go000066400000000000000000000064301323000013700173150ustar00rootroot00000000000000package edit import ( "fmt" "io/ioutil" "os" "path/filepath" "strings" "github.com/elves/elvish/edit/lscolors" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/parse" ) type argComplContext struct { complContextCommon words []string } func (*argComplContext) name() string { return "argument" } func findArgComplContext(n parse.Node, ev pureEvaler) complContext { if sep, ok := n.(*parse.Sep); ok { if form, ok := sep.Parent().(*parse.Form); ok && form.Head != nil { return &argComplContext{ complContextCommon{ "", quotingForEmptySeed, n.End(), n.End()}, evalFormPure(form, "", n.End(), ev), } } } if primary, ok := n.(*parse.Primary); ok { if compound, seed := primaryInSimpleCompound(primary, ev); compound != nil { if form, ok := compound.Parent().(*parse.Form); ok { if form.Head != nil && form.Head != compound { return &argComplContext{ complContextCommon{ seed, primary.Type, compound.Begin(), compound.End()}, evalFormPure(form, seed, compound.Begin(), ev), } } } } } return nil } func evalFormPure(form *parse.Form, seed string, seedBegin int, ev pureEvaler) []string { // Find out head of the form and preceding arguments. // If form.Head is not a simple compound, head will be "", just what we want. head, _ := ev.PurelyEvalPartialCompound(form.Head, nil) words := []string{head} for _, compound := range form.Args { if compound.Begin() >= seedBegin { break } if arg, err := ev.PurelyEvalCompound(compound); err == nil { // XXX Arguments that are not simple compounds are simply ignored. words = append(words, arg) } } words = append(words, seed) return words } // To complete an argument, delegate the actual completion work to a suitable // complContext. func (ctx *argComplContext) generate(ev *eval.Evaler, ch chan<- rawCandidate) error { return completeArg(ctx.words, ev, ch) } // TODO: getStyle does redundant stats. func complFilenameInner(head string, executableOnly bool, rawCands chan<- rawCandidate) error { dir, fileprefix := filepath.Split(head) dirToRead := dir if dirToRead == "" { dirToRead = "." } infos, err := ioutil.ReadDir(dirToRead) if err != nil { return fmt.Errorf("cannot list directory %s: %v", dirToRead, err) } lsColor := lscolors.GetColorist() // Make candidates out of elements that match the file component. for _, info := range infos { name := info.Name() // Show dot files iff file part of pattern starts with dot, and vice // versa. if dotfile(fileprefix) != dotfile(name) { continue } // Only accept searchable directories and executable files if // executableOnly is true. if executableOnly && !(info.IsDir() || (info.Mode()&0111) != 0) { continue } // Full filename for source and getStyle. full := dir + name suffix := " " if info.IsDir() { suffix = string(filepath.Separator) } else if info.Mode()&os.ModeSymlink != 0 { stat, err := os.Stat(full) if err == nil && stat.IsDir() { // Symlink to directory. suffix = string(filepath.Separator) } } rawCands <- &complexCandidate{ stem: full, codeSuffix: suffix, style: ui.StylesFromString(lsColor.GetStyle(full)), } } return nil } func dotfile(fname string) bool { return strings.HasPrefix(fname, ".") } elvish-0.11+ds1/edit/complete_arg_test.go000066400000000000000000000007431323000013700203550ustar00rootroot00000000000000package edit import ( "testing" "github.com/elves/elvish/parse" ) func TestFindArgComplContext(t *testing.T) { testComplContextFinder(t, "findArgComplContext", findArgComplContext, []complContextFinderTest{ {"a ", &argComplContext{ complContextCommon{"", quotingForEmptySeed, 2, 2}, []string{"a", ""}}}, {"a b", &argComplContext{ complContextCommon{"b", parse.Bareword, 2, 3}, []string{"a", "b"}}}, // No space after command; won't complete arg {"a", nil}, }) } elvish-0.11+ds1/edit/complete_arg_unix_test.go000066400000000000000000000052071323000013700214200ustar00rootroot00000000000000// +build !windows,!plan9 // TODO: Enable on Windows. package edit import ( "os" "reflect" "sort" "testing" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/util" ) var ( fileStyle = ui.StylesFromString("1") exeStyle = ui.StylesFromString("2") dirStyle = ui.StylesFromString("4") ) var complFilenameInnerTests = []struct { head string executableOnly bool wantCandidates rawCandidates }{ // Match all non-hidden files and dirs, in alphabetical order. // Files have suffix " " and directories "/". Styles are set according to // the LS_COLORS variable, which are set in the beginning of the test. {"haha", false, rawCandidates{ &complexCandidate{stem: "Documents", codeSuffix: "/", style: dirStyle}, &complexCandidate{stem: "bar", codeSuffix: " ", style: fileStyle}, &complexCandidate{stem: "elvish", codeSuffix: " ", style: exeStyle}, &complexCandidate{stem: "foo", codeSuffix: " ", style: fileStyle}, }}, // Only match executables and directories. {"haha", true, rawCandidates{ &complexCandidate{stem: "Documents", codeSuffix: "/", style: dirStyle}, &complexCandidate{stem: "elvish", codeSuffix: " ", style: exeStyle}, }}, // Match hidden files and directories. {".haha", false, rawCandidates{ &complexCandidate{stem: ".elvish", codeSuffix: "/", style: dirStyle}, &complexCandidate{stem: ".vimrc", codeSuffix: " ", style: fileStyle}, }}, } func TestComplFilenameInner(t *testing.T) { os.Setenv("LS_COLORS", "rs=1:ex=2:di=4") util.InTempDir(func(string) { create("foo", 0600) create(".vimrc", 0600) create("bar", 0600) create("elvish", 0700) mkdir("Documents", 0700) mkdir(".elvish", 0700) for _, test := range complFilenameInnerTests { var ( err error cands rawCandidates gets = make(chan rawCandidate) ) go func() { defer close(gets) err = complFilenameInner(test.head, test.executableOnly, gets) }() for v := range gets { cands = append(cands, v) } if err != nil { t.Errorf("complFilenameInner(%v, %v) returns error %v, want nil", test.head, test.executableOnly, err) } sort.Sort(cands) if !reflect.DeepEqual(cands, test.wantCandidates) { t.Errorf("complFilenameInner(%v, %v) returns %v, want %v", test.head, test.executableOnly, cands, test.wantCandidates) t.Log("returned candidates are:") for _, cand := range cands { t.Logf("%#v", cand) } } } }) } func mkdir(dirname string, perm os.FileMode) { err := os.Mkdir(dirname, perm) if err != nil { panic(err) } } func create(fname string, perm os.FileMode) { f, err := os.OpenFile(fname, os.O_CREATE, perm) if err != nil { panic(err) } f.Close() } elvish-0.11+ds1/edit/complete_command.go000066400000000000000000000055321323000013700201640ustar00rootroot00000000000000package edit import ( "strings" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" ) type commandComplContext struct { complContextCommon } const quotingForEmptySeed = parse.Bareword func findCommandComplContext(n parse.Node, ev pureEvaler) complContext { // Determine if we are starting a new command. There are 3 cases: // 1. The whole chunk is empty (nothing entered at all): the leaf is a // Chunk. // 2. Just after a newline or semicolon: the leaf is a Sep and its parent is // a Chunk. // 3. Just after a pipe: the leaf is a Sep and its parent is a Pipeline. if parse.IsChunk(n) { return &commandComplContext{ complContextCommon{"", parse.Bareword, n.End(), n.End()}} } if parse.IsSep(n) { parent := n.Parent() switch { case parse.IsChunk(parent), parse.IsPipeline(parent): return &commandComplContext{ complContextCommon{"", quotingForEmptySeed, n.End(), n.End()}} case parse.IsPrimary(parent): ptype := parent.(*parse.Primary).Type if ptype == parse.OutputCapture || ptype == parse.ExceptionCapture { return &commandComplContext{ complContextCommon{"", quotingForEmptySeed, n.End(), n.End()}} } } } if primary, ok := n.(*parse.Primary); ok { if compound, seed := primaryInSimpleCompound(primary, ev); compound != nil { if form, ok := compound.Parent().(*parse.Form); ok { if form.Head == compound { return &commandComplContext{ complContextCommon{seed, primary.Type, compound.Begin(), compound.End()}} } } } } return nil } func (*commandComplContext) name() string { return "command" } func (ctx *commandComplContext) generate(ev *eval.Evaler, ch chan<- rawCandidate) error { return complFormHeadInner(ctx.seed, ev, ch) } func complFormHeadInner(head string, ev *eval.Evaler, rawCands chan<- rawCandidate) error { if util.DontSearch(head) { return complFilenameInner(head, true, rawCands) } got := func(s string) { rawCands <- plainCandidate(s) } for special := range eval.IsBuiltinSpecial { got(special) } explode, ns, _ := eval.ParseVariable(head) if !explode { ev.EachVariableInTop(ns, func(varname string) { if strings.HasSuffix(varname, eval.FnSuffix) { got(eval.MakeVariableName(false, ns, varname[:len(varname)-len(eval.FnSuffix)])) } else { name := eval.MakeVariableName(false, ns, varname) rawCands <- &complexCandidate{name, " = ", " = ", ui.Styles{}} } }) } eval.EachExternal(func(command string) { got(command) if strings.HasPrefix(head, "e:") { got("e:" + command) } }) // TODO Support non-module namespaces. for name := range ev.Global { if head != name && strings.HasSuffix(name, eval.NsSuffix) { got(name) } } for name := range ev.Builtin { if head != name && strings.HasSuffix(name, eval.NsSuffix) { got(name) } } return nil } elvish-0.11+ds1/edit/complete_command_test.go000066400000000000000000000040231323000013700212150ustar00rootroot00000000000000package edit import ( "testing" "github.com/elves/elvish/parse" ) func TestFindCommandComplContext(t *testing.T) { testComplContextFinder(t, "findCommandComplContext", findCommandComplContext, []complContextFinderTest{ // Very beginning, empty seed {"", &commandComplContext{ complContextCommon{"", quotingForEmptySeed, 0, 0}}}, // Very beginning, nonempty seed {"a", &commandComplContext{ complContextCommon{"a", parse.Bareword, 0, 1}}}, // Very beginning, nonempty seed with quoting {`"a"`, &commandComplContext{ complContextCommon{"a", parse.DoubleQuoted, 0, 3}}}, // Very beginning, nonempty seed with partial quoting {`"a`, &commandComplContext{ complContextCommon{"a", parse.DoubleQuoted, 0, 2}}}, // Beginning of output capture, empty seed {"a (", &commandComplContext{ complContextCommon{"", quotingForEmptySeed, 3, 3}}}, // Beginning of output capture, nonempty seed {"a (b", &commandComplContext{ complContextCommon{"b", parse.Bareword, 3, 4}}}, // Beginning of exception capture, empty seed {"a ?(", &commandComplContext{ complContextCommon{"", quotingForEmptySeed, 4, 4}}}, // Beginning of exception capture, nonempty seed {"a ?(b", &commandComplContext{ complContextCommon{"b", parse.Bareword, 4, 5}}}, // Beginning of lambda, empty seed {"a { ", &commandComplContext{ complContextCommon{"", quotingForEmptySeed, 4, 4}}}, // Beginning of lambda, nonempty seed {"a { b", &commandComplContext{ complContextCommon{"b", parse.Bareword, 4, 5}}}, // After another command and pipe, empty seed {"a|", &commandComplContext{ complContextCommon{"", quotingForEmptySeed, 2, 2}}}, // After another command and pipe, nonempty seed {"a|b", &commandComplContext{ complContextCommon{"b", parse.Bareword, 2, 3}}}, // After another pipeline, empty seed {"a\n", &commandComplContext{ complContextCommon{"", quotingForEmptySeed, 2, 2}}}, // After another pipeline, nonempty seed {"a\nb", &commandComplContext{ complContextCommon{"b", parse.Bareword, 2, 3}}}, }) } elvish-0.11+ds1/edit/complete_index.go000066400000000000000000000047731323000013700176630ustar00rootroot00000000000000package edit import ( "errors" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" ) var errCannotIterateKey = errors.New("indexee does not support iterating keys") type indexComplContext struct { complContextCommon indexee types.Value } func (*indexComplContext) name() string { return "index" } // Find context information for complIndex. // // Right now we only support cases where there is only one level of indexing, // e.g. $a[ is supported but $a[x][ is not. func findIndexComplContext(n parse.Node, ev pureEvaler) complContext { if parse.IsSep(n) { if parse.IsIndexing(n.Parent()) { // We are just after an opening bracket. indexing := parse.GetIndexing(n.Parent()) if len(indexing.Indicies) == 1 { if indexee := ev.PurelyEvalPrimary(indexing.Head); indexee != nil { return &indexComplContext{ complContextCommon{ "", quotingForEmptySeed, n.End(), n.End()}, indexee, } } } } if parse.IsArray(n.Parent()) { array := n.Parent() if parse.IsIndexing(array.Parent()) { // We are after an existing index and spaces. indexing := parse.GetIndexing(array.Parent()) if len(indexing.Indicies) == 1 { if indexee := ev.PurelyEvalPrimary(indexing.Head); indexee != nil { return &indexComplContext{ complContextCommon{ "", quotingForEmptySeed, n.End(), n.End()}, indexee, } } } } } } if parse.IsPrimary(n) { primary := parse.GetPrimary(n) compound, seed := primaryInSimpleCompound(primary, ev) if compound != nil { if parse.IsArray(compound.Parent()) { array := compound.Parent() if parse.IsIndexing(array.Parent()) { // We are just after an incomplete index. indexing := parse.GetIndexing(array.Parent()) if len(indexing.Indicies) == 1 { if indexee := ev.PurelyEvalPrimary(indexing.Head); indexee != nil { return &indexComplContext{ complContextCommon{ seed, primary.Type, compound.Begin(), compound.End()}, indexee, } } } } } } } return nil } func (ctx *indexComplContext) generate(ev *eval.Evaler, ch chan<- rawCandidate) error { m, ok := ctx.indexee.(types.IterateKeyer) if !ok { return errCannotIterateKey } complIndexInner(m, ch) return nil } func complIndexInner(m types.IterateKeyer, ch chan<- rawCandidate) { m.IterateKey(func(v types.Value) bool { if keyv, ok := v.(types.String); ok { ch <- plainCandidate(keyv) } return true }) } elvish-0.11+ds1/edit/complete_index_test.go000066400000000000000000000025661323000013700207200ustar00rootroot00000000000000package edit import ( "reflect" "sort" "testing" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" ) var testIndexee = types.String("a") func TestFindIndexComplContext(t *testing.T) { testComplContextFinder(t, "findIndexComplContext", findIndexComplContext, []complContextFinderTest{ {"a[", &indexComplContext{ complContextCommon{"", quotingForEmptySeed, 2, 2}, testIndexee}}, {"a[x", &indexComplContext{ complContextCommon{"x", parse.Bareword, 2, 3}, testIndexee}}, {"a[x ", &indexComplContext{ complContextCommon{"", quotingForEmptySeed, 4, 4}, testIndexee}}, // Not supported when indexee cannot be evaluated statically {"(x)[", nil}, // Multi-layer indexing not supported yet {"a[x][", nil}, }) } func TestComplIndexInner(t *testing.T) { m := types.MakeMap(map[types.Value]types.Value{ types.String("foo"): types.String("bar"), types.String("lorem"): types.String("ipsum"), }) var ( candidates rawCandidates wantCandidates = rawCandidates{ plainCandidate("foo"), plainCandidate("lorem"), } ) gets := make(chan rawCandidate) go func() { defer close(gets) complIndexInner(m, gets) }() for v := range gets { candidates = append(candidates, v) } sort.Sort(candidates) if !reflect.DeepEqual(candidates, wantCandidates) { t.Errorf("complIndexInner(%v) = %v, want %v", m, candidates, wantCandidates) } } elvish-0.11+ds1/edit/complete_redir.go000066400000000000000000000015521323000013700176510ustar00rootroot00000000000000package edit import ( "github.com/elves/elvish/eval" "github.com/elves/elvish/parse" ) type redirComplContext struct { complContextCommon } func (*redirComplContext) name() string { return "redir" } func findRedirComplContext(n parse.Node, ev pureEvaler) complContext { if parse.IsSep(n) { if parse.IsRedir(n.Parent()) { return &redirComplContext{complContextCommon{ "", quotingForEmptySeed, n.End(), n.End()}} } } if primary, ok := n.(*parse.Primary); ok { if compound, seed := primaryInSimpleCompound(primary, ev); compound != nil { if parse.IsRedir(compound.Parent()) { return &redirComplContext{complContextCommon{ seed, primary.Type, compound.Begin(), compound.End()}} } } } return nil } func (ctx *redirComplContext) generate(ev *eval.Evaler, ch chan<- rawCandidate) error { return complFilenameInner(ctx.seed, false, ch) } elvish-0.11+ds1/edit/complete_redir_test.go000066400000000000000000000005501323000013700207050ustar00rootroot00000000000000package edit import "testing" func TestFindRedirComplContext(t *testing.T) { testComplContextFinder(t, "findRedirComplContext", findRedirComplContext, []complContextFinderTest{ {"a >", &redirComplContext{ complContextCommon{"", quotingForEmptySeed, 3, 3}}}, {"a >b", &redirComplContext{ complContextCommon{"b", quotingForEmptySeed, 3, 4}}}, }) } elvish-0.11+ds1/edit/complete_variable.go000066400000000000000000000030571323000013700203330ustar00rootroot00000000000000package edit import ( "strings" "github.com/elves/elvish/eval" "github.com/elves/elvish/parse" ) type variableComplContext struct { complContextCommon ns, nsPart string } func (*variableComplContext) name() string { return "variable" } func findVariableComplContext(n parse.Node, _ pureEvaler) complContext { primary := parse.GetPrimary(n) if primary != nil && primary.Type == parse.Variable { explode, qname := eval.ParseVariableSplice(primary.Value) nsPart, nameSeed := eval.ParseVariableQName(qname) // Move past "$", "@" and ":". begin := primary.Begin() + 1 + len(explode) + len(nsPart) ns := nsPart if len(ns) > 0 { ns = ns[:len(ns)-1] } return &variableComplContext{ complContextCommon{nameSeed, parse.Bareword, begin, primary.End()}, ns, nsPart, } } return nil } type evalerScopes interface { EachVariableInTop(string, func(string)) EachNsInTop(func(string)) } func (ctx *variableComplContext) generate(ev *eval.Evaler, ch chan<- rawCandidate) error { complVariable(ctx.ns, ctx.nsPart, ev, ch) return nil } func complVariable(ctxNs, ctxNsPart string, ev evalerScopes, ch chan<- rawCandidate) { ev.EachVariableInTop(ctxNs, func(varname string) { ch <- noQuoteCandidate(varname) }) ev.EachNsInTop(func(ns string) { nsPart := ns + ":" // This is to match namespaces that are "nested" under the current // namespace. if hasProperPrefix(nsPart, ctxNsPart) { ch <- noQuoteCandidate(nsPart[len(ctxNsPart):]) } }) } func hasProperPrefix(s, p string) bool { return len(s) > len(p) && strings.HasPrefix(s, p) } elvish-0.11+ds1/edit/complete_variable_test.go000066400000000000000000000044431323000013700213720ustar00rootroot00000000000000package edit import ( "reflect" "sort" "testing" "github.com/elves/elvish/parse" ) func TestFindVariableComplContext(t *testing.T) { testComplContextFinder(t, "findVariableComplContext", findVariableComplContext, []complContextFinderTest{ {"$", &variableComplContext{ complContextCommon{"", parse.Bareword, 1, 1}, "", ""}}, {"$a", &variableComplContext{ complContextCommon{"a", parse.Bareword, 1, 2}, "", ""}}, {"$a:", &variableComplContext{ complContextCommon{"", parse.Bareword, 3, 3}, "a", "a:"}}, {"$a:b", &variableComplContext{ complContextCommon{"b", parse.Bareword, 3, 4}, "a", "a:"}}, // Wrong contexts {"", nil}, {"echo", nil}, }) } type testEvalerScopes struct{} var testScopes = map[string]map[string]int{ "": {"veni": 0, "vidi": 0, "vici": 0}, "foo": {"lorem": 0, "ipsum": 0}, "foo:bar": {"lorem": 0, "dolor": 0}, } func (testEvalerScopes) EachNsInTop(f func(string)) { for ns := range testScopes { if ns != "" { f(ns) } } } func (testEvalerScopes) EachVariableInTop(ns string, f func(string)) { for name := range testScopes[ns] { f(name) } } var complVariableTests = []struct { ns string nsPart string want []rawCandidate }{ // No namespace: complete variables and namespaces {"", "", []rawCandidate{ noQuoteCandidate("foo:"), noQuoteCandidate("foo:bar:"), noQuoteCandidate("veni"), noQuoteCandidate("vici"), noQuoteCandidate("vidi"), }}, // Nonempty namespace: complete variables in namespace and subnamespaces // (but not variables in subnamespaces) {"foo", "foo:", []rawCandidate{ noQuoteCandidate("bar:"), noQuoteCandidate("ipsum"), noQuoteCandidate("lorem"), }}, // Bad namespace {"bad", "bad:", nil}, } func TestComplVariable(t *testing.T) { for _, test := range complVariableTests { got := collectComplVariable(test.ns, test.nsPart, testEvalerScopes{}) if !reflect.DeepEqual(got, test.want) { t.Errorf("complVariable(%q, %q, ...) => %v, want %v", test.ns, test.nsPart, got, test.want) } } } func collectComplVariable(ns, nsPart string, ev evalerScopes) []rawCandidate { ch := make(chan rawCandidate) go func() { complVariable(ns, nsPart, ev, ch) close(ch) }() var results []rawCandidate for result := range ch { results = append(results, result) } sort.Sort(rawCandidates(results)) return results } elvish-0.11+ds1/edit/completion.go000066400000000000000000000101261323000013700170220ustar00rootroot00000000000000package edit // Completion in Elvish is organized around the concept of "completers", // functions that take the current AST Node (the Node that the cursor is at, // always a leaf in the AST) and an eval.Evaler and returns a specification for // the completion (a complSpec) -- a list of completion candidates, and which // part of the source code they can **replace**. When completion is requested, // the editor calls each completer; it is up to the completer to decide whether // they apply to the current context. As soon as one completer returns results, // the remaining completers are not tried. // // As an example instance, if the user writes the following and presses Tab: // // echo $p // // assuming that only the builtin variables $paths, $pid and $pwd are viable // candidates, one of the completers -- the variable completer -- will return a // complSpec that means "any of paths, pid and pwd can replace the 'p' in the // source code". // // Note that the "replace" part in the semantics of complSpec is important: in // the default setting of prefix matching, it might be easier to define // complSpec in such a way that completers say "any of aths, id and wd can be // appended to the 'p' in the source code". However, this is not flexible enough // for alternative matching mechanism like substring matching or subsequence // matching, where the "seed" of completion (here, p) may not be a prefix of the // candidates. // // There is one completer that deserves more attention than others, the // completer for arguments. Unlike other completers, it delegates most of its // work to argument completers. See the comment in arg_completers.go for // details. import ( "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" ) type complContext interface { name() string common() *complContextCommon generate(*eval.Evaler, chan<- rawCandidate) error } type complContextCommon struct { seed string quoting parse.PrimaryType begin, end int } func (c *complContextCommon) common() *complContextCommon { return c } // complSpec is the result of a completion, meaning that any of the candidates // can replace the text in the interval [begin, end). type complSpec struct { begin int end int candidates []*candidate } // A complContextFinder takes the current Node (always a leaf in the AST) and an // Evaler, and returns a complContext. If the complContext does not apply to the // type of the current Node, it should return nil. type complContextFinder func(parse.Node, pureEvaler) complContext type pureEvaler interface { PurelyEvalCompound(*parse.Compound) (string, error) PurelyEvalPartialCompound(cn *parse.Compound, upto *parse.Indexing) (string, error) PurelyEvalPrimary(*parse.Primary) types.Value } var complContextFinders = []complContextFinder{ findVariableComplContext, findCommandComplContext, findIndexComplContext, findRedirComplContext, findArgComplContext, } // complete takes a Node and Evaler and tries all complContexts. It returns the // name of the complContext, and the result and error it gave. If no complContext is // available, it returns an empty complContext name. func complete(n parse.Node, ev *eval.Evaler) (string, *complSpec, error) { ed := ev.Editor.(*Editor) for _, finder := range complContextFinders { ctx := finder(n, ev) if ctx == nil { continue } name := ctx.name() ctxCommon := ctx.common() matcher, ok := ed.lookupMatcher(name) if !ok { return name, nil, errMatcherMustBeFn } chanRawCandidate := make(chan rawCandidate) chanErrGenerate := make(chan error) go func() { err := ctx.generate(ev, chanRawCandidate) close(chanRawCandidate) chanErrGenerate <- err }() rawCandidates, errFilter := filterRawCandidates(ev, matcher, ctxCommon.seed, chanRawCandidate) candidates := make([]*candidate, len(rawCandidates)) for i, raw := range rawCandidates { candidates[i] = raw.cook(ctxCommon.quoting) } spec := &complSpec{ctxCommon.begin, ctxCommon.end, candidates} return name, spec, util.Errors(<-chanErrGenerate, errFilter) } return "", nil, nil } elvish-0.11+ds1/edit/completion_mode.go000066400000000000000000000224751323000013700200400ustar00rootroot00000000000000package edit import ( "fmt" "strings" "unicode/utf8" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/util" ) // Completion mode. // Interface. var _ = registerBuiltins(modeCompletion, map[string]func(*Editor){ "smart-start": complSmartStart, "start": complStart, "up": complUp, "up-cycle": complUpCycle, "down": complDown, "down-cycle": complDownCycle, "left": complLeft, "right": complRight, "accept": complAccept, "trigger-filter": complTriggerFilter, "default": complDefault, }) type completion struct { complSpec completer string filtering bool filter string filtered []*candidate selected int firstShown int lastShownInFull int height int } func (*completion) Binding(m map[string]vartypes.Variable, k ui.Key) eval.Fn { return getBinding(m[modeCompletion], k) } func (c *completion) needScrollbar() bool { return c.firstShown > 0 || c.lastShownInFull < len(c.filtered)-1 } func (c *completion) ModeLine() ui.Renderer { ml := modeLineRenderer{fmt.Sprintf(" COMPLETING %s ", c.completer), c.filter} if !c.needScrollbar() { return ml } return modeLineWithScrollBarRenderer{ml, len(c.filtered), c.firstShown, c.lastShownInFull + 1} } func (c *completion) CursorOnModeLine() bool { return c.filtering } func complStart(ed *Editor) { startCompletionInner(ed, false) } func complSmartStart(ed *Editor) { startCompletionInner(ed, true) } func complUp(ed *Editor) { ed.completion.prev(false) } func complDown(ed *Editor) { ed.completion.next(false) } func complLeft(ed *Editor) { if c := ed.completion.selected - ed.completion.height; c >= 0 { ed.completion.selected = c } } func complRight(ed *Editor) { if c := ed.completion.selected + ed.completion.height; c < len(ed.completion.filtered) { ed.completion.selected = c } } func complUpCycle(ed *Editor) { ed.completion.prev(true) } func complDownCycle(ed *Editor) { ed.completion.next(true) } // acceptCompletion accepts currently selected completion candidate. func complAccept(ed *Editor) { c := ed.completion if 0 <= c.selected && c.selected < len(c.filtered) { ed.buffer, ed.dot = c.apply(ed.buffer, ed.dot) } ed.mode = &ed.insert } func complDefault(ed *Editor) { k := ed.lastKey c := &ed.completion if c.filtering && likeChar(k) { c.changeFilter(c.filter + string(k.Rune)) } else if c.filtering && k == (ui.Key{ui.Backspace, 0}) { _, size := utf8.DecodeLastRuneInString(c.filter) if size > 0 { c.changeFilter(c.filter[:len(c.filter)-size]) } } else { complAccept(ed) ed.setAction(reprocessKey) } } func complTriggerFilter(ed *Editor) { c := &ed.completion if c.filtering { c.filtering = false c.changeFilter("") } else { c.filtering = true } } func (c *completion) selectedCandidate() *candidate { if c.selected == -1 { return &candidate{} } return c.filtered[c.selected] } // apply returns the line and dot after applying a candidate. func (c *completion) apply(line string, dot int) (string, int) { text := c.selectedCandidate().code return line[:c.begin] + text + line[c.end:], c.begin + len(text) } func (c *completion) prev(cycle bool) { c.selected-- if c.selected == -1 { if cycle { c.selected = len(c.filtered) - 1 } else { c.selected++ } } } func (c *completion) next(cycle bool) { c.selected++ if c.selected == len(c.filtered) { if cycle { c.selected = 0 } else { c.selected-- } } } func startCompletionInner(ed *Editor, acceptPrefix bool) { node := findLeafNode(ed.chunk, ed.dot) if node == nil { return } completer, complSpec, err := complete(node, ed.evaler) if err != nil { ed.addTip("%v", err) // We don't show the full stack trace. To make debugging still possible, // we log it. if pprinter, ok := err.(util.Pprinter); ok { logger.Println("matcher error:") logger.Println(pprinter.Pprint("")) } } else if completer == "" { ed.addTip("unsupported completion :(") logger.Println("path to current leaf, leaf first") for n := node; n != nil; n = n.Parent() { logger.Printf("%T (%d-%d)", n, n.Begin(), n.End()) } } else if len(complSpec.candidates) == 0 { ed.addTip("no candidate for %s", completer) } else { if acceptPrefix { // If there is a non-empty longest common prefix, insert it and // don't start completion mode. // // As a special case, when there is exactly one candidate, it is // immeidately accepted. prefix := complSpec.candidates[0].code for _, cand := range complSpec.candidates[1:] { prefix = commonPrefix(prefix, cand.code) if prefix == "" { break } } if prefix != "" && len(prefix) > complSpec.end-complSpec.begin { ed.buffer = ed.buffer[:complSpec.begin] + prefix + ed.buffer[complSpec.end:] ed.dot = complSpec.begin + len(prefix) return } } ed.completion = completion{ completer: completer, complSpec: *complSpec, filtered: complSpec.candidates, } ed.mode = &ed.completion } } // commonPrefix returns the longest common prefix of two strings. func commonPrefix(s, t string) string { for i, r := range s { if i >= len(t) { return s[:i] } r2, _ := utf8.DecodeRuneInString(t[i:]) if r2 != r { return s[:i] } } return s } const ( completionColMarginLeft = 1 completionColMarginRight = 1 completionColMarginTotal = completionColMarginLeft + completionColMarginRight ) // maxWidth finds the maximum wcwidth of display texts of candidates [lo, hi). // hi may be larger than the number of candidates, in which case it is truncated // to the number of candidates. func (c *completion) maxWidth(lo, hi int) int { if hi > len(c.filtered) { hi = len(c.filtered) } width := 0 for i := lo; i < hi; i++ { w := util.Wcswidth(c.filtered[i].menu.Text) if width < w { width = w } } return width } func (c *completion) ListRender(width, maxHeight int) *ui.Buffer { b := ui.NewBuffer(width) cands := c.filtered if len(cands) == 0 { b.WriteString(util.TrimWcwidth("(no result)", width), "") return b } if maxHeight <= 1 || width <= 2 { b.WriteString(util.TrimWcwidth("(terminal too small)", width), "") return b } // Reserve the the rightmost row as margins. width-- // Determine comp.height and comp.firstShown. // First determine whether all candidates can be fit in the screen, // assuming that they are all of maximum width. If that is the case, we use // the computed height as the height for the listing, and the first // candidate to show is 0. Otherwise, we use min(height, len(cands)) as the // height and find the first candidate to show. perLine := max(1, width/(c.maxWidth(0, len(cands))+completionColMarginTotal)) heightBound := util.CeilDiv(len(cands), perLine) first := 0 height := 0 if heightBound < maxHeight { height = heightBound } else { height = min(maxHeight, len(cands)) // Determine the first column to show. We start with the column in which the // selected one is found, moving to the left until either the width is // exhausted, or the old value of firstShown has been hit. first = c.selected / height * height w := c.maxWidth(first, first+height) + completionColMarginTotal for ; first > c.firstShown; first -= height { dw := c.maxWidth(first-height, first) + completionColMarginTotal if w+dw > width { break } w += dw } } c.height = height c.firstShown = first var i, j int remainedWidth := width trimmed := false // Show the results in columns, until width is exceeded. for i = first; i < len(cands); i += height { // Determine the width of the column (without the margin) colWidth := c.maxWidth(i, min(i+height, len(cands))) totalColWidth := colWidth + completionColMarginTotal if totalColWidth > remainedWidth { totalColWidth = remainedWidth colWidth = totalColWidth - completionColMarginTotal trimmed = true } col := ui.NewBuffer(totalColWidth) for j = i; j < i+height; j++ { if j > i { col.Newline() } if j >= len(cands) { // Write padding to make the listing a rectangle. col.WriteSpaces(totalColWidth, styleForCompletion.String()) } else { col.WriteSpaces(completionColMarginLeft, styleForCompletion.String()) s := ui.JoinStyles(styleForCompletion, cands[j].menu.Styles) if j == c.selected { s = append(s, styleForSelectedCompletion.String()) } col.WriteString(util.ForceWcwidth(cands[j].menu.Text, colWidth), s.String()) col.WriteSpaces(completionColMarginRight, styleForCompletion.String()) if !trimmed { c.lastShownInFull = j } } } b.ExtendRight(col, 0) remainedWidth -= totalColWidth if remainedWidth <= completionColMarginTotal { break } } // When the listing is incomplete, always use up the entire width. if remainedWidth > 0 && c.needScrollbar() { col := ui.NewBuffer(remainedWidth) for i := 0; i < height; i++ { if i > 0 { col.Newline() } col.WriteSpaces(remainedWidth, styleForCompletion.String()) } b.ExtendRight(col, 0) remainedWidth = 0 } return b } func (c *completion) changeFilter(f string) { c.filter = f if f == "" { c.filtered = c.candidates return } c.filtered = nil for _, cand := range c.candidates { if strings.Contains(cand.menu.Text, f) { c.filtered = append(c.filtered, cand) } } if len(c.filtered) > 0 { c.selected = 0 } else { c.selected = -1 } } elvish-0.11+ds1/edit/completion_test.go000066400000000000000000000012631323000013700200630ustar00rootroot00000000000000package edit import ( "reflect" "testing" "github.com/elves/elvish/eval" "github.com/elves/elvish/parse" ) type complContextFinderTest struct { src string want complContext } func testComplContextFinder(t *testing.T, name string, finder complContextFinder, tests []complContextFinderTest) { ev := eval.NewEvaler() defer ev.Close() for _, test := range tests { n, err := parse.Parse("[test]", test.src) // Ignore error as long is n is non-nil if n == nil { panic(err) } leaf := findLeafNode(n, len(test.src)) got := finder(leaf, ev) if !reflect.DeepEqual(got, test.want) { t.Errorf("For %q, %s(leaf) => %v, want %v", test.src, name, got, test.want) } } } elvish-0.11+ds1/edit/dump_buf.go000066400000000000000000000015131323000013700164520ustar00rootroot00000000000000package edit import ( "fmt" "html" "strings" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" ) func _dumpBuf(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { out := ec.OutputFile() buf := ec.Editor.(*Editor).writer.CurrentBuffer() for _, line := range buf.Lines { style := "" openedSpan := false for _, c := range line { if c.Style != style { if openedSpan { fmt.Fprint(out, "") } var classes []string for _, c := range strings.Split(c.Style, ";") { classes = append(classes, "sgr-"+c) } fmt.Fprintf(out, ``, strings.Join(classes, " ")) style = c.Style openedSpan = true } fmt.Fprintf(out, "%s", html.EscapeString(c.Text)) } if openedSpan { fmt.Fprint(out, "") } fmt.Fprint(out, "\n") } } elvish-0.11+ds1/edit/edit.go000066400000000000000000000271211323000013700156010ustar00rootroot00000000000000// Package edit implements a command line editor. package edit import ( "bufio" "bytes" "fmt" "io" "os" "sync" "syscall" "time" "github.com/elves/elvish/daemon" "github.com/elves/elvish/edit/highlight" "github.com/elves/elvish/edit/history" "github.com/elves/elvish/edit/prompt" "github.com/elves/elvish/edit/tty" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/parse" "github.com/elves/elvish/sys" "github.com/elves/elvish/util" ) var logger = util.GetLogger("[edit] ") // Editor keeps the status of the line editor. type Editor struct { in *os.File out *os.File writer tty.Writer reader tty.Reader sigs chan os.Signal daemon *daemon.Client evaler *eval.Evaler variables map[string]vartypes.Variable bindings map[string]vartypes.Variable active bool activeMutex sync.Mutex historyFuser *history.Fuser historyMutex sync.RWMutex // notifyPort is a write-only port that turns data written to it into editor // notifications. notifyPort *eval.Port // notifyRead is the read end of notifyPort.File. notifyRead *os.File editorState } type editorState struct { // States used during ReadLine. Reset at the beginning of ReadLine. restoreTerminal func() error notificationMutex sync.Mutex notifications []string tips []string buffer string dot int chunk *parse.Chunk styling *highlight.Styling parseErrorAtEnd bool promptContent []*ui.Styled rpromptContent []*ui.Styled mode Mode insert insert command command completion completion navigation navigation // A cache of external commands, used in stylist. isExternal map[string]bool // Used for builtins. lastKey ui.Key nextAction action } // NewEditor creates an Editor. When the instance is no longer used, its Close // method should be called. func NewEditor(in *os.File, out *os.File, sigs chan os.Signal, ev *eval.Evaler) *Editor { daemon := ev.DaemonClient ed := &Editor{ in: in, out: out, writer: tty.NewWriter(out), reader: tty.NewReader(in), sigs: sigs, daemon: daemon, evaler: ev, bindings: makeBindings(), variables: makeVariables(), } notifyChan := make(chan types.Value) notifyRead, notifyWrite, err := os.Pipe() if err != nil { panic(err) } ed.notifyPort = &eval.Port{File: notifyWrite, Chan: notifyChan} ed.notifyRead = notifyRead // Forward reads from notifyRead to notification. go func() { reader := bufio.NewReader(notifyRead) for { line, err := reader.ReadString('\n') if err != nil { break } ed.Notify("[bytes out] %s", line[:len(line)-1]) } if err != io.EOF { logger.Println("notifyRead error:", err) } }() // Forward reads from notifyChan to notification. go func() { for v := range notifyChan { ed.Notify("[value out] %s", v.Repr(types.NoPretty)) } }() if daemon != nil { f, err := history.NewFuser(daemon) if err != nil { fmt.Fprintln(os.Stderr, "Failed to initialize command history. Disabled.") } else { ed.historyFuser = f } } ev.Editor = ed installModules(ev.Builtin, ed) err = ev.SourceText(eval.NewScriptSource("[editor]", "[editor]", "use binding; binding:install")) if err != nil { fmt.Fprintln(out, "Failed to load default binding:", err) } return ed } // Close releases resources used by the editor. func (ed *Editor) Close() { ed.reader.Close() close(ed.notifyPort.Chan) ed.notifyPort.File.Close() ed.notifyRead.Close() } // Active returns the activeness of the Editor. func (ed *Editor) Active() bool { return ed.active } // ActiveMutex returns a mutex that must be used when changing the activeness of // the Editor. func (ed *Editor) ActiveMutex() *sync.Mutex { return &ed.activeMutex } func (ed *Editor) Evaler() *eval.Evaler { return ed.evaler } func (ed *Editor) Variable(name string) vartypes.Variable { return ed.variables[name] } func (ed *Editor) flash() { // TODO implement fish-like flash effect } func (ed *Editor) addTip(format string, args ...interface{}) { ed.tips = append(ed.tips, fmt.Sprintf(format, args...)) } // Notify adds one notification entry. It is concurrency-safe. func (ed *Editor) Notify(format string, args ...interface{}) { ed.notificationMutex.Lock() defer ed.notificationMutex.Unlock() ed.notifications = append(ed.notifications, fmt.Sprintf(format, args...)) } func (ed *Editor) refresh(fullRefresh bool, addErrorsToTips bool) error { src := ed.buffer // Parse the current line n, err := parse.Parse("[interactive]", src) ed.chunk = n ed.parseErrorAtEnd = err != nil && atEnd(err, len(src)) // If all parse errors are at the end, it is likely caused by incomplete // input. In that case, do not complain about parse errors. // TODO(xiaq): Find a more reliable way to determine incomplete input. // Ideally the parser should report it. if err != nil && addErrorsToTips && !ed.parseErrorAtEnd { ed.addTip("%s", err) } ed.styling = &highlight.Styling{} doHighlight(n, ed) _, err = ed.evaler.Compile(n, eval.NewInteractiveSource(src)) if err != nil && !atEnd(err, len(src)) { if addErrorsToTips { ed.addTip("%s", err) } // Highlight errors in the input buffer. ctx := err.(*eval.CompilationError).Context ed.styling.Add(ctx.Begin, ctx.End, styleForCompilerError.String()) } // Render onto a buffer. height, width := sys.GetWinsize(ed.out) height = min(height, ed.maxHeight()) er := &editorRenderer{&ed.editorState, height, nil} buf := ui.Render(er, width) return ed.writer.CommitBuffer(er.bufNoti, buf, fullRefresh) } func atEnd(e error, n int) bool { switch e := e.(type) { case *eval.CompilationError: return e.Context.Begin == n case *parse.Error: for _, entry := range e.Entries { if entry.Context.Begin != n { return false } } return true default: logger.Printf("atEnd called with error type %T", e) return false } } // insertAtDot inserts text at the dot and moves the dot after it. func (ed *Editor) insertAtDot(text string) { ed.buffer = ed.buffer[:ed.dot] + text + ed.buffer[ed.dot:] ed.dot += len(text) } // startReadLine prepares the terminal for the editor. func (ed *Editor) startReadLine() error { ed.activeMutex.Lock() defer ed.activeMutex.Unlock() ed.active = true restoreTerminal, err := tty.Setup(ed.in, ed.out) if err != nil { if restoreTerminal != nil { restoreTerminal() } return err } ed.restoreTerminal = restoreTerminal return nil } // finishReadLine puts the terminal in a state suitable for other programs to // use. func (ed *Editor) finishReadLine() error { ed.activeMutex.Lock() defer ed.activeMutex.Unlock() ed.active = false // Refresh the terminal for the last time in a clean-ish state. ed.mode = &ed.insert ed.tips = nil ed.dot = len(ed.buffer) if !prompt.RpromptPersistent(ed) { ed.rpromptContent = nil } errRefresh := ed.refresh(false, false) ed.out.WriteString("\n") ed.writer.ResetCurrentBuffer() ed.reader.Stop() // Restore termios. errRestore := ed.restoreTerminal() // Save the line before resetting all of editorState. line := ed.buffer ed.editorState = editorState{} callHooks(ed.evaler, ed.afterReadLine(), types.String(line)) return util.Errors(errRefresh, errRestore) } // ReadLine reads a line interactively. func (ed *Editor) ReadLine() (string, error) { err := ed.startReadLine() if err != nil { return "", err } defer func() { err := ed.finishReadLine() if err != nil { fmt.Fprintln(ed.out, "error:", err) } }() ed.mode = &ed.insert // Find external commands asynchronously, so that slow I/O won't block the // editor. isExternalCh := make(chan map[string]bool, 1) go getIsExternal(ed.evaler, isExternalCh) ed.reader.Start() fullRefresh := false callHooks(ed.evaler, ed.beforeReadLine()) promptUpdater := prompt.NewUpdater(prompt.Prompt) rpromptUpdater := prompt.NewUpdater(prompt.Rprompt) MainLoop: for { promptCh := promptUpdater.Update(ed) rpromptCh := rpromptUpdater.Update(ed) promptTimeout := prompt.MakeMaxWaitChan(ed) rpromptTimeout := prompt.MakeMaxWaitChan(ed) select { case ed.promptContent = <-promptCh: logger.Println("prompt fetched") case <-promptTimeout: logger.Println("stale prompt") ed.promptContent = promptUpdater.Staled } select { case ed.rpromptContent = <-rpromptCh: logger.Println("rprompt fetched") case <-rpromptTimeout: logger.Println("stale rprompt") ed.rpromptContent = rpromptUpdater.Staled } refresh: err := ed.refresh(fullRefresh, true) fullRefresh = false if err != nil { return "", err } ed.tips = nil select { case ed.promptContent = <-promptCh: logger.Println("prompt fetched late") goto refresh case ed.rpromptContent = <-rpromptCh: logger.Println("rprompt fetched late") goto refresh case m := <-isExternalCh: ed.isExternal = m case sig := <-ed.sigs: // TODO(xiaq): Maybe support customizable handling of signals switch sig { case syscall.SIGHUP: return "", io.EOF case syscall.SIGINT: // Start over ed.editorState = editorState{ restoreTerminal: ed.restoreTerminal, isExternal: ed.isExternal, } ed.mode = &ed.insert continue MainLoop case sys.SIGWINCH: fullRefresh = true continue MainLoop default: ed.addTip("ignored signal %s", sig) } case event := <-ed.reader.EventChan(): switch event := event.(type) { case tty.NonfatalErrorEvent: ed.Notify("error when reading terminal: %v", event.Err) case tty.FatalErrorEvent: ed.Notify("fatal error when reading terminal: %v", event.Err) return "", event.Err case tty.MouseEvent: ed.addTip("mouse: %+v", event) case tty.CursorPosition: // Ignore CPR case tty.PasteSetting: if !event { continue } var buf bytes.Buffer timer := time.NewTimer(tty.DefaultSeqTimeout) paste: for { // XXX Should also select on other chans. However those chans // will be unified (again) into one later so we don't do // busywork here. select { case event := <-ed.reader.EventChan(): switch event := event.(type) { case tty.KeyEvent: k := ui.Key(event) if k.Mod != 0 { ed.Notify("function key within paste, aborting") break paste } buf.WriteRune(k.Rune) timer.Reset(tty.DefaultSeqTimeout) case tty.PasteSetting: if !event { break paste } default: // Ignore other things. } case <-timer.C: ed.Notify("bracketed paste timeout") break paste } } topaste := buf.String() if ed.insert.quotePaste { topaste = parse.Quote(topaste) } ed.insertAtDot(topaste) case tty.RawRune: insertRaw(ed, rune(event)) case tty.KeyEvent: k := ui.Key(event) lookupKey: fn := ed.mode.Binding(ed.bindings, k) if fn == nil { ed.addTip("Unbound and no default binding: %s", k) continue MainLoop } ed.insert.insertedLiteral = false ed.lastKey = k ed.CallFn(fn) if ed.insert.insertedLiteral { ed.insert.literalInserts++ } else { ed.insert.literalInserts = 0 } switch ed.popAction() { case reprocessKey: err := ed.refresh(false, true) if err != nil { return "", err } goto lookupKey case commitLine: ed.appendHistory(ed.buffer) return ed.buffer, nil case commitEOF: return "", io.EOF } } } } } // getIsExternal finds a set of all external commands and puts it on the result // channel. func getIsExternal(ev *eval.Evaler, result chan<- map[string]bool) { isExternal := make(map[string]bool) eval.EachExternal(func(name string) { isExternal[name] = true }) result <- isExternal } elvish-0.11+ds1/edit/editor_unix_test.go000066400000000000000000000032131323000013700202400ustar00rootroot00000000000000// +build !windows,!plan9 package edit import ( "testing" "time" "github.com/elves/elvish/eval" "github.com/elves/elvish/sys" "github.com/kr/pty" ) var readLineTests = []struct { input string want string }{ {"\n", ""}, {"test\n", "test"}, {"abc\x7fd\n", "abd"}, {"abc\x17d\n", "d"}, } var readLineTimeout = 5 * time.Second func TestReadLine(t *testing.T) { master, tty, err := pty.Open() if err != nil { panic(err) } defer master.Close() defer tty.Close() // Continually consume tty outputs so that the editor is not blocked on // writing. var outputs []byte go func() { var buf [256]byte for { nr, err := master.Read(buf[:]) if err != nil { break } outputs = append(outputs, buf[:nr]...) } }() ev := eval.NewEvaler() // XXX: Needed for "use" to work. ev.SetLibDir("/non/exist/ent") defer ev.Close() for _, test := range readLineTests { lineChan := make(chan string) errChan := make(chan error) go func() { ed := NewEditor(tty, tty, nil, ev) defer ed.Close() line, err := ed.ReadLine() if err != nil { errChan <- err } else { lineChan <- line } }() _, err := master.WriteString(test.input) if err != nil { panic(err) } select { case line := <-lineChan: if line != test.want { t.Errorf("ReadLine() => %q, want %q (input %q)", line, test.want, test.input) } case err := <-errChan: t.Errorf("ReadLine() => error %v (input %q)", err, test.input) case <-time.After(readLineTimeout): t.Errorf("ReadLine() timed out (input %q)", test.input) t.Log("Stack trace: \n" + sys.DumpStack()) t.Logf("Terminal output: %q", outputs) t.FailNow() } } } elvish-0.11+ds1/edit/highlight.go000066400000000000000000000022441323000013700166220ustar00rootroot00000000000000package edit import ( "os" "github.com/elves/elvish/edit/highlight" "github.com/elves/elvish/eval" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" ) func doHighlight(n parse.Node, ed *Editor) { s := &highlight.Emitter{ func(s string) bool { return goodFormHead(s, ed) }, ed.styling.Add, } s.EmitAll(n) } func goodFormHead(head string, ed *Editor) bool { if eval.IsBuiltinSpecial[head] { return true } else if util.DontSearch(head) { // XXX don't stat twice return util.IsExecutable(head) || isDir(head) } else { ev := ed.evaler explode, ns, name := eval.ParseVariable(head) if !explode { switch ns { case "": if ev.Builtin[name+eval.FnSuffix] != nil || ev.Global[name+eval.FnSuffix] != nil { return true } case "e": if ed.isExternal[name] { return true } default: mod := ev.Global[ns+eval.NsSuffix] if mod == nil { mod = ev.Builtin[ns+eval.NsSuffix] } if mod != nil && mod.Get().(eval.Ns)[name+eval.FnSuffix] != nil { return true } } } return ed.isExternal[head] } } func isDir(fname string) bool { stat, err := os.Stat(fname) return err == nil && stat.IsDir() } elvish-0.11+ds1/edit/highlight/000077500000000000000000000000001323000013700162715ustar00rootroot00000000000000elvish-0.11+ds1/edit/highlight/emitter.go000066400000000000000000000056561323000013700203050ustar00rootroot00000000000000package highlight import ( "strings" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/parse" ) type Emitter struct { GoodFormHead func(string) bool AddStyling func(begin, end int, style string) } func (e *Emitter) EmitAll(n parse.Node) { switch n := n.(type) { case *parse.Form: e.form(n) case *parse.Primary: e.primary(n) case *parse.Sep: e.sep(n) } for _, child := range n.Children() { e.EmitAll(child) } } func (e *Emitter) form(n *parse.Form) { for _, an := range n.Assignments { if an.Left != nil && an.Left.Head != nil { v := an.Left.Head e.AddStyling(v.Begin(), v.End(), styleForGoodVariable.String()) } } for _, cn := range n.Vars { if len(cn.Indexings) > 0 && cn.Indexings[0].Head != nil { v := cn.Indexings[0].Head e.AddStyling(v.Begin(), v.End(), styleForGoodVariable.String()) } } if n.Head != nil { e.formHead(n.Head) // Special forms switch n.Head.SourceText() { case "if": for i := 2; i < len(n.Args); i += 2 { a := n.Args[i] argText := a.SourceText() if argText == "elif" || argText == "else" { e.AddStyling(a.Begin(), a.End(), styleForSep[argText]) } } case "for": if len(n.Args) >= 1 && len(n.Args[0].Indexings) > 0 { v := n.Args[0].Indexings[0].Head e.AddStyling(v.Begin(), v.End(), styleForGoodVariable.String()) } if len(n.Args) >= 4 && n.Args[3].SourceText() == "else" { a := n.Args[3] e.AddStyling(a.Begin(), a.End(), styleForSep["else"]) } case "try": i := 1 highlightKeyword := func(name string) bool { if i >= len(n.Args) { return false } a := n.Args[i] if a.SourceText() != name { return false } e.AddStyling(a.Begin(), a.End(), styleForSep[name]) return true } if highlightKeyword("except") { if i+1 < len(n.Args) && len(n.Args[i+1].Indexings) > 0 { v := n.Args[i+1].Indexings[0] e.AddStyling(v.Begin(), v.End(), styleForGoodVariable.String()) } i += 3 } if highlightKeyword("else") { i += 2 } highlightKeyword("finally") } // TODO(xiaq): Handle other special forms. } } func (e *Emitter) formHead(n *parse.Compound) { head, err := eval.PurelyEvalCompound(n) st := ui.Styles{} if err == nil { if e.GoodFormHead(head) { st = styleForGoodCommand } else { st = styleForBadCommand } } else if err != eval.ErrImpure { st = styleForBadCommand } if len(st) > 0 { e.AddStyling(n.Begin(), n.End(), st.String()) } } func (e *Emitter) primary(n *parse.Primary) { e.AddStyling(n.Begin(), n.End(), styleForPrimary[n.Type].String()) } func (e *Emitter) sep(n *parse.Sep) { septext := n.SourceText() switch { case strings.TrimSpace(septext) == "": // Don't do anything. Whitespaces don't get any styling. case strings.HasPrefix(septext, "#"): // Comment. e.AddStyling(n.Begin(), n.End(), styleForComment.String()) default: e.AddStyling(n.Begin(), n.End(), styleForSep[septext]) } } elvish-0.11+ds1/edit/highlight/emitter_test.go000066400000000000000000000076421323000013700213410ustar00rootroot00000000000000package highlight import ( "reflect" "strings" "testing" "github.com/elves/elvish/parse" ) type styling struct { begin int end int style string } type emitTests struct { source string wantStylings []styling } // In the test cases, commands that start with x are bad, everything else is // good. func goodFormHead(head string) bool { return !strings.HasPrefix(head, "x") } // This just tests the Highlight method itself, its dependencies are tested // below. var emitAllTests = []emitTests{ //01234 {"x 'y'", []styling{ {0, 1, styleForBadCommand.String()}, {0, 1, styleForPrimary[parse.Bareword].String()}, {2, 5, styleForPrimary[parse.SingleQuoted].String()}, }}, } func TestEmitAll(t *testing.T) { test(t, "form", emitAllTests, func(e *Emitter, ps *parse.Parser) { e.EmitAll(parse.ParseChunk(ps)) }) } var formTests = []emitTests{ // Temporary assignments. {"a=1 b=2", []styling{ {0, 1, styleForGoodVariable.String()}, {4, 5, styleForGoodVariable.String()}}}, // Normal assignments, {"a b = 1 2", []styling{ {0, 1, styleForGoodVariable.String()}, {2, 3, styleForGoodVariable.String()}}}, // Good commands. {"a", []styling{{0, 1, styleForGoodCommand.String()}}}, // Bad commands. {"xabc", []styling{{0, 4, styleForBadCommand.String()}}}, {"'xa'", []styling{{0, 4, styleForBadCommand.String()}}}, // "for". // Highlighting variable. //012345678901 {"for x [] { }", []styling{ {0, 3, styleForGoodCommand.String()}, {4, 5, styleForGoodVariable.String()}}}, // Highlighting variable, incomplete form. //01234 {"for x", []styling{ {0, 3, styleForGoodCommand.String()}, {4, 5, styleForGoodVariable.String()}}}, // Highlighting variable and "else". //012345678901234567890 {"for x [] { } else { }", []styling{ {0, 3, styleForGoodCommand.String()}, {4, 5, styleForGoodVariable.String()}, {13, 17, styleForSep["else"]}}}, // "try". // Highlighting except-variable. //01234567890123456789 {"try { } except x { }", []styling{ {0, 3, styleForGoodCommand.String()}, {8, 14, styleForSep["except"]}, {15, 16, styleForGoodVariable.String()}, }}, // Highlighting except-variable, incomplete form. //0123456789012345 {"try { } except x", []styling{ {0, 3, styleForGoodCommand.String()}, {8, 14, styleForSep["except"]}, {15, 16, styleForGoodVariable.String()}, }}, // Highlighting "else" and "finally". //0123456789012345678901234567 {"try { } else { } finally { }", []styling{ {0, 3, styleForGoodCommand.String()}, {8, 12, styleForSep["else"]}, {17, 24, styleForSep["finally"]}, }}, } func TestForm(t *testing.T) { test(t, "form", formTests, func(e *Emitter, ps *parse.Parser) { e.form(parse.ParseForm(ps)) }) } var primaryTests = []emitTests{ {"what", []styling{{0, 4, styleForPrimary[parse.Bareword].String()}}}, {"$var", []styling{{0, 4, styleForPrimary[parse.Variable].String()}}}, {"'a'", []styling{{0, 3, styleForPrimary[parse.SingleQuoted].String()}}}, {`"x"`, []styling{{0, 3, styleForPrimary[parse.DoubleQuoted].String()}}}, } func TestPrimary(t *testing.T) { test(t, "primary", primaryTests, func(e *Emitter, ps *parse.Parser) { e.primary(parse.ParsePrimary(ps, parse.NormalExpr)) }) } var sepTests = []emitTests{ {">", []styling{{0, 1, styleForSep[">"]}}}, {"# comment", []styling{{0, 9, styleForComment.String()}}}, } func TestSep(t *testing.T) { test(t, "sep", sepTests, func(e *Emitter, ps *parse.Parser) { src := ps.Source() e.sep(parse.NewSep(src, 0, len(src))) }) } func test(t *testing.T, what string, tests []emitTests, f func(*Emitter, *parse.Parser)) { for _, test := range tests { var stylings []styling e := &Emitter{goodFormHead, func(b, e int, s string) { stylings = append(stylings, styling{b, e, s}) }} ps := parse.NewParser("", test.source) f(e, ps) if !reflect.DeepEqual(stylings, test.wantStylings) { t.Errorf("%s %q gets stylings %v, want %v", what, test.source, stylings, test.wantStylings) } } } elvish-0.11+ds1/edit/highlight/highlight.go000066400000000000000000000001271323000013700205670ustar00rootroot00000000000000// Package highlight implements syntax highlighting for Elvish code. package highlight elvish-0.11+ds1/edit/highlight/style.go000066400000000000000000000022661323000013700177660ustar00rootroot00000000000000package highlight import ( "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/parse" ) // Semantically applied styles. var ( styleForGoodCommand = ui.Styles{"green"} styleForBadCommand = ui.Styles{"red"} styleForGoodVariable = ui.Styles{"magenta"} ) // Lexically applied styles. // ui.Styles for Primary nodes. var styleForPrimary = map[parse.PrimaryType]ui.Styles{ parse.Bareword: {}, parse.SingleQuoted: {"yellow"}, parse.DoubleQuoted: {"yellow"}, parse.Variable: styleForGoodVariable, parse.Wildcard: {}, parse.Tilde: {}, } var styleForComment = ui.Styles{"cyan"} // ui.Styles for Sep nodes. var styleForSep = map[string]string{ ">": "green", ">>": "green", "<": "green", "?>": "green", "|": "green", "?(": "bold", "(": "bold", ")": "bold", "[": "bold", "]": "bold", "{": "bold", "}": "bold", "&": "bold", "if": "yellow", "then": "yellow", "elif": "yellow", "else": "yellow", "fi": "yellow", "while": "yellow", "do": "yellow", "done": "yellow", "for": "yellow", "in": "yellow", "try": "yellow", "except": "yellow", "finally": "yellow", "tried": "yellow", "begin": "yellow", "end": "yellow", } elvish-0.11+ds1/edit/highlight/styling.go000066400000000000000000000030761323000013700203170ustar00rootroot00000000000000package highlight import ( "bytes" "sort" "github.com/elves/elvish/edit/ui" ) // Preparing and applying styling. type Styling struct { begins []stylingEvent ends []stylingEvent } func (s *Styling) Add(begin, end int, style string) { if style == "" { return } s.begins = append(s.begins, stylingEvent{begin, style}) s.ends = append(s.ends, stylingEvent{end, style}) } func (s *Styling) Apply() *StylingApplier { sort.Sort(stylingEvents(s.begins)) sort.Sort(stylingEvents(s.ends)) return &StylingApplier{s, make(map[string]int), 0, 0, ""} } type StylingApplier struct { *Styling occurrence map[string]int ibegin int iend int result string } func (a *StylingApplier) At(i int) { changed := false for a.iend < len(a.ends) && a.ends[a.iend].pos == i { a.occurrence[a.ends[a.iend].style]-- a.iend++ changed = true } for a.ibegin < len(a.begins) && a.begins[a.ibegin].pos == i { a.occurrence[a.begins[a.ibegin].style]++ a.ibegin++ changed = true } if changed { b := new(bytes.Buffer) for style, occ := range a.occurrence { if occ == 0 { continue } if b.Len() > 0 { b.WriteString(";") } b.WriteString(ui.TranslateStyle(style)) } a.result = b.String() } } func (a *StylingApplier) Get() string { return a.result } type stylingEvent struct { pos int style string } type stylingEvents []stylingEvent func (s stylingEvents) Len() int { return len(s) } func (s stylingEvents) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s stylingEvents) Less(i, j int) bool { return s[i].pos < s[j].pos } elvish-0.11+ds1/edit/histlist.go000066400000000000000000000055171323000013700165240ustar00rootroot00000000000000package edit import ( "errors" "fmt" "strconv" "strings" "github.com/elves/elvish/edit/ui" ) // Command history listing mode. var _ = registerBuiltins(modeHistoryListing, map[string]func(*Editor){ "start": histlistStart, "toggle-dedup": histlistToggleDedup, "toggle-case-sensitivity": histlistToggleCaseSensitivity, }) // ErrStoreOffline is thrown when an operation requires the storage backend, but // it is offline. var ErrStoreOffline = errors.New("store offline") type histlist struct { all []string dedup bool caseInsensitive bool last map[string]int shown []string index []int indexWidth int } func newHistlist(cmds []string) *listing { last := make(map[string]int) for i, entry := range cmds { last[entry] = i } hl := &histlist{ // This has to be here for the initialization to work :( all: cmds, dedup: true, last: last, indexWidth: len(strconv.Itoa(len(cmds) - 1)), } l := newListing(modeHistoryListing, hl) return &l } func (hl *histlist) ModeTitle(i int) string { s := " HISTORY " if hl.dedup { s += "(dedup on) " } if hl.caseInsensitive { s += "(case-insensitive) " } return s } func (*histlist) CursorOnModeLine() bool { return true } func (hl *histlist) Len() int { return len(hl.shown) } func (hl *histlist) Show(i int) (string, ui.Styled) { return fmt.Sprintf("%d", hl.index[i]), ui.Unstyled(hl.shown[i]) } func (hl *histlist) Filter(filter string) int { hl.shown = nil hl.index = nil dedup := hl.dedup if hl.caseInsensitive { filter = strings.ToLower(filter) } for i, entry := range hl.all { fentry := entry if hl.caseInsensitive { fentry = strings.ToLower(entry) } if (!dedup || hl.last[entry] == i) && strings.Contains(fentry, filter) { hl.index = append(hl.index, i) hl.shown = append(hl.shown, entry) } } // TODO: Maintain old selection return len(hl.shown) - 1 } // Editor interface. func (hl *histlist) Accept(i int, ed *Editor) { line := hl.shown[i] if len(ed.buffer) > 0 { line = "\n" + line } ed.insertAtDot(line) } func histlistStart(ed *Editor) { cmds, err := getCmds(ed) if err != nil { ed.Notify("%v", err) return } ed.mode = newHistlist(cmds) } func getCmds(ed *Editor) ([]string, error) { if ed.daemon == nil { return nil, ErrStoreOffline } return ed.historyFuser.AllCmds() } func histlistToggleDedup(ed *Editor) { if l, hl, ok := getHistlist(ed); ok { hl.dedup = !hl.dedup l.refresh() } } func histlistToggleCaseSensitivity(ed *Editor) { if l, hl, ok := getHistlist(ed); ok { hl.caseInsensitive = !hl.caseInsensitive l.refresh() } } func getHistlist(ed *Editor) (*listing, *histlist, bool) { if l, ok := ed.mode.(*listing); ok { if hl, ok := l.provider.(*histlist); ok { return l, hl, true } } return nil, nil, false } elvish-0.11+ds1/edit/histlist_test.go000066400000000000000000000016061323000013700175560ustar00rootroot00000000000000package edit import ( "testing" "github.com/elves/elvish/edit/ui" ) var ( theHistList = newHistlist([]string{"ls", "echo lalala", "ls"}) histlistDedupFilterTests = []listingFilterTestCases{ {"", []shown{ {"1", ui.Unstyled("echo lalala")}, {"2", ui.Unstyled("ls")}}}, {"l", []shown{ {"1", ui.Unstyled("echo lalala")}, {"2", ui.Unstyled("ls")}}}, } histlistNoDedupFilterTests = []listingFilterTestCases{ {"", []shown{ {"0", ui.Unstyled("ls")}, {"1", ui.Unstyled("echo lalala")}, {"2", ui.Unstyled("ls")}}}, {"l", []shown{ {"0", ui.Unstyled("ls")}, {"1", ui.Unstyled("echo lalala")}, {"2", ui.Unstyled("ls")}}}, } ) func TestHistlist(t *testing.T) { testListingFilter(t, "theHistList", theHistList, histlistDedupFilterTests) theHistList.provider.(*histlist).dedup = false testListingFilter(t, "theHistList", theHistList, histlistNoDedupFilterTests) } elvish-0.11+ds1/edit/history.go000066400000000000000000000051211323000013700163510ustar00rootroot00000000000000package edit import ( "errors" "fmt" "strings" "github.com/elves/elvish/edit/history" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/vartypes" ) // Command history mode. var _ = registerBuiltins("history", map[string]func(*Editor){ "start": historyStart, "up": wrapHistoryBuiltin(historyUp), "down": wrapHistoryBuiltin(historyDown), "down-or-quit": wrapHistoryBuiltin(historyDownOrQuit), "switch-to-histlist": wrapHistoryBuiltin(historySwitchToHistlist), "default": wrapHistoryBuiltin(historyDefault), }) type hist struct { *history.Walker } func (*hist) Binding(m map[string]vartypes.Variable, k ui.Key) eval.Fn { return getBinding(m[modeHistory], k) } func (h *hist) ModeLine() ui.Renderer { return modeLineRenderer{fmt.Sprintf(" HISTORY #%d ", h.CurrentSeq()), ""} } func historyStart(ed *Editor) { if ed.historyFuser == nil { ed.Notify("history offline") return } prefix := ed.buffer[:ed.dot] walker := ed.historyFuser.Walker(prefix) hist := hist{walker} _, _, err := hist.Prev() if err == nil { ed.mode = &hist } else { ed.addTip("no matching history item") } } var errNotHistory = errors.New("not in history mode") func wrapHistoryBuiltin(f func(*Editor, *hist)) func(*Editor) { return func(ed *Editor) { hist, ok := ed.mode.(*hist) if !ok { throw(errNotHistory) } f(ed, hist) } } func historyUp(ed *Editor, hist *hist) { _, _, err := hist.Prev() if err != nil { ed.Notify("%s", err) } } func historyDown(ed *Editor, hist *hist) { _, _, err := hist.Next() if err != nil { ed.Notify("%s", err) } } func historyDownOrQuit(ed *Editor, hist *hist) { _, _, err := hist.Next() if err != nil { ed.mode = &ed.insert } } func historySwitchToHistlist(ed *Editor, hist *hist) { histlistStart(ed) if l, _, ok := getHistlist(ed); ok { ed.buffer = "" ed.dot = 0 l.changeFilter(hist.Prefix()) } } func historyDefault(ed *Editor, hist *hist) { ed.buffer = hist.CurrentCmd() ed.dot = len(ed.buffer) ed.mode = &ed.insert ed.setAction(reprocessKey) } func (ed *Editor) appendHistory(line string) { // TODO: should have a user variable to control the behavior // Do not add command leading by space into history. This is // useful for confidential operations. if strings.HasPrefix(line, " ") { return } if ed.daemon != nil && ed.historyFuser != nil { ed.historyMutex.Lock() go func() { err := ed.historyFuser.AddCmd(line) ed.historyMutex.Unlock() if err != nil { logger.Printf("Failed to AddCmd %q: %v", line, err) } }() } } elvish-0.11+ds1/edit/history/000077500000000000000000000000001323000013700160235ustar00rootroot00000000000000elvish-0.11+ds1/edit/history/fuser.go000066400000000000000000000021141323000013700174740ustar00rootroot00000000000000package history import ( "sync" ) // Fuser provides a unified view into a shared storage-backed command history // and per-session history. type Fuser struct { store Store storeUpper int *sync.RWMutex // Per-session history. cmds []string seqs []int } func NewFuser(store Store) (*Fuser, error) { upper, err := store.NextCmdSeq() if err != nil { return nil, err } return &Fuser{ store: store, storeUpper: upper, RWMutex: &sync.RWMutex{}, }, nil } func (f *Fuser) AddCmd(cmd string) error { f.Lock() defer f.Unlock() seq, err := f.store.AddCmd(cmd) if err != nil { return err } f.cmds = append(f.cmds, cmd) f.seqs = append(f.seqs, seq) return nil } func (f *Fuser) AllCmds() ([]string, error) { f.RLock() defer f.RUnlock() cmds, err := f.store.Cmds(0, f.storeUpper) if err != nil { return nil, err } return append(cmds, f.cmds...), nil } func (f *Fuser) SessionCmds() []string { return f.cmds } func (f *Fuser) Walker(prefix string) *Walker { f.RLock() defer f.RUnlock() return NewWalker(f.store, f.storeUpper, f.cmds, f.seqs, prefix) } elvish-0.11+ds1/edit/history/fuser_test.go000066400000000000000000000040461323000013700205410ustar00rootroot00000000000000package history import ( "errors" "reflect" "testing" ) func TestNewFuser(t *testing.T) { mockError := errors.New("mock error") _, err := NewFuser(&mockStore{oneOffError: mockError}) if err != mockError { t.Errorf("NewFuser -> error %v, want %v", err, mockError) } } var fuserStore = &mockStore{cmds: []string{"store 1"}} func TestFuser(t *testing.T) { f, err := NewFuser(fuserStore) if err != nil { t.Errorf("NewFuser -> error %v, want nil", err) } // AddCmd should not add command to session history if backend has an error // adding the command. mockError := errors.New("mock error") fuserStore.oneOffError = mockError err = f.AddCmd("haha") if err != mockError { t.Errorf("AddCmd doesn't forward backend error") } if len(f.cmds) != 0 { t.Errorf("AddCmd adds command to session history when backend errors") } // AddCmd should add command to both storage and session f.AddCmd("session 1") if !reflect.DeepEqual(fuserStore.cmds, []string{"store 1", "session 1"}) { t.Errorf("AddCmd doesn't add command to backend storage") } if !reflect.DeepEqual(f.SessionCmds(), []string{"session 1"}) { t.Errorf("AddCmd doesn't add command to session history") } // AllCmds should return all commands from the storage when the Fuser was // created followed by session commands fuserStore.AddCmd("other session 1") fuserStore.AddCmd("other session 2") f.AddCmd("session 2") cmds, err := f.AllCmds() if err != nil { t.Errorf("AllCmds returns error") } if !reflect.DeepEqual(cmds, []string{"store 1", "session 1", "session 2"}) { t.Errorf("AllCmds doesn't return all commands") } // AllCmds should forward backend storage error mockError = errors.New("another mock error") fuserStore.oneOffError = mockError _, err = f.AllCmds() if err != mockError { t.Errorf("AllCmds doesn't forward backend error") } // Walker should return a walker that walks through all commands w := f.Walker("") wantCmd(t, w.Prev, 4, "session 2") wantCmd(t, w.Prev, 1, "session 1") wantCmd(t, w.Prev, 0, "store 1") wantErr(t, w.Prev, ErrEndOfHistory) } elvish-0.11+ds1/edit/history/history.go000066400000000000000000000001171323000013700200520ustar00rootroot00000000000000// Package history provides utilities for the command history. package history elvish-0.11+ds1/edit/history/list.go000066400000000000000000000031571323000013700173330ustar00rootroot00000000000000package history import ( "sync" "github.com/elves/elvish/daemon" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/util" ) // List is a list-like value that provides access to history in the API // backend. It is used in the $edit:history variable. type List struct { *sync.RWMutex Daemon *daemon.Client } var ( _ types.Value = List{} _ types.ListLike = List{} ) func (hv List) Kind() string { return "list" } // Equal returns true as long as the rhs is also of type History. func (hv List) Equal(a interface{}) bool { _, ok := a.(List) return ok } func (hv List) Hash() uint32 { // TODO(xiaq): Make a global registry of singleton hashes to avoid // collision. return 100 } func (hv List) Repr(int) string { return "$edit:history" } func (hv List) Len() int { hv.RLock() defer hv.RUnlock() nextseq, err := hv.Daemon.NextCmdSeq() maybeThrow(err) return nextseq - 1 } func (hv List) Iterate(f func(types.Value) bool) { hv.RLock() defer hv.RUnlock() n := hv.Len() cmds, err := hv.Daemon.Cmds(1, n+1) maybeThrow(err) for _, cmd := range cmds { if !f(types.String(cmd)) { break } } } func (hv List) IndexOne(idx types.Value) types.Value { hv.RLock() defer hv.RUnlock() slice, i, j := types.ParseAndFixListIndex(types.ToString(idx), hv.Len()) if slice { cmds, err := hv.Daemon.Cmds(i+1, j+1) maybeThrow(err) vs := make([]types.Value, len(cmds)) for i := range cmds { vs[i] = types.String(cmds[i]) } return types.MakeList(vs...) } s, err := hv.Daemon.Cmd(i + 1) maybeThrow(err) return types.String(s) } func maybeThrow(e error) { if e != nil { util.Throw(e) } } elvish-0.11+ds1/edit/history/store.go000066400000000000000000000003671323000013700175140ustar00rootroot00000000000000package history // Store is the interface of the storage backend. type Store interface { NextCmdSeq() (int, error) AddCmd(cmd string) (int, error) Cmds(from, upto int) ([]string, error) PrevCmd(upto int, prefix string) (int, string, error) } elvish-0.11+ds1/edit/history/store_test.go000066400000000000000000000017161323000013700205520ustar00rootroot00000000000000package history import "strings" // mockStore is an implementation of the Store interface that can be used for // testing. type mockStore struct { cmds []string oneOffError error } func (s *mockStore) error() error { err := s.oneOffError s.oneOffError = nil return err } func (s *mockStore) NextCmdSeq() (int, error) { return len(s.cmds), s.error() } func (s *mockStore) AddCmd(cmd string) (int, error) { if s.oneOffError != nil { return -1, s.error() } s.cmds = append(s.cmds, cmd) return len(s.cmds) - 1, nil } func (s *mockStore) Cmds(from, upto int) ([]string, error) { return s.cmds[from:upto], s.error() } func (s *mockStore) PrevCmd(upto int, prefix string) (int, string, error) { if s.oneOffError != nil { return -1, "", s.error() } if upto < 0 || upto > len(s.cmds) { upto = len(s.cmds) } for i := upto - 1; i >= 0; i-- { if strings.HasPrefix(s.cmds[i], prefix) { return i, s.cmds[i], nil } } return -1, "", ErrEndOfHistory } elvish-0.11+ds1/edit/history/walker.go000066400000000000000000000054741323000013700176510ustar00rootroot00000000000000package history import ( "errors" "strings" "github.com/elves/elvish/store/storedefs" ) var ErrEndOfHistory = errors.New("end of history") // Walker is used for walking through history entries with a given (possibly // empty) prefix, skipping duplicates entries. type Walker struct { store Store storeUpper int sessionCmds []string sessionSeqs []int prefix string // The next element to fetch from the session history. If equal to -1, the // next element comes from the storage backend. sessionIdx int // Index of the next element in the stack that Prev will return on next // call. If equal to len(stack), the next element needs to be fetched, // either from the session history or the storage backend. top int stack []string seq []int inStack map[string]bool } func NewWalker(store Store, upper int, cmds []string, seqs []int, prefix string) *Walker { return &Walker{store, upper, cmds, seqs, prefix, len(cmds) - 1, 0, nil, nil, map[string]bool{}} } // Prefix returns the prefix of the commands that the walker walks through. func (w *Walker) Prefix() string { return w.prefix } // CurrentSeq returns the sequence number of the current entry. func (w *Walker) CurrentSeq() int { if len(w.seq) > 0 && w.top <= len(w.seq) && w.top > 0 { return w.seq[w.top-1] } return -1 } // CurrentSeq returns the content of the current entry. func (w *Walker) CurrentCmd() string { if len(w.stack) > 0 && w.top <= len(w.stack) && w.top > 0 { return w.stack[w.top-1] } return "" } // Prev walks to the previous matching history entry, skipping all duplicates. func (w *Walker) Prev() (int, string, error) { // Entry comes from the stack. if w.top < len(w.stack) { i := w.top w.top++ return w.seq[i], w.stack[i], nil } // Find the entry in the session part. for i := w.sessionIdx; i >= 0; i-- { seq := w.sessionSeqs[i] cmd := w.sessionCmds[i] if strings.HasPrefix(cmd, w.prefix) && !w.inStack[cmd] { w.push(cmd, seq) w.sessionIdx = i - 1 return seq, cmd, nil } } // Not found in the session part. w.sessionIdx = -1 seq := w.storeUpper if len(w.seq) > 0 && seq > w.seq[len(w.seq)-1] { seq = w.seq[len(w.seq)-1] } for { var ( cmd string err error ) seq, cmd, err = w.store.PrevCmd(seq, w.prefix) if err != nil { if err.Error() == storedefs.ErrNoMatchingCmd.Error() { err = ErrEndOfHistory } return -1, "", err } if !w.inStack[cmd] { w.push(cmd, seq) return seq, cmd, nil } } } func (w *Walker) push(cmd string, seq int) { w.inStack[cmd] = true w.stack = append(w.stack, cmd) w.seq = append(w.seq, seq) w.top++ } // Next reverses Prev. func (w *Walker) Next() (int, string, error) { if w.top <= 0 { return -1, "", ErrEndOfHistory } w.top-- if w.top == 0 { return -1, "", ErrEndOfHistory } return w.seq[w.top-1], w.stack[w.top-1], nil } elvish-0.11+ds1/edit/history/walker_test.go000066400000000000000000000060351323000013700207020ustar00rootroot00000000000000package history import ( "errors" "testing" "github.com/elves/elvish/store/storedefs" ) func TestWalker(t *testing.T) { mockError := errors.New("mock error") walkerStore := &mockStore{ // 0 1 2 3 4 5 cmds: []string{"echo", "ls -l", "echo a", "ls -a", "echo a", "ls a"}, } var w *Walker // Going back and forth. w = NewWalker(walkerStore, -1, nil, nil, "") wantCurrent(t, w, -1, "") wantCmd(t, w.Prev, 5, "ls a") wantCurrent(t, w, 5, "ls a") wantErr(t, w.Next, ErrEndOfHistory) wantErr(t, w.Next, ErrEndOfHistory) wantCmd(t, w.Prev, 5, "ls a") wantCmd(t, w.Prev, 4, "echo a") wantCmd(t, w.Next, 5, "ls a") wantCmd(t, w.Prev, 4, "echo a") wantCmd(t, w.Prev, 3, "ls -a") // "echo a" should be skipped wantCmd(t, w.Prev, 1, "ls -l") wantCmd(t, w.Prev, 0, "echo") wantErr(t, w.Prev, ErrEndOfHistory) // With an upper bound on the storage. w = NewWalker(walkerStore, 2, nil, nil, "") wantCmd(t, w.Prev, 1, "ls -l") wantCmd(t, w.Prev, 0, "echo") wantErr(t, w.Prev, ErrEndOfHistory) // Prefix matching 1. w = NewWalker(walkerStore, -1, nil, nil, "echo") if w.Prefix() != "echo" { t.Errorf("got prefix %q, want %q", w.Prefix(), "echo") } wantCmd(t, w.Prev, 4, "echo a") wantCmd(t, w.Prev, 0, "echo") wantErr(t, w.Prev, ErrEndOfHistory) // Prefix matching 2. w = NewWalker(walkerStore, -1, nil, nil, "ls") wantCmd(t, w.Prev, 5, "ls a") wantCmd(t, w.Prev, 3, "ls -a") wantCmd(t, w.Prev, 1, "ls -l") wantErr(t, w.Prev, ErrEndOfHistory) // Walker with session history. w = NewWalker(walkerStore, -1, []string{"ls -l", "ls -v", "echo haha"}, []int{7, 10, 12}, "ls") wantCmd(t, w.Prev, 10, "ls -v") wantCmd(t, w.Prev, 7, "ls -l") wantCmd(t, w.Next, 10, "ls -v") wantCmd(t, w.Prev, 7, "ls -l") wantCmd(t, w.Prev, 5, "ls a") wantCmd(t, w.Next, 7, "ls -l") wantCmd(t, w.Prev, 5, "ls a") wantCmd(t, w.Prev, 3, "ls -a") wantErr(t, w.Prev, ErrEndOfHistory) // Backend error. w = NewWalker(walkerStore, -1, nil, nil, "") wantCmd(t, w.Prev, 5, "ls a") wantCmd(t, w.Prev, 4, "echo a") walkerStore.oneOffError = mockError wantErr(t, w.Prev, mockError) // storedefs.ErrNoMatchingCmd is turned into ErrEndOfHistory. w = NewWalker(walkerStore, -1, nil, nil, "") walkerStore.oneOffError = storedefs.ErrNoMatchingCmd wantErr(t, w.Prev, ErrEndOfHistory) } func wantCurrent(t *testing.T, w *Walker, wantSeq int, wantCmd string) { seq, cmd := w.CurrentSeq(), w.CurrentCmd() if seq != wantSeq { t.Errorf("got seq %d, want %d", seq, wantSeq) } if cmd != wantCmd { t.Errorf("got cmd %q, want %q", cmd, wantCmd) } } func wantCmd(t *testing.T, f func() (int, string, error), wantSeq int, wantCmd string) { seq, cmd, err := f() if seq != wantSeq { t.Errorf("got seq %d, want %d", seq, wantSeq) } if cmd != wantCmd { t.Errorf("got cmd %q, want %q", cmd, wantCmd) } if err != nil { t.Errorf("got err %v, want nil", err) } } func wantErr(t *testing.T, f func() (int, string, error), want error) { _, _, err := f() if err != want { t.Errorf("got err %v, want %v", err, want) } } elvish-0.11+ds1/edit/hooks.go000066400000000000000000000023731323000013700160010ustar00rootroot00000000000000package edit import ( "fmt" "os" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" ) // The $le:{before,after}-readline lists that contain hooks. We might have more // hooks in future. var _ = RegisterVariable("before-readline", makeListVariable) func (ed *Editor) beforeReadLine() types.List { return ed.variables["before-readline"].Get().(types.List) } var _ = RegisterVariable("after-readline", makeListVariable) func (ed *Editor) afterReadLine() types.List { return ed.variables["after-readline"].Get().(types.List) } func makeListVariable() vartypes.Variable { return vartypes.NewValidatedPtr(types.EmptyList, vartypes.ShouldBeList) } func callHooks(ev *eval.Evaler, li types.List, args ...types.Value) { if li.Len() == 0 { return } li.Iterate(func(v types.Value) bool { opfunc := func(ec *eval.Frame) { fn, ok := v.(eval.Fn) if !ok { fmt.Fprintf(os.Stderr, "not a function: %s\n", v.Repr(types.NoPretty)) return } err := ec.PCall(fn, args, eval.NoOpts) if err != nil { // TODO Print stack trace. fmt.Fprintf(os.Stderr, "function error: %s\n", err.Error()) } } ev.Eval(eval.Op{opfunc, -1, -1}, eval.NewInternalSource("[hooks]")) return true }) } elvish-0.11+ds1/edit/insert.go000066400000000000000000000176441323000013700161710ustar00rootroot00000000000000package edit import ( "strings" "unicode" "unicode/utf8" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/util" ) // Builtins related to insert and command mode. var ( _ = registerBuiltins("", map[string]func(*Editor){ "kill-line-left": killLineLeft, "kill-line-right": killLineRight, "kill-word-left": killWordLeft, "kill-small-word-left": killSmallWordLeft, "kill-rune-left": killRuneLeft, "kill-rune-right": killRuneRight, "move-dot-left": moveDotLeft, "move-dot-right": moveDotRight, "move-dot-left-word": moveDotLeftWord, "move-dot-right-word": moveDotRightWord, "move-dot-sol": moveDotSOL, "move-dot-eol": moveDotEOL, "move-dot-up": moveDotUp, "move-dot-down": moveDotDown, "insert-last-word": insertLastWord, "insert-key": insertKey, "return-line": returnLine, "smart-enter": smartEnter, "return-eof": returnEOF, "toggle-quote-paste": toggleQuotePaste, "insert-raw": startInsertRaw, "end-of-history": endOfHistory, "redraw": redraw, }) _ = registerBuiltins("insert", map[string]func(*Editor){ "start": insertStart, "default": insertDefault, }) _ = registerBuiltins("command", map[string]func(*Editor){ "start": commandStart, "default": commandDefault, }) ) type insert struct { quotePaste bool // The number of consecutive key inserts. Used for abbreviation expansion. literalInserts int // Indicates whether a key was inserted (via insert-default). A hack for // maintaining the inserts field. insertedLiteral bool } // ui.Insert mode is the default mode and has an empty mode. func (ins *insert) ModeLine() ui.Renderer { if ins.quotePaste { return modeLineRenderer{" INSERT (quote paste) ", ""} } return nil } func (*insert) Binding(m map[string]vartypes.Variable, k ui.Key) eval.Fn { return getBinding(m[modeInsert], k) } type command struct{} func (*command) ModeLine() ui.Renderer { return modeLineRenderer{" COMMAND ", ""} } func (*command) Binding(m map[string]vartypes.Variable, k ui.Key) eval.Fn { return getBinding(m[modeCommand], k) } func insertStart(ed *Editor) { ed.mode = &ed.insert } func commandStart(ed *Editor) { ed.mode = &ed.command } func killLineLeft(ed *Editor) { sol := util.FindLastSOL(ed.buffer[:ed.dot]) ed.buffer = ed.buffer[:sol] + ed.buffer[ed.dot:] ed.dot = sol } func killLineRight(ed *Editor) { eol := util.FindFirstEOL(ed.buffer[ed.dot:]) + ed.dot ed.buffer = ed.buffer[:ed.dot] + ed.buffer[eol:] } // NOTE(xiaq): A word is a run of non-space runes. When killing a word, // trimming spaces are removed as well. Examples: // "abc xyz" -> "abc ", "abc xyz " -> "abc ". func killWordLeft(ed *Editor) { if ed.dot == 0 { return } space := strings.LastIndexFunc( strings.TrimRightFunc(ed.buffer[:ed.dot], unicode.IsSpace), unicode.IsSpace) + 1 ed.buffer = ed.buffer[:space] + ed.buffer[ed.dot:] ed.dot = space } // NOTE(xiaq): A small word is either a run of alphanumeric (Unicode category L // or N) runes or a run of non-alphanumeric runes. This is consistent with vi's // definition of word, except that "_" is not considered alphanumeric. When // killing a small word, trimming spaces are removed as well. Examples: // "abc/~" -> "abc", "~/abc" -> "~/", "abc* " -> "abc" func killSmallWordLeft(ed *Editor) { left := strings.TrimRightFunc(ed.buffer[:ed.dot], unicode.IsSpace) // The case of left == "" is handled as well. r, _ := utf8.DecodeLastRuneInString(left) if isAlnum(r) { left = strings.TrimRightFunc(left, isAlnum) } else { left = strings.TrimRightFunc( left, func(r rune) bool { return !isAlnum(r) }) } ed.buffer = left + ed.buffer[ed.dot:] ed.dot = len(left) } func isAlnum(r rune) bool { return unicode.IsLetter(r) || unicode.IsNumber(r) } func killRuneLeft(ed *Editor) { if ed.dot > 0 { _, w := utf8.DecodeLastRuneInString(ed.buffer[:ed.dot]) ed.buffer = ed.buffer[:ed.dot-w] + ed.buffer[ed.dot:] ed.dot -= w } else { ed.flash() } } func killRuneRight(ed *Editor) { if ed.dot < len(ed.buffer) { _, w := utf8.DecodeRuneInString(ed.buffer[ed.dot:]) ed.buffer = ed.buffer[:ed.dot] + ed.buffer[ed.dot+w:] } else { ed.flash() } } func moveDotLeft(ed *Editor) { _, w := utf8.DecodeLastRuneInString(ed.buffer[:ed.dot]) ed.dot -= w } func moveDotRight(ed *Editor) { _, w := utf8.DecodeRuneInString(ed.buffer[ed.dot:]) ed.dot += w } func moveDotLeftWord(ed *Editor) { if ed.dot == 0 { return } space := strings.LastIndexFunc( strings.TrimRightFunc(ed.buffer[:ed.dot], unicode.IsSpace), unicode.IsSpace) + 1 ed.dot = space } func moveDotRightWord(ed *Editor) { // Move to first space p := strings.IndexFunc(ed.buffer[ed.dot:], unicode.IsSpace) if p == -1 { ed.dot = len(ed.buffer) return } ed.dot += p // Move to first nonspace p = strings.IndexFunc(ed.buffer[ed.dot:], notSpace) if p == -1 { ed.dot = len(ed.buffer) return } ed.dot += p } func notSpace(r rune) bool { return !unicode.IsSpace(r) } func moveDotSOL(ed *Editor) { sol := util.FindLastSOL(ed.buffer[:ed.dot]) ed.dot = sol } func moveDotEOL(ed *Editor) { eol := util.FindFirstEOL(ed.buffer[ed.dot:]) + ed.dot ed.dot = eol } func moveDotUp(ed *Editor) { sol := util.FindLastSOL(ed.buffer[:ed.dot]) if sol == 0 { ed.flash() return } prevEOL := sol - 1 prevSOL := util.FindLastSOL(ed.buffer[:prevEOL]) width := util.Wcswidth(ed.buffer[sol:ed.dot]) ed.dot = prevSOL + len(util.TrimWcwidth(ed.buffer[prevSOL:prevEOL], width)) } func moveDotDown(ed *Editor) { eol := util.FindFirstEOL(ed.buffer[ed.dot:]) + ed.dot if eol == len(ed.buffer) { ed.flash() return } nextSOL := eol + 1 nextEOL := util.FindFirstEOL(ed.buffer[nextSOL:]) + nextSOL sol := util.FindLastSOL(ed.buffer[:ed.dot]) width := util.Wcswidth(ed.buffer[sol:ed.dot]) ed.dot = nextSOL + len(util.TrimWcwidth(ed.buffer[nextSOL:nextEOL], width)) } func insertLastWord(ed *Editor) { if ed.daemon == nil { ed.addTip("daemon offline") return } _, cmd, err := ed.daemon.PrevCmd(-1, "") if err == nil { ed.insertAtDot(lastWord(cmd)) } else { ed.addTip("db error: %s", err.Error()) } } func lastWord(s string) string { words := wordify(s) if len(words) == 0 { return "" } return words[len(words)-1] } func insertKey(ed *Editor) { k := ed.lastKey ed.insertAtDot(string(k.Rune)) } func returnLine(ed *Editor) { ed.setAction(commitLine) } func smartEnter(ed *Editor) { if ed.parseErrorAtEnd { // There is a parsing error at the end. ui.Insert a newline and copy // indents from previous line. indent := findLastIndent(ed.buffer[:ed.dot]) ed.insertAtDot("\n" + indent) } else { returnLine(ed) } } func findLastIndent(s string) string { line := s[util.FindLastSOL(s):] trimmed := strings.TrimLeft(line, " \t") return line[:len(line)-len(trimmed)] } func returnEOF(ed *Editor) { if len(ed.buffer) == 0 { ed.setAction(commitEOF) } } func toggleQuotePaste(ed *Editor) { ed.insert.quotePaste = !ed.insert.quotePaste } func endOfHistory(ed *Editor) { ed.Notify("End of history") } func redraw(ed *Editor) { ed.refresh(true, true) } func insertDefault(ed *Editor) { k := ed.lastKey if likeChar(k) { insertKey(ed) // Match abbreviations. expanded := false literals := ed.buffer[ed.dot-ed.insert.literalInserts-1 : ed.dot] ed.abbrIterate(func(abbr, full string) bool { if strings.HasSuffix(literals, abbr) { ed.buffer = ed.buffer[:ed.dot-len(abbr)] + full + ed.buffer[ed.dot:] ed.dot += len(full) - len(abbr) expanded = true return false } return true }) // No match. if !expanded { ed.insert.insertedLiteral = true } } else { ed.Notify("Unbound: %s", k) } } // likeChar returns if a key looks like a character meant to be input (as // opposed to a function key). func likeChar(k ui.Key) bool { return k.Mod == 0 && k.Rune > 0 && unicode.IsGraphic(k.Rune) } func commandDefault(ed *Editor) { k := ed.lastKey ed.Notify("Unbound: %s", k) } elvish-0.11+ds1/edit/lastcmd.go000066400000000000000000000047201323000013700163030ustar00rootroot00000000000000package edit import ( "fmt" "strconv" "strings" "github.com/elves/elvish/edit/ui" ) // LastCmd mode. var _ = registerBuiltins(modeLastCmd, map[string]func(*Editor){ "start": lastcmdStart, "alt-default": lastcmdAltDefault, }) type lastcmdEntry struct { i int s string } type lastcmd struct { line string words []string filtered []lastcmdEntry minus bool } func newLastCmd(line string) *listing { b := &lastcmd{line, wordify(line), nil, false} l := newListing(modeLastCmd, b) return &l } func (b *lastcmd) ModeTitle(int) string { return " LASTCMD " } func (b *lastcmd) Len() int { return len(b.filtered) } func (b *lastcmd) Show(i int) (string, ui.Styled) { entry := b.filtered[i] var head string if entry.i == -1 { head = "M-1" } else if b.minus { head = fmt.Sprintf("%d", entry.i-len(b.words)) } else { head = fmt.Sprintf("%d", entry.i) } return head, ui.Unstyled(entry.s) } func (b *lastcmd) Filter(filter string) int { b.filtered = nil b.minus = len(filter) > 0 && filter[0] == '-' if filter == "" || filter == "-" { b.filtered = append(b.filtered, lastcmdEntry{-1, b.line}) } else if _, err := strconv.Atoi(filter); err != nil { return -1 } // Quite inefficient way to filter by prefix of stringified index. n := len(b.words) for i, word := range b.words { if filter == "" || (!b.minus && strings.HasPrefix(strconv.Itoa(i), filter)) || (b.minus && strings.HasPrefix(strconv.Itoa(i-n), filter)) { b.filtered = append(b.filtered, lastcmdEntry{i, word}) } } if len(b.filtered) == 0 { return -1 } return 0 } // Editor interface. func (b *lastcmd) Accept(i int, ed *Editor) { ed.insertAtDot(b.filtered[i].s) insertStart(ed) } func lastcmdStart(ed *Editor) { logger.Println("lastcmd-alt-start") _, cmd, err := ed.daemon.PrevCmd(-1, "") if err != nil { ed.Notify("db error: %s", err.Error()) return } ed.mode = newLastCmd(cmd) } func lastcmdAltDefault(ed *Editor) { l, lc := getLastcmd(ed) if l == nil { return } logger.Println("lastcmd-alt-default") if ed.lastKey == (ui.Key{'1', ui.Alt}) { lc.Accept(0, ed) logger.Println("accepting") } else if l.handleFilterKey(ed.lastKey) { if lc.Len() == 1 { lc.Accept(l.selected, ed) logger.Println("accepting") } } else { insertStart(ed) ed.setAction(reprocessKey) } } func getLastcmd(ed *Editor) (*listing, *lastcmd) { if l, ok := ed.mode.(*listing); ok { if lc, ok := l.provider.(*lastcmd); ok { return l, lc } } return nil, nil } elvish-0.11+ds1/edit/lastcmd_test.go000066400000000000000000000013241323000013700173370ustar00rootroot00000000000000package edit import ( "testing" "github.com/elves/elvish/edit/ui" ) var ( theLine = "qw search 'foo bar ~y'" theLastCmd = newLastCmd(theLine) lastcmdFilterTests = []listingFilterTestCases{ {"", []shown{ {"M-1", ui.Unstyled(theLine)}, {"0", ui.Unstyled("qw")}, {"1", ui.Unstyled("search")}, {"2", ui.Unstyled("'foo bar ~y'")}}}, {"1", []shown{{"1", ui.Unstyled("search")}}}, {"-", []shown{ {"M-1", ui.Unstyled(theLine)}, {"-3", ui.Unstyled("qw")}, {"-2", ui.Unstyled("search")}, {"-1", ui.Unstyled("'foo bar ~y'")}}}, {"-1", []shown{{"-1", ui.Unstyled("'foo bar ~y'")}}}, } ) func TestLastCmd(t *testing.T) { testListingFilter(t, "theLastCmd", theLastCmd, lastcmdFilterTests) } elvish-0.11+ds1/edit/listing.go000066400000000000000000000167371323000013700163400ustar00rootroot00000000000000package edit import ( "container/list" "errors" "fmt" "strings" "unicode/utf8" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/util" ) var _ = registerBuiltins(modeListing, map[string]func(*Editor){ "up": func(ed *Editor) { getListing(ed).up(false) }, "up-cycle": func(ed *Editor) { getListing(ed).up(true) }, "page-up": func(ed *Editor) { getListing(ed).pageUp() }, "down": func(ed *Editor) { getListing(ed).down(false) }, "down-cycle": func(ed *Editor) { getListing(ed).down(true) }, "page-down": func(ed *Editor) { getListing(ed).pageDown() }, "backspace": func(ed *Editor) { getListing(ed).backspace() }, "accept": func(ed *Editor) { getListing(ed).accept(ed) }, "accept-close": func(ed *Editor) { getListing(ed).accept(ed) insertStart(ed) }, "default": func(ed *Editor) { getListing(ed).defaultBinding(ed) }, }) // listing implements a listing mode that supports the notion of selecting an // entry and filtering entries. type listing struct { name string provider listingProvider selected int filter string pagesize int headerWidth int } type listingProvider interface { Len() int Show(i int) (string, ui.Styled) Filter(filter string) int Accept(i int, ed *Editor) ModeTitle(int) string } type placeholderer interface { Placeholder() string } func newListing(t string, p listingProvider) listing { l := listing{t, p, 0, "", 0, 0} l.refresh() for i := 0; i < p.Len(); i++ { header, _ := p.Show(i) width := util.Wcswidth(header) if l.headerWidth < width { l.headerWidth = width } } return l } func (l *listing) Binding(m map[string]vartypes.Variable, k ui.Key) eval.Fn { if m[l.name] == nil { return getBinding(m[modeListing], k) } specificBindings := m[l.name].Get().(BindingTable) listingBindings := m[modeListing].Get().(BindingTable) // mode-specific binding -> listing binding -> // mode-specific default -> listing default switch { case specificBindings.HasKey(k): return specificBindings.get(k) case listingBindings.HasKey(k): return listingBindings.get(k) case specificBindings.HasKey(ui.Default): return specificBindings.get(ui.Default) case listingBindings.HasKey(ui.Default): return listingBindings.get(ui.Default) default: return nil } } func (l *listing) ModeLine() ui.Renderer { return modeLineRenderer{l.provider.ModeTitle(l.selected), l.filter} } func (l *listing) CursorOnModeLine() bool { if c, ok := l.provider.(CursorOnModeLiner); ok { return c.CursorOnModeLine() } return false } func (l *listing) List(maxHeight int) ui.Renderer { n := l.provider.Len() if n == 0 { var ph string if pher, ok := l.provider.(placeholderer); ok { ph = pher.Placeholder() } else { ph = "(no result)" } return placeholderRenderer(ph) } // Collect the entries to show. We start from the selected entry and extend // in both directions alternatingly. The entries are split into lines and // then collected in a list. low := l.selected if low == -1 { low = 0 } high := low height := 0 var listOfLines list.List getEntry := func(i int) []ui.Styled { header, content := l.provider.Show(i) lines := strings.Split(content.Text, "\n") styles := content.Styles if i == l.selected { styles = append(styles, styleForSelected...) } styleds := make([]ui.Styled, len(lines)) for i, line := range lines { if l.headerWidth > 0 { if i == 0 { line = fmt.Sprintf("%*s %s", l.headerWidth, header, line) } else { line = fmt.Sprintf("%*s %s", l.headerWidth, "", line) } } styleds[i] = ui.Styled{line, styles} } return styleds } // We start by extending high, so that the first entry to include is // l.selected. extendLow := false lastShownIncomplete := false for height < maxHeight && !(low == 0 && high == n) { var i int if (extendLow && low > 0) || high == n { low-- entry := getEntry(low) // Prepend at most the last (height - maxHeight) lines. for i = len(entry) - 1; i >= 0 && height < maxHeight; i-- { listOfLines.PushFront(entry[i]) height++ } if i >= 0 { lastShownIncomplete = true } } else { entry := getEntry(high) // Append at most the first (height - maxHeight) lines. for i = 0; i < len(entry) && height < maxHeight; i++ { listOfLines.PushBack(entry[i]) height++ } if i < len(entry) { lastShownIncomplete = true } high++ } extendLow = !extendLow } l.pagesize = high - low // Convert the List to a slice. lines := make([]ui.Styled, 0, listOfLines.Len()) for p := listOfLines.Front(); p != nil; p = p.Next() { lines = append(lines, p.Value.(ui.Styled)) } ls := listingRenderer{lines} if low > 0 || high < n || lastShownIncomplete { // Need scrollbar return listingWithScrollBarRenderer{ls, n, low, high, height} } return ls } func writeHorizontalScrollbar(b *ui.Buffer, n, low, high, width int) { slow, shigh := findScrollInterval(n, low, high, width) for i := 0; i < width; i++ { if slow <= i && i < shigh { b.Write(' ', styleForScrollBarThumb.String()) } else { b.Write('━', styleForScrollBarArea.String()) } } } func renderScrollbar(n, low, high, height int) *ui.Buffer { slow, shigh := findScrollInterval(n, low, high, height) // Logger.Printf("low = %d, high = %d, n = %d, slow = %d, shigh = %d", low, high, n, slow, shigh) b := ui.NewBuffer(1) for i := 0; i < height; i++ { if i > 0 { b.Newline() } if slow <= i && i < shigh { b.Write(' ', styleForScrollBarThumb.String()) } else { b.Write('│', styleForScrollBarArea.String()) } } return b } func findScrollInterval(n, low, high, height int) (int, int) { f := func(i int) int { return int(float64(i)/float64(n)*float64(height) + 0.5) } scrollLow, scrollHigh := f(low), f(high) if scrollLow == scrollHigh { if scrollHigh == high { scrollLow-- } else { scrollHigh++ } } return scrollLow, scrollHigh } func (l *listing) changeFilter(newfilter string) { l.filter = newfilter l.refresh() } func (l *listing) refresh() { l.selected = l.provider.Filter(l.filter) } func (l *listing) backspace() bool { _, size := utf8.DecodeLastRuneInString(l.filter) if size > 0 { l.changeFilter(l.filter[:len(l.filter)-size]) return true } return false } func (l *listing) up(cycle bool) { n := l.provider.Len() if n == 0 { return } l.selected-- if l.selected == -1 { if cycle { l.selected += n } else { l.selected++ } } } func (l *listing) pageUp() { n := l.provider.Len() if n == 0 { return } l.selected -= l.pagesize if l.selected < 0 { l.selected = 0 } } func (l *listing) down(cycle bool) { n := l.provider.Len() if n == 0 { return } l.selected++ if l.selected == n { if cycle { l.selected -= n } else { l.selected-- } } } func (l *listing) pageDown() { n := l.provider.Len() if n == 0 { return } l.selected += l.pagesize if l.selected >= n { l.selected = n - 1 } } func (l *listing) accept(ed *Editor) { if l.selected >= 0 { l.provider.Accept(l.selected, ed) } } func (l *listing) handleFilterKey(k ui.Key) bool { if likeChar(k) { l.changeFilter(l.filter + string(k.Rune)) return true } return false } func (l *listing) defaultBinding(ed *Editor) { if !l.handleFilterKey(ed.lastKey) { insertStart(ed) ed.setAction(reprocessKey) } } var errNotListing = errors.New("not in a listing mode") func getListing(ed *Editor) *listing { if l, ok := ed.mode.(*listing); ok { return l } else { throw(errNotListing) panic("unreachable") } } elvish-0.11+ds1/edit/listing_common_test.go000066400000000000000000000016121323000013700207310ustar00rootroot00000000000000package edit import ( "reflect" "testing" "github.com/elves/elvish/edit/ui" ) type shown struct { header string content ui.Styled } type listingFilterTestCases struct { filter string wantShowns []shown } func testListingFilter(t *testing.T, name string, l *listing, testcases []listingFilterTestCases) { ls := l.provider for _, testcase := range testcases { ls.Filter(testcase.filter) l := ls.Len() if l != len(testcase.wantShowns) { t.Errorf("%s.Len() -> %d, want %d (filter was %q)", name, l, len(testcase.wantShowns), testcase.filter) } else { for i, want := range testcase.wantShowns { header, content := ls.Show(i) if header != want.header || !reflect.DeepEqual(content, want.content) { t.Errorf("%s.Show(%d) => (%v, %v), want (%v, %v) (filter was %q)", name, i, header, content, want.header, want.content, testcase.filter) } } } } } elvish-0.11+ds1/edit/listing_test.go000066400000000000000000000042431323000013700173640ustar00rootroot00000000000000package edit import ( "fmt" "reflect" "strconv" "testing" "github.com/elves/elvish/edit/ui" ) type provider struct { elems []string accepted int } func (p provider) Len() int { return len(p.elems) } func (p provider) Filter(string) int { return 0 } func (p provider) Accept(i int, ed *Editor) { p.accepted = i } func (p provider) ModeTitle(i int) string { return fmt.Sprintf("test %d", i) } func (p provider) Show(i int) (string, ui.Styled) { return strconv.Itoa(i), ui.Unstyled(p.elems[i]) } var ( mode = "test233" p = provider{[]string{"foo", "bar", "foobar", "lorem", "ipsum"}, -1} ls = newListing(mode, p) ) func TestListing(t *testing.T) { wantedModeLine := modeLineRenderer{"test 0", ""} if modeLine := ls.ModeLine(); modeLine != wantedModeLine { t.Errorf("ls.ModeLine() = %v, want %v", modeLine, wantedModeLine) } // Selecting the first element and rendering with height=2. We expect to see // the first 2 elements, with the first being shown as selected. testListingList(t, 0, 2, listingWithScrollBarRenderer{ listingRenderer: listingRenderer{[]ui.Styled{ {"0 foo", styleForSelected}, {"1 bar", ui.Styles{}}, }}, n: 5, low: 0, high: 2, height: 2, }) // Selecting the last element and rendering with height=2. We expect to see // the last 2 elements, with the last being shown as selected. testListingList(t, 4, 2, listingWithScrollBarRenderer{ listingRenderer: listingRenderer{[]ui.Styled{ {"3 lorem", ui.Styles{}}, {"4 ipsum", styleForSelected}, }}, n: 5, low: 3, high: 5, height: 2, }) // Selecting the middle element and rendering with height=3. We expect to // see the middle element and two elements around it, with the middle being // shown as selected. testListingList(t, 2, 3, listingWithScrollBarRenderer{ listingRenderer: listingRenderer{[]ui.Styled{ {"1 bar", ui.Styles{}}, {"2 foobar", styleForSelected}, {"3 lorem", ui.Styles{}}, }}, n: 5, low: 1, high: 4, height: 3, }) } func testListingList(t *testing.T, i, h int, want ui.Renderer) { ls.selected = i if r := ls.List(h); !reflect.DeepEqual(r, want) { t.Errorf("selecting %d, ls.List(%d) = %v, want %v", i, h, r, want) } } elvish-0.11+ds1/edit/location.go000066400000000000000000000110621323000013700164610ustar00rootroot00000000000000package edit import ( "bytes" "fmt" "math" "os" "regexp" "strings" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/parse" "github.com/elves/elvish/store/storedefs" "github.com/elves/elvish/util" ) // Location mode. var _ = registerBuiltins(modeLocation, map[string]func(*Editor){ "start": locStart, }) // PinnedScore is a special value of Score in storedefs.Dir to represent that the // directory is pinned. var PinnedScore = math.Inf(1) type location struct { home string // The home directory; leave empty if unknown. all []storedefs.Dir filtered []storedefs.Dir } func newLocation(dirs []storedefs.Dir, home string) *listing { l := newListing(modeLocation, &location{all: dirs, home: home}) return &l } func (loc *location) ModeTitle(i int) string { return " LOCATION " } func (*location) CursorOnModeLine() bool { return true } func (loc *location) Len() int { return len(loc.filtered) } func (loc *location) Show(i int) (string, ui.Styled) { var header string score := loc.filtered[i].Score if score == PinnedScore { header = "*" } else { header = fmt.Sprintf("%.0f", score) } return header, ui.Unstyled(showPath(loc.filtered[i].Path, loc.home)) } func (loc *location) Filter(filter string) int { loc.filtered = nil pattern := makeLocationFilterPattern(filter) for _, item := range loc.all { if pattern.MatchString(showPath(item.Path, loc.home)) { loc.filtered = append(loc.filtered, item) } } if len(loc.filtered) == 0 { return -1 } return 0 } func showPath(path, home string) string { if home != "" && path == home { return "~" } else if home != "" && strings.HasPrefix(path, home+"/") { return "~/" + parse.Quote(path[len(home)+1:]) } else { return parse.Quote(path) } } var emptyRegexp = regexp.MustCompile("") func makeLocationFilterPattern(s string) *regexp.Regexp { var b bytes.Buffer b.WriteString(".*") segs := strings.Split(s, "/") for i, seg := range segs { if i > 0 { b.WriteString(".*/.*") } b.WriteString(regexp.QuoteMeta(seg)) } b.WriteString(".*") p, err := regexp.Compile(b.String()) if err != nil { logger.Printf("failed to compile regexp %q: %v", b.String(), err) return emptyRegexp } return p } // Editor interface. func (loc *location) Accept(i int, ed *Editor) { err := eval.Chdir(loc.filtered[i].Path, ed.daemon) if err != nil { ed.Notify("%v", err) } ed.mode = &ed.insert } func locStart(ed *Editor) { if ed.daemon == nil { ed.Notify("%v", ErrStoreOffline) return } // Pinned directories are also blacklisted to prevent them from showing up // twice. black := convertListsToSet(ed.locHidden(), ed.locPinned()) pwd, err := os.Getwd() if err == nil { black[pwd] = struct{}{} } stored, err := ed.daemon.Dirs(black) if err != nil { ed.Notify("store error: %v", err) return } // Concatenate pinned and stored dirs, pinned first. pinned := convertListToDirs(ed.locPinned()) dirs := make([]storedefs.Dir, len(pinned)+len(stored)) copy(dirs, pinned) copy(dirs[len(pinned):], stored) // Drop the error. When there is an error, home is "", which is used to // signify "no home known" in location. home, _ := util.GetHome("") ed.mode = newLocation(dirs, home) } // convertListToDirs converts a list of strings to []storedefs.Dir. It uses the // special score of PinnedScore to signify that the directory is pinned. func convertListToDirs(li types.List) []storedefs.Dir { pinned := make([]storedefs.Dir, 0, li.Len()) // XXX(xiaq): silently drops non-string items. li.Iterate(func(v types.Value) bool { if s, ok := v.(types.String); ok { pinned = append(pinned, storedefs.Dir{string(s), PinnedScore}) } return true }) return pinned } func convertListsToSet(lis ...types.List) map[string]struct{} { set := make(map[string]struct{}) // XXX(xiaq): silently drops non-string items. for _, li := range lis { li.Iterate(func(v types.Value) bool { if s, ok := v.(types.String); ok { set[string(s)] = struct{}{} } return true }) } return set } // Variables. var _ = RegisterVariable("loc-hidden", func() vartypes.Variable { return vartypes.NewValidatedPtr(types.EmptyList, vartypes.ShouldBeList) }) func (ed *Editor) locHidden() types.List { return ed.variables["loc-hidden"].Get().(types.List) } var _ = RegisterVariable("loc-pinned", func() vartypes.Variable { return vartypes.NewValidatedPtr(types.EmptyList, vartypes.ShouldBeList) }) func (ed *Editor) locPinned() types.List { return ed.variables["loc-pinned"].Get().(types.List) } elvish-0.11+ds1/edit/location_test.go000066400000000000000000000031451323000013700175230ustar00rootroot00000000000000package edit import ( "testing" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/store/storedefs" ) var ( theLocation = newLocation([]storedefs.Dir{ {"/pinned", PinnedScore}, {"/src/github.com/elves/elvish", 300}, {"/src/home/xyz", 233}, {"/home/dir", 100}, {"/foo/\nbar", 77}, {"/usr/elves/elvish", 6}, }, "/home") locationFilterTests = []listingFilterTestCases{ {"", []shown{ {"*", ui.Unstyled("/pinned")}, {"300", ui.Unstyled("/src/github.com/elves/elvish")}, {"233", ui.Unstyled("/src/home/xyz")}, {"100", ui.Unstyled("~/dir")}, // home is abbreviated {"77", ui.Unstyled(`"/foo/\nbar"`)}, // special char is quoted {"6", ui.Unstyled("/usr/elves/elvish")}}}, {"/s", []shown{ {"300", ui.Unstyled("/src/github.com/elves/elvish")}, {"233", ui.Unstyled("/src/home/xyz")}, {"6", ui.Unstyled("/usr/elves/elvish")}}}, {"/e/e", []shown{ {"300", ui.Unstyled("/src/github.com/elves/elvish")}, {"6", ui.Unstyled("/usr/elves/elvish")}}}, {"x", []shown{{"233", ui.Unstyled("/src/home/xyz")}}}, // Matchers operate on the displayed text, not the actual path. // 1. Home directory is abbreviated to ~, and is matched by ~, but not by // the actual path. {"~", []shown{{"100", ui.Unstyled("~/dir")}}}, {"home", []shown{{"233", ui.Unstyled("/src/home/xyz")}}}, // 2. Special characters are quoted, and are matched by the quoted form, // not by the actual form. {"\n", []shown{}}, {"\\n", []shown{{"77", ui.Unstyled(`"/foo/\nbar"`)}}}, } ) func TestLocation(t *testing.T) { testListingFilter(t, "theLocation", theLocation, locationFilterTests) } elvish-0.11+ds1/edit/lscolors/000077500000000000000000000000001323000013700161625ustar00rootroot00000000000000elvish-0.11+ds1/edit/lscolors/feature.go000066400000000000000000000050721323000013700201500ustar00rootroot00000000000000package lscolors import ( "os" ) //go:generate stringer -type=feature -output=feature_string.go type feature int const ( featureInvalid feature = iota featureOrphanedSymlink featureSymlink featureMultiHardLink featureNamedPipe featureSocket featureDoor featureBlockDevice featureCharDevice featureWorldWritableStickyDirectory featureWorldWritableDirectory featureStickyDirectory featureDirectory featureCapability featureSetuid featureSetgid featureExecutable featureRegular ) // Weirdly, permission masks for group and other are missing on platforms other // than linux, darwin and netbsd. So we replicate some of them here. const ( worldWritable = 02 // Writable by other executable = 0111 // Executable ) func determineFeature(fname string, mh bool) (feature, error) { stat, err := os.Lstat(fname) if err != nil { return featureInvalid, err } m := stat.Mode() // Symlink and OrphanedSymlink has highest precedence if is(m, os.ModeSymlink) { _, err := os.Stat(fname) if err != nil { return featureOrphanedSymlink, nil } return featureSymlink, nil } // featureMultiHardLink if mh && isMultiHardlink(stat) { return featureMultiHardLink, nil } // type bits features switch { case is(m, os.ModeNamedPipe): return featureNamedPipe, nil case is(m, os.ModeSocket): // Never on Windows return featureSocket, nil case isDoor(stat): return featureDoor, nil case is(m, os.ModeCharDevice): return featureCharDevice, nil case is(m, os.ModeDevice): // There is no dedicated os.Mode* flag for block device. On all // supported Unix platforms, when os.ModeDevice is set but // os.ModeCharDevice is not, the file is a block device (i.e. // syscall.S_IFBLK is set). On Windows, this branch is unreachable. // // On Plan9, this in inaccurate. return featureBlockDevice, nil case is(m, os.ModeDir): // Perm bits features for directory perm := m.Perm() switch { case is(m, os.ModeSticky) && is(perm, worldWritable): return featureWorldWritableStickyDirectory, nil case is(perm, worldWritable): return featureWorldWritableDirectory, nil case is(m, os.ModeSticky): return featureStickyDirectory, nil default: return featureDirectory, nil } } // TODO(xiaq): Support featureCapacity // Perm bits features for regular files switch { case is(m, os.ModeSetuid): return featureSetuid, nil case is(m, os.ModeSetgid): return featureSetgid, nil case m&executable != 0: return featureExecutable, nil } // Check extension return featureRegular, nil } func is(m, p os.FileMode) bool { return m&p == p } elvish-0.11+ds1/edit/lscolors/feature_string.go000066400000000000000000000014431323000013700215340ustar00rootroot00000000000000// Code generated by "stringer -type=feature -output=feature_string.go"; DO NOT EDIT. package lscolors import "strconv" const _feature_name = "featureInvalidfeatureOrphanedSymlinkfeatureSymlinkfeatureMultiHardLinkfeatureNamedPipefeatureSocketfeatureDoorfeatureBlockDevicefeatureCharDevicefeatureWorldWritableStickyDirectoryfeatureWorldWritableDirectoryfeatureStickyDirectoryfeatureDirectoryfeatureCapabilityfeatureSetuidfeatureSetgidfeatureExecutablefeatureRegular" var _feature_index = [...]uint16{0, 14, 36, 50, 70, 86, 99, 110, 128, 145, 180, 209, 231, 247, 264, 277, 290, 307, 321} func (i feature) String() string { if i < 0 || i >= feature(len(_feature_index)-1) { return "feature(" + strconv.FormatInt(int64(i), 10) + ")" } return _feature_name[_feature_index[i]:_feature_index[i+1]] } elvish-0.11+ds1/edit/lscolors/feature_test.go000066400000000000000000000034241323000013700212060ustar00rootroot00000000000000package lscolors import ( "os" "runtime" "testing" "github.com/elves/elvish/util" ) func TestDetermineFeature(t *testing.T) { test := func(fname string, mh bool, wantedFeature feature) { feature, err := determineFeature(fname, mh) if err != nil { t.Errorf("determineFeature(%q, %v) returns error %v, want no error", fname, mh, err) } if feature != wantedFeature { t.Errorf("determineFeature(%q, %v) returns feature %v, want %v", fname, mh, feature, wantedFeature) } } util.InTempDir(func(string) { create("a", 0600) // Regular file. test("a", true, featureRegular) // Symlink. err := os.Symlink("a", "symlink") if err != nil { t.Logf("Failed to create symlink: %v; skipping symlink test", err) } else { test("symlink", true, featureSymlink) } // Broken symlink. err = os.Symlink("aaaa", "bad-symlink") if err != nil { t.Logf("Failed to create bad symlink: %v; skipping bad symlink test", err) } else { test("bad-symlink", true, featureOrphanedSymlink) } if runtime.GOOS != "windows" { // Multiple hard links. os.Link("a", "a2") test("a", true, featureMultiHardLink) } // Don't test for multiple hard links. test("a", false, featureRegular) // Setuid and Setgid. // XXX(xiaq): Fails. /* create("su", os.ModeSetuid) test("su", true, featureSetuid) create("sg", os.ModeSetgid) test("sg", true, featureSetgid) */ if runtime.GOOS != "windows" { // Executable. create("xu", 0100) create("xg", 0010) create("xo", 0001) test("xu", true, featureExecutable) test("xg", true, featureExecutable) test("xo", true, featureExecutable) } }) } func create(fname string, perm os.FileMode) { f, err := os.OpenFile(fname, os.O_CREATE, perm) if err != nil { panic(err) } f.Close() } elvish-0.11+ds1/edit/lscolors/lscolors.go000066400000000000000000000102721323000013700203530ustar00rootroot00000000000000// Package lscolors provides styling of filenames based on file features. // // This is a reverse-engineered implementation of the parsing and // interpretation of the LS_COLORS environmental variable used by GNU // coreutils. package lscolors import ( "os" "path" "strings" "sync" ) // Colorist styles filenames based on the features of the file. type Colorist interface { // GetStyle returns the style for the named file. GetStyle(fname string) string } type colorist struct { styleForFeature map[feature]string styleForExt map[string]string } const defaultLsColorString = `rs=:di=01;34:ln=01;36:mh=:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=36:*.au=36:*.flac=36:*.mid=36:*.midi=36:*.mka=36:*.mp3=36:*.mpc=36:*.ogg=36:*.ra=36:*.wav=36:*.axa=36:*.oga=36:*.spx=36:*.xspf=36:` var ( lastColorist *colorist lastColoristMutex sync.Mutex lastLsColors string ) func init() { lastColorist = parseLsColor(defaultLsColorString) } func GetColorist() Colorist { lastColoristMutex.Lock() defer lastColoristMutex.Unlock() s := getLsColors() if lastLsColors != s { lastLsColors = s lastColorist = parseLsColor(s) } return lastColorist } func getLsColors() string { lsColorString := os.Getenv("LS_COLORS") if len(lsColorString) == 0 { return defaultLsColorString } return lsColorString } var featureForName = map[string]feature{ "rs": featureRegular, "di": featureDirectory, "ln": featureSymlink, "mh": featureMultiHardLink, "pi": featureNamedPipe, "so": featureSocket, "do": featureDoor, "bd": featureBlockDevice, "cd": featureCharDevice, "or": featureOrphanedSymlink, "su": featureSetuid, "sg": featureSetgid, "ca": featureCapability, "tw": featureWorldWritableStickyDirectory, "ow": featureWorldWritableDirectory, "st": featureStickyDirectory, "ex": featureExecutable, } // parseLsColor parses a string in the LS_COLORS format into lsColor. Erroneous // fields are silently ignored. func parseLsColor(s string) *colorist { lc := &colorist{make(map[feature]string), make(map[string]string)} for _, spec := range strings.Split(s, ":") { words := strings.Split(spec, "=") if len(words) != 2 { continue } key, value := words[0], words[1] filterValues := []string{} for _, splitValue := range strings.Split(value, ";") { if strings.Count(splitValue, "0") == len(splitValue) { continue } filterValues = append(filterValues, splitValue) } if len(filterValues) == 0 { continue } value = strings.Join(filterValues, ";") if strings.HasPrefix(key, "*.") { lc.styleForExt[key[2:]] = value } else { feature, ok := featureForName[key] if !ok { continue } lc.styleForFeature[feature] = value } } return lc } func (lc *colorist) GetStyle(fname string) string { mh := strings.Trim(lc.styleForFeature[featureMultiHardLink], "0") != "" // TODO Handle error from determineFeature feature, _ := determineFeature(fname, mh) if feature == featureRegular { if ext := path.Ext(fname); ext != "" { if style, ok := lc.styleForExt[ext]; ok { return style } } } return lc.styleForFeature[feature] } elvish-0.11+ds1/edit/lscolors/stat_notsolaris.go000066400000000000000000000002211323000013700217340ustar00rootroot00000000000000// +build !solaris package lscolors import "os" func isDoor(info os.FileInfo) bool { // Doors are only supported on Solaris. return false } elvish-0.11+ds1/edit/lscolors/stat_solaris.go000066400000000000000000000003161323000013700212200ustar00rootroot00000000000000package lscolors import ( "os" "syscall" ) // Taken from Illumos header file. const sIFDOOR = 0xD000 func isDoor(info os.FileInfo) bool { return info.Sys().(*syscall.Stat_t).Mode&sIFDOOR == sIFDOOR } elvish-0.11+ds1/edit/lscolors/stat_unix.go000066400000000000000000000002511323000013700205250ustar00rootroot00000000000000// +build !windows,!plan9 package lscolors import ( "os" "syscall" ) func isMultiHardlink(info os.FileInfo) bool { return info.Sys().(*syscall.Stat_t).Nlink > 1 } elvish-0.11+ds1/edit/lscolors/stat_windows.go000066400000000000000000000003431323000013700212360ustar00rootroot00000000000000package lscolors import "os" func isMultiHardlink(info os.FileInfo) bool { // Windows supports hardlinks, but it is not exposed directly. We omit the // implementation for now. // TODO: Maybe implement it? return false } elvish-0.11+ds1/edit/matcher.go000066400000000000000000000046351323000013700163040ustar00rootroot00000000000000package edit import ( "errors" "strings" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/util" "github.com/xiaq/persistent/hashmap" ) var ( errIncorrectNumOfResults = errors.New("matcher must return a bool for each candidate") errMatcherMustBeFn = errors.New("matcher must be a function") errMatcherInputMustBeString = errors.New("matcher input must be string") ) var ( matchPrefix = &eval.BuiltinFn{ "edit:match-prefix", wrapMatcher(strings.HasPrefix)} matchSubstr = &eval.BuiltinFn{ "edit:match-substr", wrapMatcher(strings.Contains)} matchSubseq = &eval.BuiltinFn{ "edit:match-subseq", wrapMatcher(util.HasSubseq)} matchers = []*eval.BuiltinFn{ matchPrefix, matchSubstr, matchSubseq, } _ = RegisterVariable("-matcher", func() vartypes.Variable { m := hashmap.Empty.Assoc( // Fallback matcher uses empty string as key types.String(""), matchPrefix) return vartypes.NewValidatedPtr(types.NewMap(m), vartypes.ShouldBeMap) }) ) func (ed *Editor) lookupMatcher(name string) (eval.Fn, bool) { m := ed.variables["-matcher"].Get().(types.Map) if !m.HasKey(types.String(name)) { // Use fallback matcher name = "" } matcher, ok := m.IndexOne(types.String(name)).(eval.Fn) return matcher, ok } func wrapMatcher(matcher func(s, p string) bool) eval.BuiltinFnImpl { return func(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { var pattern types.String iterate := eval.ScanArgsOptionalInput(ec, args, &pattern) var options struct { IgnoreCase bool SmartCase bool } eval.ScanOptsToStruct(opts, &options) switch { case options.IgnoreCase && options.SmartCase: throwf("-ignore-case and -smart-case cannot be used together") case options.IgnoreCase: innerMatcher := matcher matcher = func(s, p string) bool { return innerMatcher(strings.ToLower(s), strings.ToLower(p)) } case options.SmartCase: innerMatcher := matcher matcher = func(s, p string) bool { if p == strings.ToLower(p) { // Ignore case is pattern is all lower case. return innerMatcher(strings.ToLower(s), p) } else { return innerMatcher(s, p) } } } out := ec.OutputChan() iterate(func(v types.Value) { s, ok := v.(types.String) if !ok { throw(errMatcherInputMustBeString) } out <- types.Bool(matcher(string(s), string(pattern))) }) } } elvish-0.11+ds1/edit/max_height.go000066400000000000000000000007601323000013700167710ustar00rootroot00000000000000package edit import ( "math" "strconv" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/util" ) var _ = RegisterVariable("max-height", func() vartypes.Variable { return vartypes.NewValidatedPtr(types.String("+Inf"), vartypes.ShouldBeNumber) }) func (ed *Editor) maxHeight() int { f, _ := strconv.ParseFloat(string(ed.variables["max-height"].Get().(types.String)), 64) if math.IsInf(f, 1) { return util.MaxInt } return int(f) } elvish-0.11+ds1/edit/minmax.go000066400000000000000000000002171323000013700161420ustar00rootroot00000000000000package edit func min(a, b int) int { if a <= b { return a } return b } func max(a, b int) int { if a >= b { return a } return b } elvish-0.11+ds1/edit/mode.go000066400000000000000000000027041323000013700156000ustar00rootroot00000000000000package edit import ( "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/vartypes" ) // Names of modes, used for subnamespaces of edit:. const ( modeInsert = "insert" modeRawInsert = "raw-insert" modeCommand = "command" modeCompletion = "completion" modeNavigation = "navigation" modeHistory = "history" modeHistoryListing = "histlist" modeLastCmd = "lastcmd" modeLocation = "location" modeListing = "listing" // A "super mode" for histlist, lastcmd, loc modeNarrow = "narrow" // a listing mode fork to be extended by scripts ) // Mode is an editor mode. type Mode interface { ModeLine() ui.Renderer Binding(map[string]vartypes.Variable, ui.Key) eval.Fn } // CursorOnModeLiner is an optional interface that modes can implement. If a // mode does and the method returns true, the cursor is placed on the modeline // when that mode is active. type CursorOnModeLiner interface { CursorOnModeLine() bool } // Lister is a mode with a listing. type Lister interface { List(maxHeight int) ui.Renderer } // ListRenderer is a mode with a listing that handles the rendering itself. // NOTE(xiaq): This interface is being deprecated in favor of Lister. type ListRenderer interface { // ListRender renders the listing under the given constraint of width and // maximum height. It returns a rendered buffer. ListRender(width, maxHeight int) *ui.Buffer } elvish-0.11+ds1/edit/narrow.go000066400000000000000000000252151323000013700161660ustar00rootroot00000000000000package edit import ( "container/list" "errors" "strconv" "strings" "unicode/utf8" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/xiaq/persistent/hashmap" ) var _ = registerBuiltins(modeNarrow, map[string]func(*Editor){ "up": func(ed *Editor) { getNarrow(ed).up(false) }, "up-cycle": func(ed *Editor) { getNarrow(ed).up(true) }, "page-up": func(ed *Editor) { getNarrow(ed).pageUp() }, "down": func(ed *Editor) { getNarrow(ed).down(false) }, "down-cycle": func(ed *Editor) { getNarrow(ed).down(true) }, "page-down": func(ed *Editor) { getNarrow(ed).pageDown() }, "backspace": func(ed *Editor) { getNarrow(ed).backspace() }, "accept": func(ed *Editor) { getNarrow(ed).accept(ed) }, "accept-close": func(ed *Editor) { getNarrow(ed).accept(ed) insertStart(ed) }, "toggle-ignore-duplication": func(ed *Editor) { l := getNarrow(ed) l.opts.IgnoreDuplication = !l.opts.IgnoreDuplication l.refresh() }, "toggle-ignore-case": func(ed *Editor) { l := getNarrow(ed) l.opts.IgnoreCase = !l.opts.IgnoreCase l.refresh() }, "default": func(ed *Editor) { getNarrow(ed).defaultBinding(ed) }, }) // narrow implements a listing mode that supports the notion of selecting an // entry and filtering entries. type narrow struct { name string selected int filter string pagesize int headerWidth int placehold string source func() []narrowItem action func(*Editor, narrowItem) match func(string, string) bool filtered []narrowItem opts narrowOptions } func (l *narrow) Binding(m map[string]vartypes.Variable, k ui.Key) eval.Fn { if l.opts.bindingMap != nil { if f, ok := l.opts.bindingMap[k]; ok { return f } } return getBinding(m[modeNarrow], k) } func (l *narrow) ModeLine() ui.Renderer { ml := l.opts.Modeline var opt []string if l.opts.AutoCommit { opt = append(opt, "A") } if l.opts.IgnoreCase { opt = append(opt, "C") } if l.opts.IgnoreDuplication { opt = append(opt, "D") } if len(opt) != 0 { ml += "[" + strings.Join(opt, " ") + "]" } return modeLineRenderer{ml, l.filter} } func (l *narrow) CursorOnModeLine() bool { return true } func (l *narrow) List(maxHeight int) ui.Renderer { if l.opts.MaxLines > 0 && l.opts.MaxLines < maxHeight { maxHeight = l.opts.MaxLines } if l.filtered == nil { l.refresh() } n := len(l.filtered) if n == 0 { return placeholderRenderer(l.placehold) } // Collect the entries to show. We start from the selected entry and extend // in both directions alternatingly. The entries are split into lines and // then collected in a list. low := l.selected if low == -1 { low = 0 } high := low height := 0 var listOfLines list.List getEntry := func(i int) []ui.Styled { display := l.filtered[i].Display() lines := strings.Split(display.Text, "\n") styles := display.Styles if i == l.selected { styles = append(styles, styleForSelected...) } styleds := make([]ui.Styled, len(lines)) for i, line := range lines { styleds[i] = ui.Styled{line, styles} } return styleds } // We start by extending high, so that the first entry to include is // l.selected. extendLow := false lastShownIncomplete := false for height < maxHeight && !(low == 0 && high == n) { var i int if (extendLow && low > 0) || high == n { low-- entry := getEntry(low) // Prepend at most the last (height - maxHeight) lines. for i = len(entry) - 1; i >= 0 && height < maxHeight; i-- { listOfLines.PushFront(entry[i]) height++ } if i >= 0 { lastShownIncomplete = true } } else { entry := getEntry(high) // Append at most the first (height - maxHeight) lines. for i = 0; i < len(entry) && height < maxHeight; i++ { listOfLines.PushBack(entry[i]) height++ } if i < len(entry) { lastShownIncomplete = true } high++ } extendLow = !extendLow } l.pagesize = high - low // Convert the List to a slice. lines := make([]ui.Styled, 0, listOfLines.Len()) for p := listOfLines.Front(); p != nil; p = p.Next() { lines = append(lines, p.Value.(ui.Styled)) } ls := listingRenderer{lines} if low > 0 || high < n || lastShownIncomplete { // Need scrollbar return listingWithScrollBarRenderer{ls, n, low, high, height} } return ls } func (l *narrow) refresh() { var candidates []narrowItem if l.source != nil { candidates = l.source() } l.filtered = make([]narrowItem, 0, len(candidates)) filter := l.filter if l.opts.IgnoreCase { filter = strings.ToLower(filter) } set := make(map[string]struct{}) for _, item := range candidates { text := item.FilterText() s := text if l.opts.IgnoreCase { s = strings.ToLower(s) } if !l.match(s, filter) { continue } if l.opts.IgnoreDuplication { if _, ok := set[text]; ok { continue } set[text] = struct{}{} } l.filtered = append(l.filtered, item) } if l.opts.KeepBottom { l.selected = len(l.filtered) - 1 } else { l.selected = 0 } } func (l *narrow) changeFilter(newfilter string) { l.filter = newfilter l.refresh() } func (l *narrow) backspace() bool { _, size := utf8.DecodeLastRuneInString(l.filter) if size > 0 { l.changeFilter(l.filter[:len(l.filter)-size]) return true } return false } func (l *narrow) up(cycle bool) { n := len(l.filtered) if n == 0 { return } l.selected-- if l.selected == -1 { if cycle { l.selected += n } else { l.selected++ } } } func (l *narrow) pageUp() { n := len(l.filtered) if n == 0 { return } l.selected -= l.pagesize if l.selected < 0 { l.selected = 0 } } func (l *narrow) down(cycle bool) { n := len(l.filtered) if n == 0 { return } l.selected++ if l.selected == n { if cycle { l.selected -= n } else { l.selected-- } } } func (l *narrow) pageDown() { n := len(l.filtered) if n == 0 { return } l.selected += l.pagesize if l.selected >= n { l.selected = n - 1 } } func (l *narrow) accept(ed *Editor) { if l.selected >= 0 { l.action(ed, l.filtered[l.selected]) } } func (l *narrow) handleFilterKey(ed *Editor) bool { k := ed.lastKey if likeChar(k) { l.changeFilter(l.filter + string(k.Rune)) if len(l.filtered) == 1 && l.opts.AutoCommit { l.accept(ed) insertStart(ed) } return true } return false } func (l *narrow) defaultBinding(ed *Editor) { if !l.handleFilterKey(ed) { insertStart(ed) ed.setAction(reprocessKey) } } var errNotNarrow = errors.New("not in a narrow mode") func getNarrow(ed *Editor) *narrow { if l, ok := ed.mode.(*narrow); ok { return l } else { throw(errNotNarrow) panic("unreachable") } } type narrowItem interface { types.Value Display() ui.Styled Content() string FilterText() string } type narrowOptions struct { AutoCommit bool Bindings types.Map IgnoreDuplication bool IgnoreCase bool KeepBottom bool MaxLines int Modeline string bindingMap map[ui.Key]eval.Fn } type narrowItemString struct { types.String } func (s *narrowItemString) Content() string { return string(s.String) } func (s *narrowItemString) Display() ui.Styled { return ui.Unstyled(string(s.String)) } func (s *narrowItemString) FilterText() string { return s.Content() } type narrowItemComplex struct { types.Map } func (c *narrowItemComplex) Content() string { key := types.String("content") if !c.Map.HasKey(key) { return "" } if s, ok := c.Map.IndexOne(key).(types.String); !ok { return "" } else { return s.String() } } // TODO: add style func (c *narrowItemComplex) Display() ui.Styled { key := types.String("display") if !c.Map.HasKey(key) { return ui.Unstyled("") } if s, ok := c.Map.IndexOne(key).(types.String); !ok { return ui.Unstyled("") } else { return ui.Unstyled(s.String()) } } func (c *narrowItemComplex) FilterText() string { key := types.String("filter-text") if c.Map.HasKey(key) { return c.Map.IndexOne(key).(types.String).String() } return c.Content() } func NarrowRead(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { var source, action eval.Fn l := &narrow{ opts: narrowOptions{ Bindings: types.NewMap(hashmap.Empty), }, } eval.ScanArgs(args, &source, &action) eval.ScanOptsToStruct(opts, &l.opts) l.opts.Bindings.IterateKey(func(k types.Value) bool { key := ui.ToKey(k) f := l.opts.Bindings.IndexOne(k) maybeThrow(eval.ShouldBeFn(f)) if l.opts.bindingMap == nil { l.opts.bindingMap = make(map[ui.Key]eval.Fn) } l.opts.bindingMap[key] = f.(eval.Fn) return true }) l.source = narrowGetSource(ec, source) l.action = func(ed *Editor, item narrowItem) { ed.CallFn(action, item) } // TODO: user customize varible l.match = strings.Contains l.changeFilter("") ed := ec.Editor.(*Editor) ed.mode = l } func narrowGetSource(ec *eval.Frame, source eval.Fn) func() []narrowItem { return func() []narrowItem { ed := ec.Editor.(*Editor) vs, err := ec.PCaptureOutput(source, eval.NoArgs, eval.NoOpts) if err != nil { ed.Notify(err.Error()) return nil } var lis []narrowItem for _, v := range vs { switch raw := v.(type) { case types.String: lis = append(lis, &narrowItemString{raw}) case types.Map: lis = append(lis, &narrowItemComplex{raw}) } } return lis } } func CommandHistory(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { var ( rest []int limit, start, end int ) eval.ScanArgsVariadic(args, &rest) eval.TakeNoOpt(opts) out := ec.OutputChan() ed := ec.Editor.(*Editor) cmds, err := ed.historyFuser.AllCmds() if err != nil { return } if len(rest) > 0 { limit = rest[0] } total := len(cmds) switch { case limit > 0: start = 0 end = limit if limit > total { end = total } case limit < 0: start = limit + total if start < 0 { start = 0 } end = total default: start = 0 end = total } for i := start; i < end; i++ { out <- types.MakeMap(map[types.Value]types.Value{ types.String("id"): types.String(strconv.Itoa(i)), types.String("cmd"): types.String(cmds[i]), }) } } func InsertAtDot(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { var text types.String eval.ScanArgs(args, &text) eval.TakeNoOpt(opts) ed := ec.Editor.(*Editor) ed.insertAtDot(text.String()) } func ReplaceInput(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { var text types.String eval.ScanArgs(args, &text) eval.TakeNoOpt(opts) ed := ec.Editor.(*Editor) ed.buffer = text.String() } func Wordify(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { var text types.String eval.ScanArgs(args, &text) eval.TakeNoOpt(opts) out := ec.OutputChan() for _, s := range wordify(text.String()) { out <- types.String(s) } } elvish-0.11+ds1/edit/navigation.go000066400000000000000000000220171323000013700170120ustar00rootroot00000000000000package edit import ( "errors" "os" "path" "sort" "strings" "unicode/utf8" "github.com/elves/elvish/edit/lscolors" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" ) // Navigation subsystem. // Interface. var _ = registerBuiltins(modeNavigation, map[string]func(*Editor){ "start": navStart, "up": navUp, "down": navDown, "page-up": navPageUp, "page-down": navPageDown, "left": navLeft, "right": navRight, "file-preview-up": navFilePreviewUp, "file-preview-down": navFilePreviewDown, "trigger-shown-hidden": navTriggerShowHidden, "trigger-filter": navTriggerFilter, "insert-selected": navInsertSelected, "insert-selected-and-quit": navInsertSelectedAndQuit, "default": navDefault, }) type navigation struct { current *navColumn parent *navColumn preview navPreview showHidden bool filtering bool filter string chdir func(string) error } type navPreview interface { FullWidth(int) int List(int) ui.Renderer } func (*navigation) Binding(m map[string]vartypes.Variable, k ui.Key) eval.Fn { return getBinding(m[modeNavigation], k) } func (n *navigation) ModeLine() ui.Renderer { title := " NAVIGATING " if n.showHidden { title += "(show hidden) " } return modeLineRenderer{title, n.filter} } func (n *navigation) CursorOnModeLine() bool { return n.filtering } func navStart(ed *Editor) { initNavigation(&ed.navigation, ed) ed.mode = &ed.navigation } func navUp(ed *Editor) { ed.navigation.prev() } func navDown(ed *Editor) { ed.navigation.next() } func navPageUp(ed *Editor) { ed.navigation.current.pageUp() ed.navigation.refresh() } func navPageDown(ed *Editor) { ed.navigation.current.pageDown() ed.navigation.refresh() } func navLeft(ed *Editor) { ed.navigation.ascend() } func navRight(ed *Editor) { ed.navigation.descend() } func navFilePreviewUp(ed *Editor) { fp, ok := ed.navigation.preview.(*navFilePreview) if ok { if fp.beginLine > 0 { fp.beginLine-- } } } func navFilePreviewDown(ed *Editor) { fp, ok := ed.navigation.preview.(*navFilePreview) if ok { if fp.beginLine < len(fp.lines)-1 { fp.beginLine++ } } } func navTriggerShowHidden(ed *Editor) { ed.navigation.showHidden = !ed.navigation.showHidden ed.navigation.refresh() } func navTriggerFilter(ed *Editor) { ed.navigation.filtering = !ed.navigation.filtering } func navInsertSelected(ed *Editor) { ed.insertAtDot(parse.Quote(ed.navigation.current.selectedName()) + " ") } func navInsertSelectedAndQuit(ed *Editor) { ed.insertAtDot(parse.Quote(ed.navigation.current.selectedName()) + " ") ed.mode = &ed.insert } func navDefault(ed *Editor) { // Use key binding for insert mode without exiting nigation mode. k := ed.lastKey n := &ed.navigation if n.filtering && likeChar(k) { n.filter += k.String() n.refreshCurrent() n.refreshDirPreview() } else if n.filtering && k == (ui.Key{ui.Backspace, 0}) { _, size := utf8.DecodeLastRuneInString(n.filter) if size > 0 { n.filter = n.filter[:len(n.filter)-size] n.refreshCurrent() n.refreshDirPreview() } } else { ed.CallFn(getBinding(ed.bindings[modeInsert], k)) } } // Implementation. // TODO(xiaq): Remember which file was selected in each directory. var errorEmptyCwd = errors.New("current directory is empty") func initNavigation(n *navigation, ed *Editor) { *n = navigation{chdir: func(dir string) error { return eval.Chdir(dir, ed.daemon) }} n.refresh() } func (n *navigation) maintainSelected(name string) { n.current.selected = 0 for i, s := range n.current.candidates { if s.Text > name { break } n.current.selected = i } } func (n *navigation) refreshCurrent() { selectedName := n.current.selectedName() all, err := n.loaddir(".") if err != nil { n.current = newErrNavColumn(err) return } // Try to select the old selected file. // XXX(xiaq): This would break when we support alternative ordering. n.current = newNavColumn(all, func(i int) bool { return i == 0 || all[i].Text <= selectedName }) n.current.changeFilter(n.filter) n.maintainSelected(selectedName) } func (n *navigation) refreshParent() { wd, err := os.Getwd() if err != nil { n.parent = newErrNavColumn(err) return } if wd == "/" { n.parent = newNavColumn(nil, nil) } else { all, err := n.loaddir("..") if err != nil { n.parent = newErrNavColumn(err) return } cwd, err := os.Stat(".") if err != nil { n.parent = newErrNavColumn(err) return } n.parent = newNavColumn(all, func(i int) bool { d, _ := os.Lstat("../" + all[i].Text) return os.SameFile(d, cwd) }) } } func (n *navigation) refreshDirPreview() { if n.current.selected != -1 { name := n.current.selectedName() fi, err := os.Stat(name) if err != nil { n.preview = newErrNavColumn(err) return } if fi.Mode().IsDir() { all, err := n.loaddir(name) if err != nil { n.preview = newErrNavColumn(err) return } n.preview = newNavColumn(all, func(int) bool { return false }) } else { n.preview = makeNavFilePreview(name) } } else { n.preview = nil } } // refresh rereads files in current and parent directories and maintains the // selected file if possible. func (n *navigation) refresh() { n.refreshCurrent() n.refreshParent() n.refreshDirPreview() } // ascend changes current directory to the parent. // TODO(xiaq): navigation.{ascend descend} bypasses the cd builtin. This can be // problematic if cd acquires more functionality (e.g. trigger a hook). func (n *navigation) ascend() error { wd, err := os.Getwd() if err != nil { return err } if wd == "/" { return nil } name := n.parent.selectedName() err = os.Chdir("..") if err != nil { return err } n.filter = "" n.refresh() n.maintainSelected(name) // XXX Refresh dir preview again. We should perhaps not have used refresh // above. n.refreshDirPreview() return nil } // descend changes current directory to the selected file, if it is a // directory. func (n *navigation) descend() error { if n.current.selected == -1 { return errorEmptyCwd } name := n.current.selectedName() err := n.chdir(name) if err != nil { return err } n.filter = "" n.current.selected = -1 n.refresh() n.refreshDirPreview() return nil } // prev selects the previous file. func (n *navigation) prev() { if n.current.selected > 0 { n.current.selected-- } n.refresh() } // next selects the next file. func (n *navigation) next() { if n.current.selected != -1 && n.current.selected < len(n.current.candidates)-1 { n.current.selected++ } n.refresh() } func (n *navigation) loaddir(dir string) ([]ui.Styled, error) { f, err := os.Open(dir) if err != nil { return nil, err } names, err := f.Readdirnames(-1) if err != nil { return nil, err } sort.Strings(names) var all []ui.Styled lsColor := lscolors.GetColorist() for _, name := range names { if n.showHidden || name[0] != '.' { all = append(all, ui.Styled{name, ui.StylesFromString(lsColor.GetStyle(path.Join(dir, name)))}) } } return all, nil } func (n *navigation) List(maxHeight int) ui.Renderer { return makeNavRenderer( maxHeight, n.parent.FullWidth(maxHeight), n.current.FullWidth(maxHeight), n.preview.FullWidth(maxHeight), n.parent.List(maxHeight), n.current.List(maxHeight), n.preview.List(maxHeight), ) } // navColumn is a column in the navigation layout. type navColumn struct { listing all []ui.Styled candidates []ui.Styled // selected int err error } func newNavColumn(all []ui.Styled, sel func(int) bool) *navColumn { nc := &navColumn{all: all, candidates: all} nc.provider = nc nc.selected = -1 for i := range all { if sel(i) { nc.selected = i } } return nc } func newErrNavColumn(err error) *navColumn { nc := &navColumn{err: err} nc.provider = nc return nc } func (nc *navColumn) Placeholder() string { if nc.err != nil { return nc.err.Error() } return "" } func (nc *navColumn) Len() int { return len(nc.candidates) } func (nc *navColumn) Show(i int) (string, ui.Styled) { cand := nc.candidates[i] return "", ui.Styled{" " + cand.Text + " ", cand.Styles} } func (nc *navColumn) Filter(filter string) int { nc.candidates = nc.candidates[:0] for _, s := range nc.all { if strings.Contains(s.Text, filter) { nc.candidates = append(nc.candidates, s) } } return 0 } func (nc *navColumn) FullWidth(h int) int { if nc == nil { return 0 } if nc.err != nil { return util.Wcswidth(nc.err.Error()) } maxw := 0 for _, s := range nc.candidates { maxw = max(maxw, util.Wcswidth(s.Text)+2) } if len(nc.candidates) > h { maxw++ } return maxw } func (nc *navColumn) Accept(i int, ed *Editor) { // TODO } func (nc *navColumn) ModeTitle(i int) string { // Not used return "" } func (nc *navColumn) selectedName() string { if nc == nil || nc.selected == -1 || nc.selected >= len(nc.candidates) { return "" } return nc.candidates[nc.selected].Text } elvish-0.11+ds1/edit/navigation_preview.go000066400000000000000000000042521323000013700205540ustar00rootroot00000000000000package edit import ( "errors" "io" "os" "strings" "unicode/utf8" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/util" ) // PreviewBytes is the maximum number of bytes to preview a file. const PreviewBytes = 64 * 1024 // Errors displayed in the preview area of navigation mode. var ( ErrNotRegular = errors.New("no preview for non-regular file") ErrNotValidUTF8 = errors.New("no preview for non-utf8 file") ) type navFilePreview struct { lines []ui.Styled fullWidth int beginLine int } func newNavFilePreview(lines []string) *navFilePreview { width := 0 convertedLines := make([]ui.Styled, len(lines)) for i, line := range lines { // BUG: Handle tabstops correctly convertedLine := strings.Replace(line, "\t", " ", -1) convertedLines[i] = ui.Unstyled(convertedLine) width = max(width, util.Wcswidth(convertedLine)) } return &navFilePreview{convertedLines, width, 0} } func (fp *navFilePreview) FullWidth(h int) int { width := fp.fullWidth if h < len(fp.lines) { return width + 1 } return width } func (fp *navFilePreview) List(h int) ui.Renderer { if len(fp.lines) <= h { logger.Printf("Height %d fit all lines", h) return listingRenderer{fp.lines} } shown := fp.lines[fp.beginLine:] if len(shown) > h { shown = shown[:h] } logger.Printf("Showing lines %d to %d", fp.beginLine, fp.beginLine+len(shown)) return listingWithScrollBarRenderer{ listingRenderer{shown}, len(fp.lines), fp.beginLine, fp.beginLine + len(shown), h} } func makeNavFilePreview(fname string) navPreview { file, err := os.Open(fname) if err != nil { return newErrNavColumn(err) } info, err := file.Stat() if err != nil { return newErrNavColumn(err) } if (info.Mode() & (os.ModeDevice | os.ModeNamedPipe | os.ModeSocket | os.ModeCharDevice)) != 0 { return newErrNavColumn(ErrNotRegular) } // BUG: when the file is bigger than the buffer, the scrollbar is wrong. var buf [PreviewBytes]byte nr, err := file.Read(buf[:]) if err != nil && err != io.EOF { return newErrNavColumn(err) } content := string(buf[:nr]) if !utf8.ValidString(content) { return newErrNavColumn(ErrNotValidUTF8) } return newNavFilePreview(strings.Split(content, "\n")) } elvish-0.11+ds1/edit/navigation_width.go000066400000000000000000000021641323000013700202120ustar00rootroot00000000000000package edit // getNavWidths calculates the widths for the three (parent, current and // preview) columns in the navigation mode. It takes the available width, full // width required to display the current and preview columns, and returns // suitable widths for the columns. // // The parent column always gets 1/6 of the total width. The current and preview // columns initially get 1/2 of the remaining width each, but if one of them // does not have enough widths and another has some spare width, the amount of // spare width or the needed width (whichever is smaller) is donated from the // latter to the former. func getNavWidths(total, currentFull, previewFull int) (int, int, int) { parent := total / 6 remain := total - parent current := remain / 2 preview := remain - current if current < currentFull && preview > previewFull { donate := min(currentFull-current, preview-previewFull) current += donate preview -= donate } else if preview < previewFull && current > currentFull { donate := min(previewFull-preview, current-currentFull) preview += donate current -= donate } return parent, current, preview } elvish-0.11+ds1/edit/navigation_widths_test.go000066400000000000000000000013071323000013700214320ustar00rootroot00000000000000package edit import ( "testing" "github.com/elves/elvish/tt" ) var ( getNavWidthsTests = tt.Table{ // Enough room for both current and preview: parent gets 1/6, current // and preview gets 1/2 of remain tt.Args(120, 10, 10).Rets(20, 50, 50), // Not enough room for either of current and preview: same as above tt.Args(120, 100, 100).Rets(20, 50, 50), // Enough room for current but not preview; current donates to preview tt.Args(120, 10, 100).Rets(20, 10, 90), // Enough room for preview but not current; preview donates to current tt.Args(120, 100, 10).Rets(20, 90, 10), } ) func TestGetNavWidths(t *testing.T) { tt.Test(t, tt.Fn("getNavWidths", getNavWidths), getNavWidthsTests) } elvish-0.11+ds1/edit/nodeutil.go000066400000000000000000000024561323000013700165030ustar00rootroot00000000000000package edit import ( "strings" "github.com/elves/elvish/parse" ) // Utilities for insepcting the AST. Used for completers and stylists. func primaryInSimpleCompound(pn *parse.Primary, ev pureEvaler) (*parse.Compound, string) { indexing := parse.GetIndexing(pn.Parent()) if indexing == nil { return nil, "" } compound := parse.GetCompound(indexing.Parent()) if compound == nil { return nil, "" } head, err := ev.PurelyEvalPartialCompound(compound, indexing) if err != nil { return nil, "" } return compound, head } // leafNodeAtDot finds the leaf node at a specific position. It returns nil if // position is out of bound. func findLeafNode(n parse.Node, p int) parse.Node { descend: for len(n.Children()) > 0 { for _, ch := range n.Children() { if ch.Begin() <= p && p <= ch.End() { n = ch continue descend } } return nil } return n } func wordify(src string) []string { n, _ := parse.Parse("[wordify]", src) return wordifyInner(n, nil) } func wordifyInner(n parse.Node, words []string) []string { if len(n.Children()) == 0 || parse.IsCompound(n) { text := n.SourceText() if strings.TrimFunc(text, parse.IsSpaceOrNewline) != "" { return append(words, text) } return words } for _, ch := range n.Children() { words = wordifyInner(ch, words) } return words } elvish-0.11+ds1/edit/prompt/000077500000000000000000000000001323000013700156435ustar00rootroot00000000000000elvish-0.11+ds1/edit/prompt/prompt.go000066400000000000000000000124251323000013700175170ustar00rootroot00000000000000// Package prompt implements prompt-related functionalities of the editor. package prompt import ( "io/ioutil" "math" "os" "os/user" "strconv" "sync" "time" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/util" ) var logger = util.GetLogger("[edit/prompt] ") // Editor is the interface used by the prompt to access the editor. type Editor interface { Evaler() *eval.Evaler Variable(string) vartypes.Variable Notify(string, ...interface{}) } // maxSeconds is the maximum number of seconds time.Duration can represent. const maxSeconds = float64(math.MaxInt64 / time.Second) // PromptVariable returns a variable for $edit:prompt. func PromptVariable() vartypes.Variable { user, err := user.Current() isRoot := err == nil && user.Uid == "0" prompt := func(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { out := ec.OutputChan() out <- types.String(util.Getwd()) if isRoot { out <- &ui.Styled{"# ", ui.Styles{"red"}} } else { out <- &ui.Styled{"> ", ui.Styles{}} } } return vartypes.NewValidatedPtr( &eval.BuiltinFn{"default prompt", prompt}, eval.ShouldBeFn) } // Prompt extracts $edit:prompt. func Prompt(ed Editor) eval.Callable { return ed.Variable("prompt").Get().(eval.Callable) } // RpromptVariable returns a variable for $edit:rprompt. func RpromptVariable() vartypes.Variable { username := "???" user, err := user.Current() if err == nil { username = user.Username } hostname, err := os.Hostname() if err != nil { hostname = "???" } rpromptStr := username + "@" + hostname rprompt := func(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { out := ec.OutputChan() out <- &ui.Styled{rpromptStr, ui.Styles{"inverse"}} } return vartypes.NewValidatedPtr( &eval.BuiltinFn{"default rprompt", rprompt}, eval.ShouldBeFn) } // Rprompt extracts $edit:rprompt. func Rprompt(ed Editor) eval.Callable { return ed.Variable("rprompt").Get().(eval.Callable) } // Implementation for $rprompt-persistent. // TODO Keep the underlying boolean and float64 values somewhere within the // Editor to avoid casts. // RpromptPersistentVariable returns a variable for $edit:rprompt-persistent. func RpromptPersistentVariable() vartypes.Variable { b := false return vartypes.NewBool(&b) } // RpromptPersistent extracts $edit:rprompt-persistent. func RpromptPersistent(ed Editor) bool { return bool(ed.Variable("rprompt-persistent").Get().(types.Bool).Bool()) } // MaxWaitVariable returns a variable for $edit:-prompts-max-wait. func MaxWaitVariable() vartypes.Variable { f := math.Inf(1) return vartypes.NewNumber(&f) } // MaxWait extracts $edit:-prompts-max-wait. func MaxWait(ed Editor) float64 { f, _ := strconv.ParseFloat(string(ed.Variable("-prompts-max-wait").Get().(types.String)), 64) return f } // MakeMaxWait makes a channel that sends the current time after // $edit:-prompts-max-wait seconds if the time fits in a time.Duration value, or // nil otherwise. func MakeMaxWaitChan(ed Editor) <-chan time.Time { f := MaxWait(ed) if f > maxSeconds { return nil } return time.After(time.Duration(f * float64(time.Second))) } // callPrompt calls a Fn, assuming that it is a prompt. It calls the Fn with no // arguments and closed input, and converts its outputs to styled objects. func callPrompt(ed Editor, fn eval.Callable) []*ui.Styled { ports := []*eval.Port{ eval.DevNullClosedChan, {}, // Will be replaced when capturing output {File: os.Stderr}, } var ( styleds []*ui.Styled styledsMutex sync.Mutex ) add := func(s *ui.Styled) { styledsMutex.Lock() styleds = append(styleds, s) styledsMutex.Unlock() } // Value output may be of type ui.Styled or any other type, in which case // they are converted to ui.Styled. valuesCb := func(ch <-chan types.Value) { for v := range ch { if s, ok := v.(*ui.Styled); ok { add(s) } else { add(&ui.Styled{types.ToString(v), ui.Styles{}}) } } } // Byte output is added to the prompt as a single unstyled text. bytesCb := func(r *os.File) { allBytes, err := ioutil.ReadAll(r) if err != nil { logger.Println("error reading prompt byte output:", err) } if len(allBytes) > 0 { add(&ui.Styled{string(allBytes), ui.Styles{}}) } } // XXX There is no source to pass to NewTopEvalCtx. ec := eval.NewTopFrame(ed.Evaler(), eval.NewInternalSource("[prompt]"), ports) err := ec.PCaptureOutputInner(fn, nil, eval.NoOpts, valuesCb, bytesCb) if err != nil { ed.Notify("prompt function error: %v", err) return nil } return styleds } // Updater manages the update of a prompt. type Updater struct { promptFn func(Editor) eval.Callable Staled []*ui.Styled } var staledPrompt = &ui.Styled{"?", ui.Styles{"inverse"}} // NewUpdater creates a new Updater. func NewUpdater(promptFn func(Editor) eval.Callable) *Updater { return &Updater{promptFn, []*ui.Styled{staledPrompt}} } // Update updates the prompt, returning a channel onto which the result will be // written. func (pu *Updater) Update(ed Editor) <-chan []*ui.Styled { ch := make(chan []*ui.Styled) go func() { result := callPrompt(ed, pu.promptFn(ed)) pu.Staled = make([]*ui.Styled, len(result)+1) pu.Staled[0] = staledPrompt copy(pu.Staled[1:], result) ch <- result }() return ch } elvish-0.11+ds1/edit/prompt/prompt_test.go000066400000000000000000000001371323000013700205530ustar00rootroot00000000000000package prompt import "testing" func TestPrompt(t *testing.T) { // TODO(xiaq): Add tests. } elvish-0.11+ds1/edit/prompt_vars.go000066400000000000000000000005051323000013700172250ustar00rootroot00000000000000package edit import "github.com/elves/elvish/edit/prompt" var ( _ = RegisterVariable("prompt", prompt.PromptVariable) _ = RegisterVariable("rprompt", prompt.RpromptVariable) _ = RegisterVariable("rprompt-persistent", prompt.RpromptPersistentVariable) _ = RegisterVariable("-prompts-max-wait", prompt.MaxWaitVariable) ) elvish-0.11+ds1/edit/raw_insert.go000066400000000000000000000013531323000013700170300ustar00rootroot00000000000000package edit import ( "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/vartypes" ) // Raw insert mode is a special mode, in that it does not use the normal key // binding. Rather, insertRaw is called directly from the main loop in // Editor.ReadLine. type rawInsert struct { } func startInsertRaw(ed *Editor) { ed.reader.SetRaw(true) ed.mode = rawInsert{} } func insertRaw(ed *Editor, r rune) { ed.insertAtDot(string(r)) ed.reader.SetRaw(false) ed.mode = &ed.insert } func (rawInsert) Binding(map[string]vartypes.Variable, ui.Key) eval.Fn { // The raw insert mode does not handle keys. return nil } func (ri rawInsert) ModeLine() ui.Renderer { return modeLineRenderer{" RAW ", ""} } elvish-0.11+ds1/edit/registry.go000066400000000000000000000050221323000013700165200ustar00rootroot00000000000000package edit import ( "errors" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" ) // This file contains several "registries", data structure that are written // during program initialization and later used when initializing the Editor. // // The purpose of these registries is to decentralize the definition for // builtins, default bindings, and variables (e.g. $edit:prompt). For instance, // the definition for $edit:prompt can live in prompt.go instead of api.go. var variableRegistry = map[string]func() vartypes.Variable{} // RegisterVariable registers a variable: its name and a func to derive a // Variable instance. It is later to be used during Editor initialization to // populate Editor.variables as well as the edit: namespace. func RegisterVariable(name string, maker func() vartypes.Variable) struct{} { variableRegistry[name] = maker return struct{}{} } func makeVariables() map[string]vartypes.Variable { m := make(map[string]vartypes.Variable, len(variableRegistry)) for name, maker := range variableRegistry { m[name] = maker() } return m } var builtinMaps = map[string]map[string]*BuiltinFn{} // registerBuiltins registers builtins under a subnamespace of edit:, to be used // during the initialization of the Editor. It should be called for global // variable initializations to make sure every subnamespace is registered before // makeBindings is ever called. func registerBuiltins(module string, impls map[string]func(*Editor)) struct{} { if _, ok := builtinMaps[module]; !ok { builtinMaps[module] = make(map[string]*BuiltinFn) } for name, impl := range impls { ns := "edit" if module != "" { ns += ":" + module } fullName := ns + ":" + name + eval.FnSuffix builtinMaps[module][name] = &BuiltinFn{fullName, impl} } return struct{}{} } func makeNsFromBuiltins(builtins map[string]*BuiltinFn) eval.Ns { ns := make(eval.Ns) for name, builtin := range builtins { ns[name+eval.FnSuffix] = vartypes.NewPtr(builtin) } return ns } func makeBindings() map[string]vartypes.Variable { bindings := map[string]vartypes.Variable{} // XXX This abuses the builtin registry to get a list of mode names for mode := range builtinMaps { bindings[mode] = vartypes.NewValidatedPtr( BindingTable{types.EmptyMap}, shouldBeBindingTable) } return bindings } var errShouldBeBindingTable = errors.New("should be binding table") func shouldBeBindingTable(v types.Value) error { if _, ok := v.(BindingTable); !ok { return errShouldBeBindingTable } return nil } elvish-0.11+ds1/edit/render.go000066400000000000000000000175751323000013700161470ustar00rootroot00000000000000package edit import ( "strings" "unicode/utf8" "github.com/elves/elvish/edit/highlight" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/util" ) type modeLineRenderer struct { title string filter string } func (ml modeLineRenderer) Render(b *ui.Buffer) { b.WriteString(ml.title, styleForMode.String()) b.WriteSpaces(1, "") b.WriteString(ml.filter, styleForFilter.String()) b.Dot = b.Cursor() } type modeLineWithScrollBarRenderer struct { modeLineRenderer n, low, high int } func (ml modeLineWithScrollBarRenderer) Render(b *ui.Buffer) { ml.modeLineRenderer.Render(b) scrollbarWidth := b.Width - ui.CellsWidth(b.Lines[len(b.Lines)-1]) - 2 if scrollbarWidth >= 3 { b.WriteSpaces(1, "") writeHorizontalScrollbar(b, ml.n, ml.low, ml.high, scrollbarWidth) } } type placeholderRenderer string func (lp placeholderRenderer) Render(b *ui.Buffer) { b.WriteString(util.TrimWcwidth(string(lp), b.Width), "") } type listingRenderer struct { lines []ui.Styled } func (ls listingRenderer) Render(b *ui.Buffer) { for i, line := range ls.lines { if i > 0 { b.Newline() } b.WriteString(util.ForceWcwidth(line.Text, b.Width), line.Styles.String()) } } type listingWithScrollBarRenderer struct { listingRenderer n, low, high, height int } func (ls listingWithScrollBarRenderer) Render(b *ui.Buffer) { b1 := ui.Render(ls.listingRenderer, b.Width-1) b.ExtendRight(b1, 0) scrollbar := renderScrollbar(ls.n, ls.low, ls.high, ls.height) b.ExtendRight(scrollbar, b.Width-1) } type navRenderer struct { maxHeight int fwParent, fwCurrent, fwPreview int parent, current, preview ui.Renderer } func makeNavRenderer(h int, w1, w2, w3 int, r1, r2, r3 ui.Renderer) ui.Renderer { return &navRenderer{h, w1, w2, w3, r1, r2, r3} } const navColMargin = 1 func (nr *navRenderer) Render(b *ui.Buffer) { wParent, wCurrent, wPreview := getNavWidths(b.Width-navColMargin*2, nr.fwCurrent, nr.fwPreview) bParent := ui.Render(nr.parent, wParent) b.ExtendRight(bParent, 0) bCurrent := ui.Render(nr.current, wCurrent) b.ExtendRight(bCurrent, wParent+navColMargin) if wPreview > 0 { bPreview := ui.Render(nr.preview, wPreview) b.ExtendRight(bPreview, wParent+wCurrent+2*navColMargin) } } // linesRenderer renders lines with a uniform style. type linesRenderer struct { lines []string style string } func (nr linesRenderer) Render(b *ui.Buffer) { b.WriteString(strings.Join(nr.lines, "\n"), "") } // cmdlineRenderer renders the command line, including the prompt, the user's // input and the rprompt. type cmdlineRenderer struct { prompt []*ui.Styled line string styling *highlight.Styling dot int rprompt []*ui.Styled hasComp bool compBegin int compEnd int compText string hasHist bool histBegin int histText string } func newCmdlineRenderer(p []*ui.Styled, l string, s *highlight.Styling, d int, rp []*ui.Styled) *cmdlineRenderer { return &cmdlineRenderer{prompt: p, line: l, styling: s, dot: d, rprompt: rp} } func (clr *cmdlineRenderer) setComp(b, e int, t string) { clr.hasComp = true clr.compBegin, clr.compEnd, clr.compText = b, e, t } func (clr *cmdlineRenderer) setHist(b int, t string) { clr.hasHist = true clr.histBegin, clr.histText = b, t } func (clr *cmdlineRenderer) Render(b *ui.Buffer) { b.EagerWrap = true b.WriteStyleds(clr.prompt) // If the prompt takes less than half of a line, set the indent. if len(b.Lines) == 1 && b.Col*2 < b.Width { b.Indent = b.Col } // i keeps track of number of bytes written. i := 0 applier := clr.styling.Apply() // nowAt is called at every rune boundary. nowAt := func(i int) { applier.At(i) if clr.hasComp && i == clr.compBegin { b.WriteString(clr.compText, styleForCompleted.String()) } if i == clr.dot { b.Dot = b.Cursor() } } nowAt(0) for _, r := range clr.line { if clr.hasComp && clr.compBegin <= i && i < clr.compEnd { // Do nothing. This part is replaced by the completion candidate. } else { b.Write(r, applier.Get()) } i += utf8.RuneLen(r) nowAt(i) if clr.hasHist && i == clr.histBegin { break } } if clr.hasHist { // Put the rest of current history and position the cursor at the // end of the line. b.WriteString(clr.histText, styleForCompletedHistory.String()) b.Dot = b.Cursor() } // Write rprompt if len(clr.rprompt) > 0 { padding := b.Width - b.Col for _, s := range clr.rprompt { padding -= util.Wcswidth(s.Text) } if padding >= 1 { b.EagerWrap = false b.WriteSpaces(padding, "") b.WriteStyleds(clr.rprompt) } } } var logEditorRender = false // editorRenderer renders the entire editor. type editorRenderer struct { *editorState height int bufNoti *ui.Buffer } func (er *editorRenderer) Render(buf *ui.Buffer) { height, width, es := er.height, buf.Width, er.editorState var bufNoti, bufLine, bufMode, bufTips, bufListing *ui.Buffer // butNoti if len(es.notifications) > 0 { bufNoti = ui.Render(linesRenderer{es.notifications, ""}, width) es.notifications = nil } // bufLine clr := newCmdlineRenderer(es.promptContent, es.buffer, es.styling, es.dot, es.rpromptContent) // TODO(xiaq): Instead of doing a type switch, expose an API for modes to // modify the text (and mark their part as modified). switch mode := es.mode.(type) { case *completion: c := es.completion clr.setComp(c.begin, c.end, c.selectedCandidate().code) case *hist: begin := len(mode.Prefix()) clr.setHist(begin, mode.CurrentCmd()[begin:]) } bufLine = ui.Render(clr, width) // bufMode bufMode = ui.Render(es.mode.ModeLine(), width) // bufTips // TODO tips is assumed to contain no newlines. if len(es.tips) > 0 { bufTips = ui.Render(linesRenderer{es.tips, styleForTip.String()}, width) } hListing := 0 // Trim lines and determine the maximum height for bufListing // TODO come up with a UI to tell the user that something is not shown. switch { case height >= ui.BuffersHeight(bufNoti, bufLine, bufMode, bufTips): hListing = height - ui.BuffersHeight(bufLine, bufMode, bufTips) case height >= ui.BuffersHeight(bufNoti, bufLine, bufTips): bufMode = nil case height >= ui.BuffersHeight(bufNoti, bufLine): bufMode = nil if bufTips != nil { bufTips.TrimToLines(0, height-ui.BuffersHeight(bufNoti, bufLine)) } case height >= ui.BuffersHeight(bufLine): bufTips, bufMode = nil, nil if bufNoti != nil { n := len(bufNoti.Lines) bufNoti.TrimToLines(n-(height-ui.BuffersHeight(bufLine)), n) } case height >= 1: bufNoti, bufTips, bufMode = nil, nil, nil dotLine := bufLine.Dot.Line bufLine.TrimToLines(dotLine+1-height, dotLine+1) default: // Broken terminal. Still try to render one line of bufLine. bufNoti, bufTips, bufMode = nil, nil, nil dotLine := bufLine.Dot.Line bufLine.TrimToLines(dotLine, dotLine+1) } // bufListing. if hListing > 0 { if lister, ok := es.mode.(ListRenderer); ok { bufListing = lister.ListRender(width, hListing) } else if lister, ok := es.mode.(Lister); ok { bufListing = ui.Render(lister.List(hListing), width) } // XXX When in completion mode, we re-render the mode line, since the // scrollbar in the mode line depends on completion.lastShown which is // only known after the listing has been rendered. Since rendering the // scrollbar never adds additional lines to bufMode, we may do this // without recalculating the layout. if _, ok := es.mode.(*completion); ok { bufMode = ui.Render(es.mode.ModeLine(), width) } } if logEditorRender { logger.Printf("bufLine %d, bufMode %d, bufTips %d, bufListing %d", ui.BuffersHeight(bufLine), ui.BuffersHeight(bufMode), ui.BuffersHeight(bufTips), ui.BuffersHeight(bufListing)) } // XXX buf.Lines = nil // Combine buffers (reusing bufLine) buf.Extend(bufLine, true) cursorOnModeLine := false if coml, ok := es.mode.(CursorOnModeLiner); ok { cursorOnModeLine = coml.CursorOnModeLine() } buf.Extend(bufMode, cursorOnModeLine) buf.Extend(bufTips, false) buf.Extend(bufListing, false) er.bufNoti = bufNoti } elvish-0.11+ds1/edit/style.go000066400000000000000000000014741323000013700160170ustar00rootroot00000000000000package edit import ( "github.com/elves/elvish/edit/ui" ) var styleForCompilerError = ui.Styles{"white", "bg-red"} // Styles for UI. var ( //styleForPrompt = "" //styleForRPrompt = "inverse" styleForCompleted = ui.Styles{"underlined"} styleForCompletedHistory = ui.Styles{"underlined"} styleForMode = ui.Styles{"bold", "lightgray", "bg-magenta"} styleForTip = ui.Styles{} styleForFilter = ui.Styles{"underlined"} styleForSelected = ui.Styles{"inverse"} styleForScrollBarArea = ui.Styles{"magenta"} styleForScrollBarThumb = ui.Styles{"magenta", "inverse"} // Use default style for completion listing styleForCompletion = ui.Styles{} // Use inverse style for selected completion entry styleForSelectedCompletion = ui.Styles{"inverse"} ) elvish-0.11+ds1/edit/styled.go000066400000000000000000000006541323000013700161620ustar00rootroot00000000000000package edit import ( "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" ) func styled(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { var textv, stylev types.String eval.ScanArgs(args, &textv, &stylev) text, style := string(textv), string(stylev) eval.TakeNoOpt(opts) out := ec.OutputChan() out <- &ui.Styled{text, ui.StylesFromString(style)} } elvish-0.11+ds1/edit/tty/000077500000000000000000000000001323000013700151425ustar00rootroot00000000000000elvish-0.11+ds1/edit/tty/event.go000066400000000000000000000030151323000013700166110ustar00rootroot00000000000000package tty import "github.com/elves/elvish/edit/ui" // Event represents an event that can be read from the terminal. type Event interface { isEvent() } // KeyEvent represents a key press. type KeyEvent ui.Key // MouseEvent represents a mouse event (either pressing or releasing). type MouseEvent struct { Pos Down bool // Number of the Button, 0-based. -1 for unknown. Button int Mod ui.Mod } // RawRune represents a rune read in raw mode. type RawRune rune // CursorPosition represents a report of the current cursor position from the // terminal driver, usually as a response from a cursor position request. type CursorPosition Pos // PasteSetting indicates the start or finish of pasted text. type PasteSetting bool // FatalErrorEvent represents an error that affects the Reader's ability to // continue reading events. After sending a FatalError, the Reader makes no more // attempts at continuing to read events and wait for Stop to be called. type FatalErrorEvent struct{ Err error } // NonfatalErrorEvent represents an error that can be gradually recovered. After // sending a NonfatalError, the Reader will continue to read events. Note that // one anamoly in the terminal might cause multiple NonfatalError events to be // sent. type NonfatalErrorEvent struct{ Err error } func (KeyEvent) isEvent() {} func (MouseEvent) isEvent() {} func (RawRune) isEvent() {} func (CursorPosition) isEvent() {} func (PasteSetting) isEvent() {} func (FatalErrorEvent) isEvent() {} func (NonfatalErrorEvent) isEvent() {} elvish-0.11+ds1/edit/tty/pos.go000066400000000000000000000001261323000013700162710ustar00rootroot00000000000000package tty // Pos is the position in a terminal. type Pos struct { line, col int } elvish-0.11+ds1/edit/tty/reader.go000066400000000000000000000020061323000013700167310ustar00rootroot00000000000000package tty import "os" // Reader reads terminal events and makes them available on a channel. type Reader interface { // SetRaw turns the raw mode on or off. In raw mode, the Reader does not // decode special sequences, but simply deliver them as RawRune events. If // the Reader is in the middle of reading one event, it takes effect after // this event is fully read. On platforms (i.e. Windows) where events are // not encoded as special sequences, SetRaw has no effect. SetRaw(bool) // EventChan returns the channel onto which the Reader writes events that it // has read. EventChan() <-chan Event // Start starts the Reader. Start() // Stop stops the Reader. Stop() // Close releases resources associated with the Reader. It does not affect // resources used to create it. Close() } // NewReader creates a new Reader on the given terminal file. // TODO: NewReader should return an error as well. Right now failure to // initialize Reader panics. func NewReader(f *os.File) Reader { return newReader(f) } elvish-0.11+ds1/edit/tty/reader_unix.go000066400000000000000000000251241323000013700200020ustar00rootroot00000000000000// +build !windows,!plan9 package tty import ( "fmt" "os" "time" "github.com/elves/elvish/edit/ui" ) // DefaultSeqTimeout is the amount of time within which runes that make up an // escape sequence are supposed to follow each other. Modern terminal emulators // send escape sequences very fast, so 10ms is more than sufficient. SSH // connections on a slow link might be problematic though. const DefaultSeqTimeout = 10 * time.Millisecond // reader reads terminal escape sequences and decodes them into events. type reader struct { ar *runeReader seqTimeout time.Duration raw bool eventChan chan Event stopChan chan struct{} stopAckChan chan struct{} } func newReader(f *os.File) *reader { rd := &reader{ newRuneReader(f), DefaultSeqTimeout, false, make(chan Event), nil, nil, } return rd } // SetRaw turns the raw option on or off. If the reader is in the middle of // reading one event, it takes effect after this event is fully read. func (rd *reader) SetRaw(raw bool) { rd.raw = raw } // EventChan returns the channel onto which the Reader writes what it has read. func (rd *reader) EventChan() <-chan Event { return rd.eventChan } // Start starts the Reader. func (rd *reader) Start() { rd.stopChan = make(chan struct{}) rd.stopAckChan = make(chan struct{}) rd.ar.Start() go rd.run() } func (rd *reader) run() { // NOTE: Stop may be called at any time. All channel reads and sends should // be wrapped in a select and have a "case <-rd.stopChan" clause. for { select { case r := <-rd.ar.Chan(): if rd.raw { rd.send(RawRune(r)) } else { event, seqError, ioError := rd.readOne(r) if event != nil { rd.send(event) } if seqError != nil { rd.send(NonfatalErrorEvent{seqError}) } if ioError != nil { rd.send(FatalErrorEvent{ioError}) <-rd.stopChan } } case err := <-rd.ar.ErrorChan(): rd.send(FatalErrorEvent{err}) <-rd.stopChan case <-rd.stopChan: } select { case <-rd.stopChan: close(rd.stopAckChan) return default: } } } // send tries to send an event, unless stop was requested. If stop was requested // before, it does nothing; hence it is safe to use after stop. func (rd *reader) send(event Event) { select { case rd.eventChan <- event: case <-rd.stopChan: } } // Stop stops the Reader. func (rd *reader) Stop() { rd.ar.Stop() close(rd.stopChan) <-rd.stopAckChan } // Close releases files associated with the Reader. It does not close the file // used to create it. func (rd *reader) Close() { rd.ar.Close() } // Used by readRune in readOne to signal end of current sequence. const runeEndOfSeq rune = -1 // readOne attempts to read one key or CPR, led by a rune already read. func (rd *reader) readOne(r rune) (event Event, seqError, ioError error) { currentSeq := string(r) badSeq := func(msg string) { seqError = fmt.Errorf("%s: %q", msg, currentSeq) } // readRune attempts to read a rune within a timeout of EscSequenceTimeout. // It may return runeEndOfSeq when the read timed out, an error was // encountered (in which case it sets ioError) or when stopped. In all three // cases, the reader should terminate the current sequence. If the current // sequence is valid, it should set event. If not, it should set seqError by // calling badSeq. readRune := func() rune { select { case r := <-rd.ar.Chan(): currentSeq += string(r) return r case ioError = <-rd.ar.ErrorChan(): return runeEndOfSeq case <-time.After(rd.seqTimeout): return runeEndOfSeq case <-rd.stopChan: return runeEndOfSeq } } switch r { case 0x1b: // ^[ Escape r2 := readRune() // According to https://unix.stackexchange.com/a/73697, rxvt and derivatives // prepend another ESC to a CSI-style or G3-style sequence to signal Alt. // If that happens, remember this now; it will be later picked up when parsing // those two kinds of sequences. // // issue #181 hasTwoLeadingESC := false if r2 == 0x1b { hasTwoLeadingESC = true r2 = readRune() } if r2 == runeEndOfSeq { // XXX Error is swallowed // Nothing follows. Taken as a lone Escape. event = KeyEvent{'[', ui.Ctrl} break } switch r2 { case '[': // A '[' follows. CSI style function key sequence. r = readRune() if r == runeEndOfSeq { event = KeyEvent{'[', ui.Alt} return } nums := make([]int, 0, 2) var starter rune // Read an optional starter. switch r { case '<': starter = r r = readRune() case 'M': // Mouse event. cb := readRune() if cb == runeEndOfSeq { badSeq("Incomplete mouse event") return } cx := readRune() if cx == runeEndOfSeq { badSeq("Incomplete mouse event") return } cy := readRune() if cy == runeEndOfSeq { badSeq("Incomplete mouse event") return } down := true button := int(cb & 3) if button == 3 { down = false button = -1 } mod := mouseModify(int(cb)) event = MouseEvent{ Pos{int(cy) - 32, int(cx) - 32}, down, button, mod} return } CSISeq: for { switch { case r == ';': nums = append(nums, 0) case '0' <= r && r <= '9': if len(nums) == 0 { nums = append(nums, 0) } cur := len(nums) - 1 nums[cur] = nums[cur]*10 + int(r-'0') case r == runeEndOfSeq: // Incomplete CSI. badSeq("Incomplete CSI") return default: // Treat as a terminator. break CSISeq } r = readRune() } if starter == 0 && r == 'R' { // Cursor position report. if len(nums) != 2 { badSeq("bad CPR") return } event = CursorPosition{nums[0], nums[1]} } else if starter == '<' && (r == 'm' || r == 'M') { // SGR-style mouse event. if len(nums) != 3 { badSeq("bad SGR mouse event") return } down := r == 'M' button := nums[0] & 3 mod := mouseModify(nums[0]) event = MouseEvent{Pos{nums[2], nums[1]}, down, button, mod} } else if r == '~' && len(nums) == 1 && (nums[0] == 200 || nums[0] == 201) { b := nums[0] == 200 event = PasteSetting(b) } else { k := parseCSI(nums, r, currentSeq) if k == (ui.Key{}) { badSeq("bad CSI") } else { if hasTwoLeadingESC { k.Mod |= ui.Alt } event = KeyEvent(k) } } case 'O': // An 'O' follows. G3 style function key sequence: read one rune. r = readRune() if r == runeEndOfSeq { // Nothing follows after 'O'. Taken as Alt-o. event = KeyEvent{'o', ui.Alt} return } r, ok := g3Seq[r] if ok { k := KeyEvent{r, 0} if hasTwoLeadingESC { k.Mod |= ui.Alt } event = KeyEvent(k) } else { badSeq("bad G3") } default: // Something other than '[' or 'O' follows. Taken as an // Alt-modified key, possibly also modified by Ctrl. k := ctrlModify(r2) k.Mod |= ui.Alt event = KeyEvent(k) } default: event = KeyEvent(ctrlModify(r)) } return } // ctrlModify determines whether a rune corresponds to a Ctrl-modified key and // returns the ui.Key the rune represents. func ctrlModify(r rune) ui.Key { switch r { case 0x0: return ui.Key{'`', ui.Ctrl} // ^@ case 0x1e: return ui.Key{'6', ui.Ctrl} // ^^ case 0x1f: return ui.Key{'/', ui.Ctrl} // ^_ case ui.Tab, ui.Enter, ui.Backspace: // ^I ^J ^? return ui.Key{r, 0} default: // Regular ui.Ctrl sequences. if 0x1 <= r && r <= 0x1d { return ui.Key{r + 0x40, ui.Ctrl} } } return ui.Key{r, 0} } // G3-style key sequences: \eO followed by exactly one character. For instance, // \eOP is F1. var g3Seq = map[rune]rune{ 'A': ui.Up, 'B': ui.Down, 'C': ui.Right, 'D': ui.Left, // F1-F4: xterm, libvte and tmux 'P': ui.F1, 'Q': ui.F2, 'R': ui.F3, 'S': ui.F4, // Home and End: libvte 'H': ui.Home, 'F': ui.End, } // Tables for CSI-style key sequences, which are \e[ followed by a list of // semicolon-delimited numeric arguments, before being concluded by a // non-numeric, non-semicolon rune. // CSI-style key sequences that can be identified based on the ending rune. For // instance, \e[A is Up. var keyByLast = map[rune]ui.Key{ 'A': {ui.Up, 0}, 'B': {ui.Down, 0}, 'C': {ui.Right, 0}, 'D': {ui.Left, 0}, 'H': {ui.Home, 0}, 'F': {ui.End, 0}, 'Z': {ui.Tab, ui.Shift}, } // CSI-style key sequences ending with '~' and can be identified based on the // only number argument. For instance, \e[~ is Home. When they are // modified, they take two arguments, first being 1 and second identifying the // modifier (see xtermModify). For instance, \e[1;4~ is Shift-Alt-Home. var keyByNum0 = map[int]rune{ 1: ui.Home, 2: ui.Insert, 3: ui.Delete, 4: ui.End, 5: ui.PageUp, 6: ui.PageDown, 11: ui.F1, 12: ui.F2, 13: ui.F3, 14: ui.F4, 15: ui.F5, 17: ui.F6, 18: ui.F7, 19: ui.F8, 20: ui.F9, 21: ui.F10, 23: ui.F11, 24: ui.F12, } // CSI-style key sequences ending with '~', with 27 as the first numeric // argument. For instance, \e[27;9~ is Tab. // // The list is taken blindly from tmux source xterm-keys.c. I don't have a // keyboard-terminal combination that generate such sequences, but assumably // some PC keyboard with a numpad can. var keyByNum2 = map[int]rune{ 9: '\t', 13: '\r', 33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-', 46: '.', 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7', 56: '8', 57: '9', 58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';', } // parseCSI parses a CSI-style key sequence. func parseCSI(nums []int, last rune, seq string) ui.Key { if k, ok := keyByLast[last]; ok { if len(nums) == 0 { // Unmodified: \e[A (Up) return k } else if len(nums) == 2 && nums[0] == 1 { // Modified: \e[1;5A (Ctrl-Up) return xtermModify(k, nums[1], seq) } else { return ui.Key{} } } if last == '~' { if len(nums) == 1 || len(nums) == 2 { if r, ok := keyByNum0[nums[0]]; ok { k := ui.Key{r, 0} if len(nums) == 1 { // Unmodified: \e[5~ (PageUp) return k } // Modified: \e[5;5~ (Ctrl-PageUp) return xtermModify(k, nums[1], seq) } } else if len(nums) == 3 && nums[0] == 27 { if r, ok := keyByNum2[nums[2]]; ok { k := ui.Key{r, 0} return xtermModify(k, nums[1], seq) } } } return ui.Key{} } func xtermModify(k ui.Key, mod int, seq string) ui.Key { switch mod { case 0: // do nothing case 2: k.Mod |= ui.Shift case 3: k.Mod |= ui.Alt case 4: k.Mod |= ui.Shift | ui.Alt case 5: k.Mod |= ui.Ctrl case 6: k.Mod |= ui.Shift | ui.Ctrl case 7: k.Mod |= ui.Alt | ui.Ctrl case 8: k.Mod |= ui.Shift | ui.Alt | ui.Ctrl default: return ui.Key{} } return k } func mouseModify(n int) ui.Mod { var mod ui.Mod if n&4 != 0 { mod |= ui.Shift } if n&8 != 0 { mod |= ui.Alt } if n&16 != 0 { mod |= ui.Ctrl } return mod } elvish-0.11+ds1/edit/tty/reader_unix_test.go000066400000000000000000000066651323000013700210520ustar00rootroot00000000000000// +build !windows,!plan9 package tty import ( "os" "testing" "time" "github.com/elves/elvish/edit/ui" ) // timeout is the longest time the tests wait between writing something on // the writer and reading it from the reader before declaring that the // reader has a bug. const timeoutInterval = 100 * time.Millisecond func timeout() <-chan time.Time { return time.After(timeoutInterval) } var ( theWriter *os.File innerReader *os.File theReader *reader ) func TestMain(m *testing.M) { r, w, err := os.Pipe() if err != nil { panic("os.Pipe returned error, something is seriously wrong") } defer r.Close() defer w.Close() theWriter = w innerReader = r theReader = newReader(r) theReader.Start() defer theReader.Stop() os.Exit(m.Run()) } var keyTests = []struct { input string want Event }{ // Simple graphical key. {"x", KeyEvent{'x', 0}}, {"X", KeyEvent{'X', 0}}, {" ", KeyEvent{' ', 0}}, // Ctrl key. {"\001", KeyEvent{'A', ui.Ctrl}}, {"\033", KeyEvent{'[', ui.Ctrl}}, // Ctrl-ish keys, but not thought as Ctrl keys by our reader. {"\n", KeyEvent{'\n', 0}}, {"\t", KeyEvent{'\t', 0}}, {"\x7f", KeyEvent{'\x7f', 0}}, // backspace // Alt plus simple graphical key. {"\033a", KeyEvent{'a', ui.Alt}}, {"\033[", KeyEvent{'[', ui.Alt}}, // G3-style key. {"\033OA", KeyEvent{ui.Up, 0}}, {"\033OH", KeyEvent{ui.Home, 0}}, // CSI-sequence key identified by the ending rune. {"\033[A", KeyEvent{ui.Up, 0}}, {"\033[H", KeyEvent{ui.Home, 0}}, // Test for all possible modifier {"\033[1;2A", KeyEvent{ui.Up, ui.Shift}}, // CSI-sequence key with one argument, always ending in '~'. {"\033[1~", KeyEvent{ui.Home, 0}}, {"\033[11~", KeyEvent{ui.F1, 0}}, // CSI-sequence key with three arguments and ending in '~'. The first // argument is always 27, the second identifies the modifier and the last // identifies the key. {"\033[27;4;63~", KeyEvent{';', ui.Shift | ui.Alt}}, } func TestKey(t *testing.T) { for _, test := range keyTests { theWriter.WriteString(test.input) select { case event := <-theReader.EventChan(): if event != test.want { t.Errorf("Reader reads event %v, want %v", event, test.want) } case <-timeout(): t.Errorf("Reader timed out") } } } // TestStopMakesUnderlyingFileAvailable tests that after calling Stop, the // Reader no longer attempts to read from the underlying file, so it is // available for use by others. func TestStopMakesUnderlyingFileAvailable(t *testing.T) { theReader.Stop() defer theReader.Start() s := "lorem ipsum" theWriter.WriteString(s) gotChan := make(chan string) go func() { var buf [32]byte nr, err := innerReader.Read(buf[:]) if err != nil { t.Errorf("inner.Read returns error: %v", err) } gotChan <- string(buf[:nr]) }() select { case got := <-gotChan: if got != s { t.Errorf("got %q, want %q", got, s) } case <-time.After(time.Second): t.Error("inner.Read times out") } } // TestStartAfterStopIndeedStarts tests that calling Start very shortly after // Stop puts the Reader the correct started state. func TestStartAfterStopIndeedStarts(t *testing.T) { for i := 0; i < 100; i++ { theReader.Stop() theReader.Start() theWriter.WriteString("a") select { case event := <-theReader.EventChan(): wantEvent := KeyEvent(ui.Key{'a', 0}) if event != wantEvent { t.Errorf("After Stop and Start, Reader reads %v, want %v", event, wantEvent) } case <-timeout(): t.Errorf("After Stop and Start, Reader timed out") } } } elvish-0.11+ds1/edit/tty/reader_windows.go000066400000000000000000000117631323000013700205150ustar00rootroot00000000000000package tty import ( "errors" "fmt" "log" "os" "time" "github.com/elves/elvish/edit/ui" "github.com/elves/elvish/sys" "golang.org/x/sys/windows" ) // XXX Put here to make edit package build on Windows. const DefaultSeqTimeout = 10 * time.Millisecond type reader struct { console windows.Handle eventChan chan Event stopEvent windows.Handle stopChan chan struct{} stopAckChan chan struct{} } // NewReader creates a new Reader instance. func newReader(file *os.File) Reader { console, err := windows.GetStdHandle(windows.STD_INPUT_HANDLE) if err != nil { panic(fmt.Errorf("GetStdHandle(STD_INPUT_HANDLE): %v", err)) } stopEvent, err := windows.CreateEvent(nil, 0, 0, nil) if err != nil { panic(fmt.Errorf("CreateEvent: %v", err)) } return &reader{ console, make(chan Event), stopEvent, nil, nil} } func (r *reader) SetRaw(bool) { // NOP on Windows. } func (r *reader) EventChan() <-chan Event { return r.eventChan } func (r *reader) Start() { r.stopChan = make(chan struct{}) r.stopAckChan = make(chan struct{}) go r.run() } var errNr0 = errors.New("ReadConsoleInput reads 0 input") func (r *reader) run() { handles := []windows.Handle{r.console, r.stopEvent} for { triggered, _, err := sys.WaitForMultipleObjects(handles, false, sys.INFINITE) if err != nil { r.fatal(err) return } if triggered == 1 { <-r.stopChan close(r.stopAckChan) return } var buf [1]sys.InputRecord nr, err := sys.ReadConsoleInput(r.console, buf[:]) if nr == 0 { r.fatal(errNr0) return } if err != nil { r.fatal(err) return } event := convertEvent(buf[0].GetEvent()) if event != nil { r.send(event) } } } func (r *reader) nonFatal(err error) { r.send(NonfatalErrorEvent{err}) } func (r *reader) fatal(err error) { if !r.send(FatalErrorEvent{err}) { <-r.stopChan close(r.stopAckChan) r.resetStopEvent() } } func (r *reader) send(event Event) (stopped bool) { select { case r.eventChan <- event: return false case <-r.stopChan: close(r.stopAckChan) r.resetStopEvent() return true } } func (r *reader) resetStopEvent() { err := windows.ResetEvent(r.stopEvent) if err != nil { panic(err) } } func (r *reader) Stop() { err := windows.SetEvent(r.stopEvent) if err != nil { log.Println("SetEvent:", err) } close(r.stopChan) <-r.stopAckChan } func (r *reader) Close() { err := windows.CloseHandle(r.stopEvent) if err != nil { log.Println("Closing stopEvent handle for reader:", err) } close(r.eventChan) } // A subset of virtual key codes listed in // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx var keyCodeToRune = map[uint16]rune{ 0x08: ui.Backspace, 0x09: ui.Tab, 0x0d: ui.Enter, 0x1b: '\x1b', 0x20: ' ', 0x23: ui.End, 0x24: ui.Home, 0x25: ui.Left, 0x26: ui.Up, 0x27: ui.Right, 0x28: ui.Down, 0x2d: ui.Insert, 0x2e: ui.Delete, /* 0x30 - 0x39: digits, same with ASCII */ /* 0x41 - 0x5a: letters, same with ASCII */ /* 0x60 - 0x6f: numpads; currently ignored */ 0x70: ui.F1, 0x71: ui.F2, 0x72: ui.F3, 0x73: ui.F4, 0x74: ui.F5, 0x75: ui.F6, 0x76: ui.F7, 0x77: ui.F8, 0x78: ui.F9, 0x79: ui.F10, 0x7a: ui.F11, 0x7b: ui.F12, /* 0x7c - 0x87: F13 - F24; currently ignored */ 0xba: ';', 0xbb: '=', 0xbc: ',', 0xbd: '-', 0xbe: '.', 0xbf: '/', 0xc0: '`', 0xdb: '[', 0xdc: '\\', 0xdd: ']', 0xde: '\'', } // A subset of constants listed in // https://docs.microsoft.com/en-us/windows/console/key-event-record-str const ( leftAlt = 0x02 leftCtrl = 0x08 rightAlt = 0x01 rightCtrl = 0x04 shift = 0x10 ) // convertEvent converts the native sys.InputEvent type to a suitable Event // type. It returns nil if the event should be ignored. func convertEvent(event sys.InputEvent) Event { switch event := event.(type) { case *sys.KeyEvent: if event.BKeyDown == 0 { // Ignore keyup events. return nil } r := rune(event.UChar[0]) + rune(event.UChar[1])<<8 if event.DwControlKeyState == 0 { // No modifier // TODO: Deal with surrogate pairs if 0x20 <= r && r != 0x7f { return KeyEvent(ui.Key{Rune: r}) } } else if event.DwControlKeyState == shift { // If only the shift is held down, we try and see if this is a // non-functional key by looking if the rune generated is a // printable ASCII character. if 0x20 <= r && r < 0x7f { return KeyEvent(ui.Key{Rune: r}) } } r = convertRune(event.WVirtualKeyCode) if r == 0 { return nil } mod := convertMod(event.DwControlKeyState) return KeyEvent(ui.Key{Rune: r, Mod: mod}) //case *sys.MouseEvent: //case *sys.WindowBufferSizeEvent: default: // Other events are ignored. return nil } } func convertRune(keyCode uint16) rune { r, ok := keyCodeToRune[keyCode] if ok { return r } if '0' <= keyCode && keyCode <= '9' || 'A' <= keyCode && keyCode <= 'Z' { return rune(keyCode) } return 0 } func convertMod(state uint32) ui.Mod { mod := ui.Mod(0) if state&(leftAlt|rightAlt) != 0 { mod |= ui.Alt } if state&(leftCtrl|rightCtrl) != 0 { mod |= ui.Ctrl } if state&shift != 0 { mod |= ui.Shift } return mod } elvish-0.11+ds1/edit/tty/rune_reader_unix.go000066400000000000000000000061031323000013700210270ustar00rootroot00000000000000// +build !windows,!plan9 package tty import ( "fmt" "os" "syscall" "github.com/elves/elvish/sys" ) const ( runeReaderChanSize int = 128 ) // runeReader reads a Unix file continuously, assemble the bytes it reads into // runes (assuming UTF-8), and delivers them on a channel. type runeReader struct { file *os.File rStop *os.File wStop *os.File stopChan chan struct{} runeChan chan rune errorChan chan error debug bool } // newRuneReader creates a new runeReader from a file. func newRuneReader(file *os.File) *runeReader { rStop, wStop, err := os.Pipe() if err != nil { panic(err) } return &runeReader{ file, rStop, wStop, make(chan struct{}), make(chan rune, runeReaderChanSize), make(chan error), false, } } // Chan returns a channel onto which the runeReader writes the runes it reads. func (ar *runeReader) Chan() <-chan rune { return ar.runeChan } // ErrorChan returns a channel onto which the runeReader writes the errors it // encounters. func (ar *runeReader) ErrorChan() <-chan error { return ar.errorChan } // Start starts the runeReader. func (ar *runeReader) Start() { go ar.run() } // run runs the runeReader. It blocks until Quit is called and should be called // in a separate goroutine. func (ar *runeReader) run() { var buf [1]byte for { ready, err := sys.WaitForRead(ar.file, ar.rStop) if err != nil { if err == syscall.EINTR { continue } ar.fatal(err) return } if ready[1] { // Consume the written byte ar.rStop.Read(buf[:]) <-ar.stopChan return } nr, err := ar.file.Read(buf[:]) if nr != 1 { continue } else if err != nil { ar.fatal(err) return } leader := buf[0] var ( r rune pending int ) switch { case leader>>7 == 0: r = rune(leader) case leader>>5 == 0x6: r = rune(leader & 0x1f) pending = 1 case leader>>4 == 0xe: r = rune(leader & 0xf) pending = 2 case leader>>3 == 0x1e: r = rune(leader & 0x7) pending = 3 } if ar.debug { fmt.Printf("leader 0x%x, pending %d, r = 0x%x\n", leader, pending, r) } for i := 0; i < pending; i++ { nr, err := ar.file.Read(buf[:]) if nr != 1 { r = 0xfffd break } else if err != nil { ar.fatal(err) return } r = r<<6 + rune(buf[0]&0x3f) if ar.debug { fmt.Printf(" got 0x%d, r = 0x%x\n", buf[0], r) } } // Write rune to ch, unless termination is requested. select { case ar.runeChan <- r: case <-ar.stopChan: ar.rStop.Read(buf[:]) return } } } func (ar *runeReader) fatal(err error) { var cBuf [1]byte select { case ar.errorChan <- err: case <-ar.stopChan: ar.rStop.Read(cBuf[:]) return } <-ar.stopChan ar.rStop.Read(cBuf[:]) } // Stop terminates the loop of Run. func (ar *runeReader) Stop() { _, err := ar.wStop.Write([]byte{'q'}) if err != nil { panic(err) } ar.stopChan <- struct{}{} } // Close releases files and channels associated with the AsyncReader. It does // not close the file used to create it. func (ar *runeReader) Close() { ar.rStop.Close() ar.wStop.Close() close(ar.stopChan) close(ar.runeChan) } elvish-0.11+ds1/edit/tty/rune_reader_unix_test.go000066400000000000000000000042241323000013700220700ustar00rootroot00000000000000// +build !windows,!plan9 package tty import ( "fmt" "math/rand" "os" "testing" "time" "github.com/elves/elvish/sys" ) // Pretty arbitrary numbers. May not reveal deadlocks on all machines. var ( DeadlockNWrite = 1024 DeadlockRun = 64 DeadlockTimeout = 500 * time.Millisecond DeadlockMaxJitter = time.Millisecond ) func jitter() { time.Sleep(time.Duration(float64(DeadlockMaxJitter) * rand.Float64())) } // stopTester tries to trigger a potential race condition where // (*RuneReader).Stop deadlocks and blocks forever. It inserts random jitters to // try to trigger race condition. func stopTester() { r, w, err := os.Pipe() if err != nil { panic(err) } defer r.Close() defer w.Close() ar := newRuneReader(r) defer ar.Close() fmt.Fprintf(w, "%*s", DeadlockNWrite, "") go func() { jitter() ar.Start() }() jitter() // Is there a deadlock that makes this call block indefinitely. ar.Stop() } func TestRuneReaderStopAlwaysStops(t *testing.T) { isatty := sys.IsATTY(os.Stdin) rand.Seed(time.Now().UTC().UnixNano()) timer := time.NewTimer(DeadlockTimeout) for i := 0; i < DeadlockRun; i++ { if isatty { fmt.Printf("\r%d/%d ", i+1, DeadlockRun) } done := make(chan bool) go func() { stopTester() close(done) }() select { case <-done: // no deadlock trigerred case <-timer.C: // deadlock t.Errorf("%s", sys.DumpStack()) t.Fatalf("AsyncReader deadlock trigerred on run %d/%d, stack trace:\n%s", i, DeadlockRun, sys.DumpStack()) } timer.Reset(DeadlockTimeout) } if isatty { fmt.Print("\r \r") } } var ReadTimeout = time.Second func TestRuneReader(t *testing.T) { r, w, err := os.Pipe() if err != nil { panic(err) } defer r.Close() defer w.Close() ar := newRuneReader(r) defer ar.Close() ar.Start() go func() { var i rune for i = 0; i <= 1280; i += 10 { w.WriteString(string(i)) } }() var i rune timer := time.NewTimer(ReadTimeout) for i = 0; i <= 1280; i += 10 { select { case r := <-ar.Chan(): if r != i { t.Fatalf("expect %q, got %q\n", i, r) } case <-timer.C: t.Fatalf("read timeout (i = %d)", i) } timer.Reset(ReadTimeout) } ar.Stop() } elvish-0.11+ds1/edit/tty/setup.go000066400000000000000000000045341323000013700166370ustar00rootroot00000000000000package tty import ( "fmt" "os" "github.com/elves/elvish/sys" "github.com/elves/elvish/util" ) // Setup sets up the terminal so that it is suitable for the Reader and // Writer to use. It returns a function that can be used to restore the // original terminal config. func Setup(in, out *os.File) (func() error, error) { return setup(in, out) } const ( lackEOLRune = '\u23ce' lackEOL = "\033[7m" + string(lackEOLRune) + "\033[m" enableSGRMouse = false ) // setupVT performs setup for VT-like terminals. func setupVT(out *os.File) error { _, width := sys.GetWinsize(out) s := "" /* Write a lackEOLRune if the cursor is not in the leftmost column. This is done as follows: 1. Turn on autowrap; 2. Write lackEOL along with enough padding, so that the total width is equal to the width of the screen. If the cursor was in the first column, we are still in the same line, just off the line boundary. Otherwise, we are now in the next line. 3. Rewind to the first column, write one space and rewind again. If the cursor was in the first column to start with, we have just erased the LackEOL character. Otherwise, we are now in the next line and this is a no-op. The LackEOL character remains. */ s += fmt.Sprintf("\033[?7h%s%*s\r \r", lackEOL, width-util.Wcwidth(lackEOLRune), "") /* Turn off autowrap. The terminals sometimes has different opinions about how wide some characters are (notably emojis and some dingbats) with elvish. When that happens, elvish becomes wrong about where the cursor is when it writes its output, and the effect can be disastrous. If we turn off autowrap, the terminal won't insert any newlines behind the scene, so elvish is always right about which line the cursor is. With a bit more caution, this can restrict the consequence of the mismatch within one line. */ s += "\033[?7l" // Turn on SGR-style mouse tracking. if enableSGRMouse { s += "\033[?1000;1006h" } // Enable bracketed paste. s += "\033[?2004h" _, err := out.WriteString(s) return err } // restoreVT performs restore for VT-like terminals. func restoreVT(out *os.File) error { s := "" // Turn on autowrap. s += "\033[?7h" // Turn off mouse tracking. if enableSGRMouse { s += "\033[?1000;1006l" } // Disable bracketed paste. s += "\033[?2004l" _, err := out.WriteString(s) return err } elvish-0.11+ds1/edit/tty/setup_unix.go000066400000000000000000000024171323000013700177000ustar00rootroot00000000000000// +build !windows,!plan9 package tty import ( "fmt" "os" "github.com/elves/elvish/sys" "github.com/elves/elvish/util" ) const flushInputDuringSetup = false func setup(in, out *os.File) (func() error, error) { // On Unix, use input file for changing termios. All fds pointing to the // same terminal are equivalent. fd := int(in.Fd()) term, err := sys.NewTermiosFromFd(fd) if err != nil { return nil, fmt.Errorf("can't get terminal attribute: %s", err) } savedTermios := term.Copy() term.SetICanon(false) term.SetEcho(false) term.SetVMin(1) term.SetVTime(0) // Enforcing crnl translation on readline. Assuming user won't set // inlcr or -onlcr, otherwise we have to hardcode all of them here. term.SetICRNL(true) err = term.ApplyToFd(fd) if err != nil { return nil, fmt.Errorf("can't set up terminal attribute: %s", err) } var errFlushInput error if flushInputDuringSetup { err = sys.FlushInput(fd) if err != nil { errFlushInput = fmt.Errorf("can't flush input: %s", err) } } var errSetupVT error err = setupVT(out) if err != nil { errSetupVT = fmt.Errorf("can't setup VT: %s", err) } restore := func() error { return util.Errors(savedTermios.ApplyToFd(fd), restoreVT(out)) } return restore, util.Errors(errFlushInput, errSetupVT) } elvish-0.11+ds1/edit/tty/setup_unix_test.go000066400000000000000000000007621323000013700207400ustar00rootroot00000000000000// +build !windows,plan9 package tty import ( "testing" "github.com/kr/pty" ) func TestSetupTerminal(t *testing.T) { pty, tty, err := pty.Open() if err != nil { t.Errorf("cannot open pty for testing setupTerminal") } defer pty.Close() defer tty.Close() _, err = setup(tty) if err != nil { t.Errorf("setupTerminal returns an error") } // TODO(xiaq): Test whether the interesting flags in the termios were indeed // set. // termios, err := sys.NewTermiosFromFd(int(tty.Fd())) } elvish-0.11+ds1/edit/tty/setup_windows.go000066400000000000000000000017121323000013700204040ustar00rootroot00000000000000package tty import ( "os" "github.com/elves/elvish/util" "golang.org/x/sys/windows" ) const ( wantedInMode = windows.ENABLE_WINDOW_INPUT | windows.ENABLE_MOUSE_INPUT | windows.ENABLE_PROCESSED_INPUT wantedOutMode = windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING ) func setup(in, out *os.File) (func() error, error) { hIn := windows.Handle(in.Fd()) hOut := windows.Handle(out.Fd()) var oldInMode, oldOutMode uint32 err := windows.GetConsoleMode(hIn, &oldInMode) if err != nil { return nil, err } err = windows.GetConsoleMode(hOut, &oldOutMode) if err != nil { return nil, err } errSetIn := windows.SetConsoleMode(hIn, wantedInMode) errSetOut := windows.SetConsoleMode(hOut, wantedOutMode) errVT := setupVT(out) return func() error { return util.Errors( windows.SetConsoleMode(hIn, oldInMode), windows.SetConsoleMode(hOut, oldOutMode), restoreVT(out)) }, util.Errors(errSetIn, errSetOut, errVT) } elvish-0.11+ds1/edit/tty/tty.go000066400000000000000000000002451323000013700163120ustar00rootroot00000000000000// Package tty provides terminal functionality for the Elvish editor. package tty import "github.com/elves/elvish/util" var logger = util.GetLogger("[edit/tty] ") elvish-0.11+ds1/edit/tty/writer.go000066400000000000000000000101531323000013700170050ustar00rootroot00000000000000package tty import ( "bytes" "fmt" "os" "github.com/elves/elvish/edit/ui" ) var logWriterDetail = false type Writer interface { // CurrentBuffer returns the current buffer. CurrentBuffer() *ui.Buffer // ResetCurrentBuffer resets the current buffer. ResetCurrentBuffer() // CommitBuffer updates the terminal display to reflect current buffer. CommitBuffer(bufNoti, buf *ui.Buffer, fullRefresh bool) error } // writer renders the editor UI. type writer struct { file *os.File curBuf *ui.Buffer } func NewWriter(f *os.File) Writer { return &writer{f, &ui.Buffer{}} } // CurrentBuffer returns the current buffer. func (w *writer) CurrentBuffer() *ui.Buffer { return w.curBuf } // ResetCurrentBuffer resets the current buffer. func (w *writer) ResetCurrentBuffer() { w.curBuf = &ui.Buffer{} } // deltaPos calculates the escape sequence needed to move the cursor from one // position to another. It use relative movements to move to the destination // line and absolute movement to move to the destination column. func deltaPos(from, to ui.Pos) []byte { buf := new(bytes.Buffer) if from.Line < to.Line { // move down fmt.Fprintf(buf, "\033[%dB", to.Line-from.Line) } else if from.Line > to.Line { // move up fmt.Fprintf(buf, "\033[%dA", from.Line-to.Line) } fmt.Fprint(buf, "\r") if to.Col > 0 { fmt.Fprintf(buf, "\033[%dC", to.Col) } return buf.Bytes() } // CommitBuffer updates the terminal display to reflect current buffer. func (w *writer) CommitBuffer(bufNoti, buf *ui.Buffer, fullRefresh bool) error { if buf.Width != w.curBuf.Width && w.curBuf.Lines != nil { // Width change, force full refresh w.curBuf.Lines = nil fullRefresh = true } bytesBuf := new(bytes.Buffer) // Hide cursor. bytesBuf.WriteString("\033[?25l") // Rewind cursor if pLine := w.curBuf.Dot.Line; pLine > 0 { fmt.Fprintf(bytesBuf, "\033[%dA", pLine) } bytesBuf.WriteString("\r") if fullRefresh { // Do an erase. bytesBuf.WriteString("\033[J") } // style of last written cell. style := "" switchStyle := func(newstyle string) { if newstyle != style { fmt.Fprintf(bytesBuf, "\033[0;%sm", newstyle) style = newstyle } } writeCells := func(cs []ui.Cell) { for _, c := range cs { if c.Width > 0 { switchStyle(c.Style) } bytesBuf.WriteString(c.Text) } } if bufNoti != nil { if logWriterDetail { logger.Printf("going to write %d lines of notifications", len(bufNoti.Lines)) } // Write notifications for _, line := range bufNoti.Lines { writeCells(line) switchStyle("") bytesBuf.WriteString("\033[K\n") } // XXX Hacky. if len(w.curBuf.Lines) > 0 { w.curBuf.Lines = w.curBuf.Lines[1:] } } if logWriterDetail { logger.Printf("going to write %d lines, oldBuf had %d", len(buf.Lines), len(w.curBuf.Lines)) } for i, line := range buf.Lines { if i > 0 { bytesBuf.WriteString("\n") } var j int // First column where buf and oldBuf differ // No need to update current line if !fullRefresh && i < len(w.curBuf.Lines) { var eq bool if eq, j = ui.CompareCells(line, w.curBuf.Lines[i]); eq { continue } } // Move to the first differing column if necessary. firstCol := ui.CellsWidth(line[:j]) if firstCol != 0 { fmt.Fprintf(bytesBuf, "\033[%dC", firstCol) } // Erase the rest of the line if necessary. if !fullRefresh && i < len(w.curBuf.Lines) && j < len(w.curBuf.Lines[i]) { switchStyle("") bytesBuf.WriteString("\033[K") } writeCells(line[j:]) } if len(w.curBuf.Lines) > len(buf.Lines) && !fullRefresh { // If the old buffer is higher, erase old content. // Note that we cannot simply write \033[J, because if the cursor is // just over the last column -- which is precisely the case if we have a // rprompt, \033[J will also erase the last column. switchStyle("") bytesBuf.WriteString("\n\033[J\033[A") } switchStyle("") cursor := buf.Cursor() bytesBuf.Write(deltaPos(cursor, buf.Dot)) // Show cursor. bytesBuf.WriteString("\033[?25h") if logWriterDetail { logger.Printf("going to write %q", bytesBuf.String()) } _, err := w.file.Write(bytesBuf.Bytes()) if err != nil { return err } w.curBuf = buf return nil } elvish-0.11+ds1/edit/ui/000077500000000000000000000000001323000013700147375ustar00rootroot00000000000000elvish-0.11+ds1/edit/ui/buffer.go000066400000000000000000000131221323000013700165360ustar00rootroot00000000000000package ui import ( "strings" "github.com/elves/elvish/util" ) // Cell is an indivisible unit on the screen. It is not necessarily 1 column // wide. type Cell struct { Text string Width byte Style string } // Pos is the position within a buffer. type Pos struct { Line, Col int } // CellsWidth returns the total width of a Cell slice. func CellsWidth(cs []Cell) int { w := 0 for _, c := range cs { w += int(c.Width) } return w } // CompareCells returns whether two Cell slices are equal, and when they are // not, the first index at which they differ. func CompareCells(r1, r2 []Cell) (bool, int) { for i, c := range r1 { if i >= len(r2) || c != r2[i] { return false, i } } if len(r1) < len(r2) { return false, len(r1) } return true, 0 } // Buffer reflects a continuous range of lines on the terminal. // // The Unix terminal API provides only awkward ways of querying the terminal // Buffer, so we keep an internal reflection and do one-way synchronizations // (Buffer -> terminal, and not the other way around). This requires us to // exactly match the terminal's idea of the width of characters (wcwidth) and // where to insert soft carriage returns, so there could be bugs. type Buffer struct { Width, Col, Indent int // EagerWrap controls whether to wrap line as soon as the cursor reaches the // right edge of the terminal. This is not often desirable as it creates // unneessary line breaks, but is is useful when echoing the user input. // will otherwise EagerWrap bool // Lines the content of the buffer. Lines [][]Cell // Dot is what the user perceives as the cursor. Dot Pos } // NewBuffer builds a new buffer, with one empty line. func NewBuffer(width int) *Buffer { return &Buffer{Width: width, Lines: [][]Cell{make([]Cell, 0, width)}} } func (b *Buffer) SetIndent(indent int) *Buffer { b.Indent = indent return b } func (b *Buffer) SetEagerWrap(v bool) *Buffer { b.EagerWrap = v return b } func (b *Buffer) SetLines(lines ...[]Cell) *Buffer { b.Lines = lines b.Col = CellsWidth(lines[len(lines)-1]) return b } func (b *Buffer) SetDot(dot Pos) *Buffer { b.Dot = dot return b } // Cursor returns the current position of the cursor. func (b *Buffer) Cursor() Pos { return Pos{len(b.Lines) - 1, b.Col} } // BuffersHeight computes the combined height of a number of buffers. func BuffersHeight(bufs ...*Buffer) (l int) { for _, buf := range bufs { if buf != nil { l += len(buf.Lines) } } return } // Low level buffer mutations. func (b *Buffer) appendLine() { b.Lines = append(b.Lines, make([]Cell, 0, b.Width)) b.Col = 0 } func (b *Buffer) appendCell(c Cell) { n := len(b.Lines) b.Lines[n-1] = append(b.Lines[n-1], c) b.Col += int(c.Width) } // High-level buffer mutations. // Newline starts a newline. func (b *Buffer) Newline() { b.appendLine() if b.Indent > 0 { for i := 0; i < b.Indent; i++ { b.appendCell(Cell{Text: " ", Width: 1}) } } } var styleForControlChar = Styles{"inverse"} // Write writes a single rune to a buffer, wrapping the line when needed. If the // rune is a control character, it will be written using the caret notation // (like ^X) and gets the additional style of styleForControlChar. func (b *Buffer) Write(r rune, style string) { if r == '\n' { b.Newline() return } wd := util.Wcwidth(r) c := Cell{string(r), byte(wd), style} if r < 0x20 || r == 0x7f { wd = 2 if style != "" { style = style + ";" + styleForControlChar.String() } else { style = styleForControlChar.String() } c = Cell{"^" + string(r^0x40), 2, style} } if b.Col+wd > b.Width { b.Newline() b.appendCell(c) } else { b.appendCell(c) if b.Col == b.Width && b.EagerWrap { b.Newline() } } } // WriteString writes a string to a buffer, with one style. func (b *Buffer) WriteString(text, style string) { for _, r := range text { b.Write(r, style) } } // WriteSpaces writes w spaces. func (b *Buffer) WriteSpaces(w int, style string) { b.WriteString(strings.Repeat(" ", w), style) } // WriteStyleds writes a slice of styled structs. func (b *Buffer) WriteStyleds(ss []*Styled) { for _, s := range ss { b.WriteString(s.Text, s.Styles.String()) } } // TrimToLines trims a buffer to the lines [low, high). func (b *Buffer) TrimToLines(low, high int) { for i := 0; i < low; i++ { b.Lines[i] = nil } for i := high; i < len(b.Lines); i++ { b.Lines[i] = nil } b.Lines = b.Lines[low:high] b.Dot.Line -= low if b.Dot.Line < 0 { b.Dot.Line = 0 } } // Extend adds all lines from b2 to the bottom of this buffer. If moveDot is // true, the dot is updated to match the dot of b2. func (b *Buffer) Extend(b2 *Buffer, moveDot bool) { if b2 != nil && b2.Lines != nil { if moveDot { b.Dot.Line = b2.Dot.Line + len(b.Lines) b.Dot.Col = b2.Dot.Col } b.Lines = append(b.Lines, b2.Lines...) b.Col = b2.Col } } // ExtendRight extends b to the right. It pads each line in b to be at least of // width w and appends the corresponding line in b2 to it, making new lines in b // when b2 has more lines than b. // BUG(xiaq): after calling ExtendRight, the widths of some lines can exceed // b.width. func (b *Buffer) ExtendRight(b2 *Buffer, w int) { i := 0 for ; i < len(b.Lines) && i < len(b2.Lines); i++ { if w0 := CellsWidth(b.Lines[i]); w0 < w { b.Lines[i] = append(b.Lines[i], makeSpacing(w-w0)...) } b.Lines[i] = append(b.Lines[i], b2.Lines[i]...) } for ; i < len(b2.Lines); i++ { row := append(makeSpacing(w), b2.Lines[i]...) b.Lines = append(b.Lines, row) } b.Col = CellsWidth(b.Lines[len(b.Lines)-1]) } func makeSpacing(n int) []Cell { s := make([]Cell, n) for i := 0; i < n; i++ { s[i].Text = " " s[i].Width = 1 } return s } elvish-0.11+ds1/edit/ui/buffer_test.go000066400000000000000000000203651323000013700176040ustar00rootroot00000000000000package ui import ( "reflect" "testing" ) var cellsWidthTests = []struct { cells []Cell wantWidth int }{ {[]Cell{}, 0}, {[]Cell{{"a", 1, ""}, {"好", 2, ""}}, 3}, } func TestCellsWidth(t *testing.T) { for _, test := range cellsWidthTests { if width := CellsWidth(test.cells); width != test.wantWidth { t.Errorf("cellsWidth(%v) = %v, want %v", test.cells, width, test.wantWidth) } } } var makeSpacingTests = []struct { n int want []Cell }{ {0, []Cell{}}, {1, []Cell{{" ", 1, ""}}}, {4, []Cell{{" ", 1, ""}, {" ", 1, ""}, {" ", 1, ""}, {" ", 1, ""}}}, } func TestMakeSpacing(t *testing.T) { for _, test := range makeSpacingTests { if got := makeSpacing(test.n); !reflect.DeepEqual(got, test.want) { t.Errorf("makeSpacing(%v) = %v, want %v", test.n, got, test.want) } } } var compareCellsTests = []struct { cells1 []Cell cells2 []Cell wantEqual bool wantIndex int }{ {[]Cell{}, []Cell{}, true, 0}, {[]Cell{}, []Cell{{"a", 1, ""}}, false, 0}, { []Cell{{"a", 1, ""}, {"好", 2, ""}, {"b", 1, ""}}, []Cell{{"a", 1, ""}, {"好", 2, ""}, {"c", 1, ""}}, false, 2, }, { []Cell{{"a", 1, ""}, {"好", 2, ""}, {"b", 1, ""}}, []Cell{{"a", 1, ""}, {"好", 2, "1"}, {"c", 1, ""}}, false, 1, }, } func TestCompareCells(t *testing.T) { for _, test := range compareCellsTests { equal, index := CompareCells(test.cells1, test.cells2) if equal != test.wantEqual || index != test.wantIndex { t.Errorf("compareCells(%v, %v) = (%v, %v), want (%v, %v)", test.cells1, test.cells2, equal, index, test.wantEqual, test.wantIndex) } } } var bufferCursorTests = []struct { buf *Buffer want Pos }{ {NewBuffer(10), Pos{0, 0}}, {NewBuffer(10).SetLines([]Cell{{"a", 1, ""}}, []Cell{{"好", 2, ""}}), Pos{1, 2}}, } func TestBufferCursor(t *testing.T) { for _, test := range bufferCursorTests { if p := test.buf.Cursor(); p != test.want { t.Errorf("(%v).cursor() = %v, want %v", test.buf, p, test.want) } } } var buffersHeighTests = []struct { buffers []*Buffer want int }{ {nil, 0}, {[]*Buffer{NewBuffer(10)}, 1}, {[]*Buffer{ NewBuffer(10).SetLines([]Cell{}, []Cell{}), NewBuffer(10), NewBuffer(10).SetLines([]Cell{}, []Cell{})}, 5}, } func TestBuffersHeight(t *testing.T) { for _, test := range buffersHeighTests { if h := BuffersHeight(test.buffers...); h != test.want { t.Errorf("buffersHeight(%v...) = %v, want %v", test.buffers, h, test.want) } } } var bufferWritesTests = []struct { buf *Buffer text string style string want *Buffer }{ // Writing nothing. {NewBuffer(10), "", "", NewBuffer(10)}, // Writing a single rune. {NewBuffer(10), "a", "1", NewBuffer(10).SetLines([]Cell{{"a", 1, "1"}})}, // Writing control character. {NewBuffer(10), "\033", "", NewBuffer(10).SetLines( []Cell{{"^[", 2, styleForControlChar.String()}}, )}, // Writing styled control character. {NewBuffer(10), "a\033b", "1", NewBuffer(10).SetLines( []Cell{ {"a", 1, "1"}, {"^[", 2, "1;" + styleForControlChar.String()}, {"b", 1, "1"}, }, )}, // Writing text containing a newline. {NewBuffer(10), "a\nb", "1", NewBuffer(10).SetLines( []Cell{{"a", 1, "1"}}, []Cell{{"b", 1, "1"}}, )}, // Writing text containing a newline when there is indent. {NewBuffer(10).SetIndent(2), "a\nb", "1", NewBuffer(10).SetIndent(2).SetLines( []Cell{{"a", 1, "1"}}, []Cell{{" ", 1, ""}, {" ", 1, ""}, {"b", 1, "1"}}, )}, // Writing long text that triggers wrapping. {NewBuffer(4), "aaaab", "1", NewBuffer(4).SetLines( []Cell{{"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}}, []Cell{{"b", 1, "1"}}, )}, // Writing long text that triggers wrapping when there is indent. {NewBuffer(4).SetIndent(2), "aaaab", "1", NewBuffer(4).SetIndent(2).SetLines( []Cell{{"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}}, []Cell{{" ", 1, ""}, {" ", 1, ""}, {"b", 1, "1"}}, )}, // Writing long text that triggers eager wrapping. {NewBuffer(4).SetIndent(2).SetEagerWrap(true), "aaaa", "1", NewBuffer(4).SetIndent(2).SetEagerWrap(true).SetLines( []Cell{{"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}}, []Cell{{" ", 1, ""}, {" ", 1, ""}}, )}, } // TestBufferWrites tests buffer.writes by calling writes on a buffer and see if // the buffer matches what is expected. func TestBufferWrites(t *testing.T) { for _, test := range bufferWritesTests { b := test.buf b.WriteString(test.text, test.style) if !reflect.DeepEqual(b, test.want) { t.Errorf("buf.writes(%q, %q) makes it %v, want %v", test.text, test.style, b, test.want) } } } var bufferTrimToLinesTests = []struct { buf *Buffer low int high int want *Buffer }{ { NewBuffer(10).SetLines( []Cell{{"a", 1, ""}}, []Cell{{"b", 1, ""}}, []Cell{{"c", 1, ""}}, []Cell{{"d", 1, ""}}, ), 0, 2, NewBuffer(10).SetLines( []Cell{{"a", 1, ""}}, []Cell{{"b", 1, ""}}, ), }, // With dot. { NewBuffer(10).SetLines( []Cell{{"a", 1, ""}}, []Cell{{"b", 1, ""}}, []Cell{{"c", 1, ""}}, []Cell{{"d", 1, ""}}, ).SetDot(Pos{1, 1}), 1, 3, NewBuffer(10).SetLines( []Cell{{"b", 1, ""}}, []Cell{{"c", 1, ""}}, ).SetDot(Pos{0, 1}), }, // With dot that is going to be trimmed away. { NewBuffer(10).SetLines( []Cell{{"a", 1, ""}}, []Cell{{"b", 1, ""}}, []Cell{{"c", 1, ""}}, []Cell{{"d", 1, ""}}, ).SetDot(Pos{0, 1}), 1, 3, NewBuffer(10).SetLines( []Cell{{"b", 1, ""}}, []Cell{{"c", 1, ""}}, ).SetDot(Pos{0, 1}), }, } func TestBufferTrimToLines(t *testing.T) { for _, test := range bufferTrimToLinesTests { b := test.buf b.TrimToLines(test.low, test.high) if !reflect.DeepEqual(b, test.want) { t.Errorf("buf.trimToLines(%v, %v) makes it %v, want %v", test.low, test.high, b, test.want) } } } var bufferExtendTests = []struct { buf *Buffer buf2 *Buffer moveDot bool want *Buffer }{ { NewBuffer(10).SetLines([]Cell{{"a", 1, ""}}, []Cell{{"b", 1, ""}}), NewBuffer(11).SetLines([]Cell{{"c", 1, ""}}, []Cell{{"d", 1, ""}}), false, NewBuffer(10).SetLines( []Cell{{"a", 1, ""}}, []Cell{{"b", 1, ""}}, []Cell{{"c", 1, ""}}, []Cell{{"d", 1, ""}}, ), }, // Moving dot. { NewBuffer(10).SetLines([]Cell{{"a", 1, ""}}, []Cell{{"b", 1, ""}}), NewBuffer(11).SetLines( []Cell{{"c", 1, ""}}, []Cell{{"d", 1, ""}}, ).SetDot(Pos{1, 1}), true, NewBuffer(10).SetLines( []Cell{{"a", 1, ""}}, []Cell{{"b", 1, ""}}, []Cell{{"c", 1, ""}}, []Cell{{"d", 1, ""}}, ).SetDot(Pos{3, 1}), }, } func TestExtend(t *testing.T) { for _, test := range bufferExtendTests { b := test.buf b.Extend(test.buf2, test.moveDot) if !reflect.DeepEqual(b, test.want) { t.Errorf("buf.extend(%v, %v) makes it %v, want %v", test.buf2, test.moveDot, b, test.want) } } } var bufferExtendRightTests = []struct { buf *Buffer buf2 *Buffer w int want *Buffer }{ // No padding, equal height. { NewBuffer(10).SetLines([]Cell{{"a", 1, ""}}, []Cell{}), NewBuffer(11).SetLines([]Cell{{"c", 1, ""}}, []Cell{{"d", 1, ""}}), 0, NewBuffer(10).SetLines( []Cell{{"a", 1, ""}, {"c", 1, ""}}, []Cell{{"d", 1, ""}}, ), }, // With padding. { NewBuffer(10).SetLines([]Cell{{"a", 1, ""}}, []Cell{{"b", 1, ""}}), NewBuffer(11).SetLines([]Cell{{"c", 1, ""}}, []Cell{{"d", 1, ""}}), 2, NewBuffer(10).SetLines( []Cell{{"a", 1, ""}, {" ", 1, ""}, {"c", 1, ""}}, []Cell{{"b", 1, ""}, {" ", 1, ""}, {"d", 1, ""}}, ), }, // buf is higher. { NewBuffer(10).SetLines( []Cell{{"a", 1, ""}}, []Cell{{"b", 1, ""}}, []Cell{{"x", 1, ""}}, ), NewBuffer(11).SetLines([]Cell{{"c", 1, ""}}, []Cell{{"d", 1, ""}}), 1, NewBuffer(10).SetLines( []Cell{{"a", 1, ""}, {"c", 1, ""}}, []Cell{{"b", 1, ""}, {"d", 1, ""}}, []Cell{{"x", 1, ""}}, ), }, // buf2 is higher. { NewBuffer(10).SetLines([]Cell{{"a", 1, ""}}, []Cell{{"b", 1, ""}}), NewBuffer(11).SetLines( []Cell{{"c", 1, ""}}, []Cell{{"d", 1, ""}}, []Cell{{"e", 1, ""}}, ), 1, NewBuffer(10).SetLines( []Cell{{"a", 1, ""}, {"c", 1, ""}}, []Cell{{"b", 1, ""}, {"d", 1, ""}}, []Cell{{" ", 1, ""}, {"e", 1, ""}}, ), }, } func TestExtendRight(t *testing.T) { for _, test := range bufferExtendRightTests { b := test.buf b.ExtendRight(test.buf2, test.w) if !reflect.DeepEqual(b, test.want) { t.Errorf("buf.extendRight(%v, %v) makes it %v, want %v", test.buf2, test.w, b, test.want) } } } elvish-0.11+ds1/edit/ui/key.go000066400000000000000000000124661323000013700160670ustar00rootroot00000000000000package ui import ( "bytes" "errors" "fmt" "strings" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" "github.com/xiaq/persistent/hash" ) var ErrKeyMustBeString = errors.New("key must be key or string value") // Key represents a single keyboard input, typically assembled from a escape // sequence. type Key struct { Rune rune Mod Mod } // Default is used in the key binding table to indicate default binding. var Default = Key{DefaultBindingRune, 0} // Mod represents a modifier key. type Mod byte // Values for Mod. const ( // Shift is the shift modifier. It is only applied to special keys (e.g. // Shift-F1). For instance 'A' and '@' which are typically entered with the // shift key pressed, are not considered to be shift-modified. Shift Mod = 1 << iota // Alt is the alt modifier, traditionally known as the meta modifier. Alt Ctrl ) const functionKeyOffset = 1000 // Special negative runes to represent function keys, used in the Rune field of // the Key struct. const ( // DefaultBindingRune is a special value to represent default binding. DefaultBindingRune rune = iota - functionKeyOffset F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 Up Down Right Left Home Insert Delete End PageUp PageDown // Some function key names are just aliases for their ASCII representation Tab = '\t' Enter = '\n' Backspace = 0x7f ) // functionKey stores the names of function keys, in the same order they appeared above. var functionKeyNames = [...]string{ "Default", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "Up", "Down", "Right", "Left", "Home", "Insert", "Delete", "End", "PageUp", "PageDown", } // keyNames stores the name of function keys with a positive rune. var keyNames = map[rune]string{ Tab: "Tab", Enter: "Enter", Backspace: "Backspace", } func (k Key) Kind() string { return "edit:Key" } func (k Key) Equal(other interface{}) bool { return k == other } func (k Key) Hash() uint32 { h := hash.DJBInit h = hash.DJBCombine(h, uint32(k.Rune)) h = hash.DJBCombine(h, uint32(k.Mod)) return h } func (k Key) Repr(int) string { return "(edit:key " + parse.Quote(k.String()) + ")" } func (k Key) String() string { var b bytes.Buffer if k.Mod&Ctrl != 0 { b.WriteString("Ctrl-") } if k.Mod&Alt != 0 { b.WriteString("Alt-") } if k.Mod&Shift != 0 { b.WriteString("Shift-") } if k.Rune > 0 { if name, ok := keyNames[k.Rune]; ok { b.WriteString(name) } else { b.WriteRune(k.Rune) } } else { i := int(k.Rune + functionKeyOffset) if i >= len(functionKeyNames) { fmt.Fprintf(&b, "(bad function key %d)", k.Rune) } else { b.WriteString(functionKeyNames[i]) } } return b.String() } // modifierByName maps a name to an modifier. It is used for parsing keys where // the modifier string is first turned to lower case, so that all of C, c, // CTRL, Ctrl and ctrl can represent the Ctrl modifier. var modifierByName = map[string]Mod{ "s": Shift, "shift": Shift, "a": Alt, "alt": Alt, "m": Alt, "meta": Alt, "c": Ctrl, "ctrl": Ctrl, } // parseKey parses a key. The syntax is: // // Key = { Mod ('+' | '-') } BareKey // // BareKey = FunctionKeyName | SingleRune func parseKey(s string) (Key, error) { var k Key // parse modifiers for { i := strings.IndexAny(s, "+-") if i == -1 { break } modname := strings.ToLower(s[:i]) mod, ok := modifierByName[modname] if !ok { return Key{}, fmt.Errorf("bad modifier: %q", modname) } k.Mod |= mod s = s[i+1:] } if len(s) == 1 { k.Rune = rune(s[0]) // XXX The following assumptions about keys with Ctrl are not checked // with all terminals. if k.Mod&Ctrl != 0 { // Keys with Ctrl as one of the modifiers and a single ASCII letter // as the base rune do not distinguish between cases. So we // normalize the base rune to upper case. if 'a' <= k.Rune && k.Rune <= 'z' { k.Rune += 'A' - 'a' } // Tab is equivalent to Ctrl-I and Ctrl-J is equivalent to Enter. // Normalize Ctrl-I to Tab and Ctrl-J to Enter. if k.Rune == 'I' { k.Mod &= ^Ctrl k.Rune = Tab } else if k.Rune == 'J' { k.Mod &= ^Ctrl k.Rune = Enter } } return k, nil } for r, name := range keyNames { if s == name { k.Rune = r return k, nil } } for i, name := range functionKeyNames { if s == name { k.Rune = rune(i - functionKeyOffset) return k, nil } } return Key{}, fmt.Errorf("bad key: %q", s) } // ToKey converts an Elvish Value to a Key. If the passed Value is not Key or // String, it throws an error. func ToKey(k types.Value) Key { switch k := k.(type) { case Key: return k case types.String: key, err := parseKey(string(k)) if err != nil { util.Throw(err) } return key default: util.Throw(ErrKeyMustBeString) panic("unreachable") } } // KeyBuiltin implements the edit:key builtin. func KeyBuiltin(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { var s types.String eval.ScanArgs(args, &s) eval.TakeNoOpt(opts) ec.OutputChan() <- ToKey(s) } // Keys implements sort.Interface. type Keys []Key func (ks Keys) Len() int { return len(ks) } func (ks Keys) Swap(i, j int) { ks[i], ks[j] = ks[j], ks[i] } func (ks Keys) Less(i, j int) bool { return ks[i].Mod < ks[j].Mod || (ks[i].Mod == ks[j].Mod && ks[i].Rune < ks[j].Rune) } elvish-0.11+ds1/edit/ui/key_test.go000066400000000000000000000015431323000013700171200ustar00rootroot00000000000000package ui import "testing" var parseKeyTests = []struct { s string wantKey Key }{ // Alt- keys are case-sensitive. {"a-x", Key{'x', Alt}}, {"a-X", Key{'X', Alt}}, // Ctrl- keys are case-insensitive. {"C-x", Key{'X', Ctrl}}, {"C-X", Key{'X', Ctrl}}, // + is the same as -. {"C+X", Key{'X', Ctrl}}, // Full names and alternative names can also be used. {"M-x", Key{'x', Alt}}, {"Meta-x", Key{'x', Alt}}, // Multiple modifiers can appear in any order. {"Alt-Ctrl-Delete", Key{Delete, Alt | Ctrl}}, {"Ctrl-Alt-Delete", Key{Delete, Alt | Ctrl}}, } func TestParseKey(t *testing.T) { for _, test := range parseKeyTests { key, err := parseKey(test.s) if key != test.wantKey { t.Errorf("ParseKey(%q) => %v, want %v", test.s, key, test.wantKey) } if err != nil { t.Errorf("ParseKey(%q) => error %v, want nil", test.s, err) } } } elvish-0.11+ds1/edit/ui/render.go000066400000000000000000000005331323000013700165460ustar00rootroot00000000000000package ui // Renderer wraps the Render method. type Renderer interface { // Render renders onto a Buffer. Render(b *Buffer) } // Render creates a new Buffer with the given width, and lets a Renderer render // onto it. func Render(r Renderer, width int) *Buffer { if r == nil { return nil } b := NewBuffer(width) r.Render(b) return b } elvish-0.11+ds1/edit/ui/render_test.go000066400000000000000000000006521323000013700176070ustar00rootroot00000000000000package ui import "testing" type dummyRenderer struct { } func (dummyRenderer) Render(b *Buffer) { b.WriteString("xy", "1") } func TestRender(t *testing.T) { b := Render(dummyRenderer{}, 10) if b.Width != 10 { t.Errorf("Rendered Buffer has Width %d, want %d", b.Width, 10) } if eq, _ := CompareCells(b.Lines[0], []Cell{{"x", 1, "1"}, {"y", 1, "1"}}); !eq { t.Errorf("Rendered Buffer has unexpected content") } } elvish-0.11+ds1/edit/ui/styled.go000066400000000000000000000052111323000013700165710ustar00rootroot00000000000000package ui import ( "strings" "github.com/elves/elvish/parse" "github.com/xiaq/persistent/hash" ) // Styled is a piece of text with style. type Styled struct { Text string Styles Styles } var styleTranslationTable = map[string]string{ "bold": "1", "dim": "2", "italic": "3", "underlined": "4", "blink": "5", "inverse": "7", "black": "30", "red": "31", "green": "32", "yellow": "33", "blue": "34", "magenta": "35", "cyan": "36", "lightgray": "37", "gray": "90", "lightred": "91", "lightgreen": "92", "lightyellow": "93", "lightblue": "94", "lightmagenta": "95", "lightcyan": "96", "white": "97", "bg-default": "49", "bg-black": "40", "bg-red": "41", "bg-green": "42", "bg-yellow": "43", "bg-blue": "44", "bg-magenta": "45", "bg-cyan": "46", "bg-lightgray": "47", "bg-gray": "100", "bg-lightred": "101", "bg-lightgreen": "102", "bg-lightyellow": "103", "bg-lightblue": "104", "bg-lightmagenta": "105", "bg-lightcyan": "106", "bg-white": "107", } func Unstyled(s string) Styled { return Styled{s, Styles{}} } func (s *Styled) Kind() string { return "styled" } func (s *Styled) Equal(a interface{}) bool { rhs, ok := a.(*Styled) if !ok { return false } return s.Text == rhs.Text && s.Styles.Eq(rhs.Styles) } func (s *Styled) Hash() uint32 { h := hash.DJBInit h = hash.DJBCombine(h, hash.String(s.Text)) h = hash.DJBCombine(h, s.Styles.Hash()) return h } func (s *Styled) String() string { return "\033[" + s.Styles.String() + "m" + s.Text + "\033[m" } func (s *Styled) Repr(indent int) string { return "(le:styled " + parse.Quote(s.Text) + " " + parse.Quote(s.Styles.String()) + ")" } type Styles []string func (ss Styles) Eq(rhs Styles) bool { if len(ss) != len(rhs) { return false } for i, s := range ss { if s != rhs[i] { return false } } return true } func (ss Styles) Hash() uint32 { h := hash.DJBInit for _, s := range ss { h = hash.DJBCombine(h, hash.String(s)) } return h } func JoinStyles(so Styles, st ...Styles) Styles { for _, v := range st { so = append(so, v...) } return so } func TranslateStyle(s string) string { v, ok := styleTranslationTable[s] if ok { return v } return s } func StylesFromString(s string) Styles { var st Styles for _, v := range strings.Split(s, ";") { st = append(st, v) } return st } func (s Styles) String() string { var o string for i, v := range s { if len(v) > 0 { if i > 0 { o += ";" } o += TranslateStyle(v) } } return o } elvish-0.11+ds1/edit/ui/ui.go000066400000000000000000000001301323000013700156750ustar00rootroot00000000000000// Package ui contains types that may be used by different editor frontends. package ui elvish-0.11+ds1/eval/000077500000000000000000000000001323000013700143245ustar00rootroot00000000000000elvish-0.11+ds1/eval/args_walker.go000066400000000000000000000033151323000013700171560ustar00rootroot00000000000000package eval import "github.com/elves/elvish/parse" type errorpfer interface { errorpf(begin, end int, fmt string, args ...interface{}) } // argsWalker is used by builtin special forms to implement argument parsing. type argsWalker struct { cp errorpfer form *parse.Form idx int } func (cp *compiler) walkArgs(f *parse.Form) *argsWalker { return &argsWalker{cp, f, 0} } func (aw *argsWalker) more() bool { return aw.idx < len(aw.form.Args) } func (aw *argsWalker) peek() *parse.Compound { if !aw.more() { aw.cp.errorpf(aw.form.End(), aw.form.End(), "need more arguments") } return aw.form.Args[aw.idx] } func (aw *argsWalker) next() *parse.Compound { n := aw.peek() aw.idx++ return n } // nextIs returns whether the next argument's source matches the given text. It // also consumes the argument if it is. func (aw *argsWalker) nextIs(text string) bool { if aw.more() && aw.form.Args[aw.idx].SourceText() == text { aw.idx++ return true } return false } // nextMustLambda fetches the next argument, raising an error if it is not a // lambda. func (aw *argsWalker) nextMustLambda() *parse.Primary { n := aw.next() if len(n.Indexings) != 1 { aw.cp.errorpf(n.Begin(), n.End(), "must be lambda") } if len(n.Indexings[0].Indicies) != 0 { aw.cp.errorpf(n.Begin(), n.End(), "must be lambda") } pn := n.Indexings[0].Head if pn.Type != parse.Lambda { aw.cp.errorpf(n.Begin(), n.End(), "must be lambda") } return pn } func (aw *argsWalker) nextMustLambdaIfAfter(leader string) *parse.Primary { if aw.nextIs(leader) { return aw.nextMustLambda() } return nil } func (aw *argsWalker) mustEnd() { if aw.more() { aw.cp.errorpf(aw.form.Args[aw.idx].Begin(), aw.form.End(), "too many arguments") } } elvish-0.11+ds1/eval/boilerplate.go000066400000000000000000000105101323000013700171520ustar00rootroot00000000000000package eval import "github.com/elves/elvish/parse" func (cp *compiler) chunkOp(n *parse.Chunk) Op { cp.compiling(n) return Op{cp.chunk(n), n.Begin(), n.End()} } func (cp *compiler) chunkOps(ns []*parse.Chunk) []Op { ops := make([]Op, len(ns)) for i, n := range ns { ops[i] = cp.chunkOp(n) } return ops } func (cp *compiler) pipelineOp(n *parse.Pipeline) Op { cp.compiling(n) return Op{cp.pipeline(n), n.Begin(), n.End()} } func (cp *compiler) pipelineOps(ns []*parse.Pipeline) []Op { ops := make([]Op, len(ns)) for i, n := range ns { ops[i] = cp.pipelineOp(n) } return ops } func (cp *compiler) formOp(n *parse.Form) Op { cp.compiling(n) return Op{cp.form(n), n.Begin(), n.End()} } func (cp *compiler) formOps(ns []*parse.Form) []Op { ops := make([]Op, len(ns)) for i, n := range ns { ops[i] = cp.formOp(n) } return ops } func (cp *compiler) assignmentOp(n *parse.Assignment) Op { cp.compiling(n) return Op{cp.assignment(n), n.Begin(), n.End()} } func (cp *compiler) assignmentOps(ns []*parse.Assignment) []Op { ops := make([]Op, len(ns)) for i, n := range ns { ops[i] = cp.assignmentOp(n) } return ops } func (cp *compiler) redirOp(n *parse.Redir) Op { cp.compiling(n) return Op{cp.redir(n), n.Begin(), n.End()} } func (cp *compiler) redirOps(ns []*parse.Redir) []Op { ops := make([]Op, len(ns)) for i, n := range ns { ops[i] = cp.redirOp(n) } return ops } func (cp *compiler) compoundOp(n *parse.Compound) ValuesOp { cp.compiling(n) return ValuesOp{cp.compound(n), n.Begin(), n.End()} } func (cp *compiler) compoundOps(ns []*parse.Compound) []ValuesOp { ops := make([]ValuesOp, len(ns)) for i, n := range ns { ops[i] = cp.compoundOp(n) } return ops } func (cp *compiler) arrayOp(n *parse.Array) ValuesOp { cp.compiling(n) return ValuesOp{cp.array(n), n.Begin(), n.End()} } func (cp *compiler) arrayOps(ns []*parse.Array) []ValuesOp { ops := make([]ValuesOp, len(ns)) for i, n := range ns { ops[i] = cp.arrayOp(n) } return ops } func (cp *compiler) indexingOp(n *parse.Indexing) ValuesOp { cp.compiling(n) return ValuesOp{cp.indexing(n), n.Begin(), n.End()} } func (cp *compiler) indexingOps(ns []*parse.Indexing) []ValuesOp { ops := make([]ValuesOp, len(ns)) for i, n := range ns { ops[i] = cp.indexingOp(n) } return ops } func (cp *compiler) primaryOp(n *parse.Primary) ValuesOp { cp.compiling(n) return ValuesOp{cp.primary(n), n.Begin(), n.End()} } func (cp *compiler) primaryOps(ns []*parse.Primary) []ValuesOp { ops := make([]ValuesOp, len(ns)) for i, n := range ns { ops[i] = cp.primaryOp(n) } return ops } func (cp *compiler) listOp(n *parse.Primary) ValuesOp { cp.compiling(n) return ValuesOp{cp.list(n), n.Begin(), n.End()} } func (cp *compiler) listOps(ns []*parse.Primary) []ValuesOp { ops := make([]ValuesOp, len(ns)) for i, n := range ns { ops[i] = cp.listOp(n) } return ops } func (cp *compiler) exceptionCaptureOp(n *parse.Chunk) ValuesOp { cp.compiling(n) return ValuesOp{cp.exceptionCapture(n), n.Begin(), n.End()} } func (cp *compiler) exceptionCaptureOps(ns []*parse.Chunk) []ValuesOp { ops := make([]ValuesOp, len(ns)) for i, n := range ns { ops[i] = cp.exceptionCaptureOp(n) } return ops } func (cp *compiler) outputCaptureOp(n *parse.Primary) ValuesOp { cp.compiling(n) return ValuesOp{cp.outputCapture(n), n.Begin(), n.End()} } func (cp *compiler) outputCaptureOps(ns []*parse.Primary) []ValuesOp { ops := make([]ValuesOp, len(ns)) for i, n := range ns { ops[i] = cp.outputCaptureOp(n) } return ops } func (cp *compiler) lambdaOp(n *parse.Primary) ValuesOp { cp.compiling(n) return ValuesOp{cp.lambda(n), n.Begin(), n.End()} } func (cp *compiler) lambdaOps(ns []*parse.Primary) []ValuesOp { ops := make([]ValuesOp, len(ns)) for i, n := range ns { ops[i] = cp.lambdaOp(n) } return ops } func (cp *compiler) map_Op(n *parse.Primary) ValuesOp { cp.compiling(n) return ValuesOp{cp.map_(n), n.Begin(), n.End()} } func (cp *compiler) map_Ops(ns []*parse.Primary) []ValuesOp { ops := make([]ValuesOp, len(ns)) for i, n := range ns { ops[i] = cp.map_Op(n) } return ops } func (cp *compiler) bracedOp(n *parse.Primary) ValuesOp { cp.compiling(n) return ValuesOp{cp.braced(n), n.Begin(), n.End()} } func (cp *compiler) bracedOps(ns []*parse.Primary) []ValuesOp { ops := make([]ValuesOp, len(ns)) for i, n := range ns { ops[i] = cp.bracedOp(n) } return ops } elvish-0.11+ds1/eval/boilerplate.py000077500000000000000000000023151323000013700172040ustar00rootroot00000000000000#!/usr/bin/python2.7 import re import os def put_compile_s(out, name, intype, extraargs, outtype): if not outtype.endswith('Func'): return outtype = outtype[:-4] extranames = ', '.join(a.split(' ')[0] for a in extraargs.split(', ')) if extraargs else '' print >>out, ''' func (cp *compiler) {name}Op(n {intype}{extraargs}) {outtype} {{ cp.compiling(n) return {outtype}{{cp.{name}(n{extranames}), n.Begin(), n.End()}} }} func (cp *compiler) {name}Ops(ns []{intype}{extraargs}) []{outtype} {{ ops := make([]{outtype}, len(ns)) for i, n := range ns {{ ops[i] = cp.{name}Op(n{extranames}) }} return ops }} '''.format(name=name, intype=intype, outtype=outtype, extraargs=extraargs, extranames=extranames) def main(): out = open('boilerplate.go', 'w') print >>out, '''package eval import "github.com/elves/elvish/parse"''' for fname in 'compile_op.go', 'compile_value.go': for line in file(fname): m = re.match(r'^func \(cp \*compiler\) (\w+)\(\w+ ([^,\[\]]+)(.*)\) (\w*OpFunc) {$', line) if m: put_compile_s(out, *m.groups()) out.close() os.system('gofmt -w boilerplate.go') if __name__ == '__main__': main() elvish-0.11+ds1/eval/builtin_fn.go000066400000000000000000000121411323000013700170030ustar00rootroot00000000000000package eval // Builtin functions. import ( "errors" "fmt" "math/rand" "net" "path/filepath" "runtime" "time" "unsafe" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/util" "github.com/xiaq/persistent/hash" ) // BuiltinFn is a builtin function. type BuiltinFn struct { Name string Impl BuiltinFnImpl } type BuiltinFnImpl func(*Frame, []types.Value, map[string]types.Value) var _ Fn = &BuiltinFn{} // Kind returns "fn". func (*BuiltinFn) Kind() string { return "fn" } // Equal compares based on identity. func (b *BuiltinFn) Equal(rhs interface{}) bool { return b == rhs } func (b *BuiltinFn) Hash() uint32 { return hash.Pointer(unsafe.Pointer(b)) } // Repr returns an opaque representation "". func (b *BuiltinFn) Repr(int) string { return "" } // Call calls a builtin function. func (b *BuiltinFn) Call(ec *Frame, args []types.Value, opts map[string]types.Value) { b.Impl(ec, args, opts) } var builtinFns []*BuiltinFn func addToBuiltinFns(moreFns []*BuiltinFn) { builtinFns = append(builtinFns, moreFns...) } // Builtins that have not been put into their own groups go here. var ErrArgs = errors.New("args error") func init() { addToBuiltinFns([]*BuiltinFn{ {"nop", nop}, {"kind-of", kindOf}, {"bool", boolFn}, {"not", not}, {"is", is}, {"eq", eq}, {"not-eq", notEq}, {"constantly", constantly}, {"-source", source}, // Time {"esleep", sleep}, {"-time", _time}, // Debugging {"src", src}, {"-gc", _gc}, {"-stack", _stack}, {"-log", _log}, {"-ifaddrs", _ifaddrs}, }) // For rand and randint. rand.Seed(time.Now().UTC().UnixNano()) } func nop(ec *Frame, args []types.Value, opts map[string]types.Value) { } func kindOf(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) out := ec.ports[1].Chan for _, a := range args { out <- types.String(a.Kind()) } } func boolFn(ec *Frame, args []types.Value, opts map[string]types.Value) { var v types.Value ScanArgs(args, &v) TakeNoOpt(opts) ec.OutputChan() <- types.Bool(types.ToBool(v)) } func not(ec *Frame, args []types.Value, opts map[string]types.Value) { var v types.Value ScanArgs(args, &v) TakeNoOpt(opts) ec.OutputChan() <- types.Bool(!types.ToBool(v)) } func is(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) result := true for i := 0; i+1 < len(args); i++ { if args[i] != args[i+1] { result = false break } } ec.OutputChan() <- types.Bool(result) } func eq(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) result := true for i := 0; i+1 < len(args); i++ { if !args[i].Equal(args[i+1]) { result = false break } } ec.OutputChan() <- types.Bool(result) } func notEq(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) result := true for i := 0; i+1 < len(args); i++ { if args[i].Equal(args[i+1]) { result = false break } } ec.OutputChan() <- types.Bool(result) } func constantly(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) out := ec.ports[1].Chan // XXX Repr of this fn is not right out <- &BuiltinFn{ "created by constantly", func(ec *Frame, a []types.Value, o map[string]types.Value) { TakeNoOpt(o) if len(a) != 0 { throw(ErrArgs) } out := ec.ports[1].Chan for _, v := range args { out <- v } }, } } func source(ec *Frame, args []types.Value, opts map[string]types.Value) { var argFname types.String ScanArgs(args, &argFname) ScanOpts(opts) fname := string(argFname) abs, err := filepath.Abs(fname) maybeThrow(err) maybeThrow(ec.Source(fname, abs)) } func sleep(ec *Frame, args []types.Value, opts map[string]types.Value) { var t float64 ScanArgs(args, &t) TakeNoOpt(opts) d := time.Duration(float64(time.Second) * t) select { case <-ec.Interrupts(): throw(ErrInterrupted) case <-time.After(d): } } func _time(ec *Frame, args []types.Value, opts map[string]types.Value) { var f Fn ScanArgs(args, &f) TakeNoOpt(opts) t0 := time.Now() f.Call(ec, NoArgs, NoOpts) t1 := time.Now() dt := t1.Sub(t0) fmt.Fprintln(ec.ports[1].File, dt) } func src(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) ec.OutputChan() <- ec.srcMeta } func _gc(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) runtime.GC() } func _stack(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) out := ec.ports[1].File // XXX dup with main.go buf := make([]byte, 1024) for runtime.Stack(buf, true) == cap(buf) { buf = make([]byte, cap(buf)*2) } out.Write(buf) } func _log(ec *Frame, args []types.Value, opts map[string]types.Value) { var fnamev types.String ScanArgs(args, &fnamev) fname := string(fnamev) TakeNoOpt(opts) maybeThrow(util.SetOutputFile(fname)) } func _ifaddrs(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) out := ec.ports[1].Chan addrs, err := net.InterfaceAddrs() maybeThrow(err) for _, addr := range addrs { out <- types.String(addr.String()) } } elvish-0.11+ds1/eval/builtin_fn_cmd.go000066400000000000000000000036341323000013700176350ustar00rootroot00000000000000package eval import ( "errors" "fmt" "os" "os/exec" "github.com/elves/elvish/eval/types" ) // Command and process control. var ErrNotInSameGroup = errors.New("not in the same process group") func init() { addToBuiltinFns([]*BuiltinFn{ // Command resolution {"resolve", resolveFn}, {"external", external}, {"has-external", hasExternal}, {"search-external", searchExternal}, // Process control {"fg", fg}, {"exec", execFn}, {"exit", exit}, }) } func resolveFn(ec *Frame, args []types.Value, opts map[string]types.Value) { var cmd types.String ScanArgs(args, &cmd) TakeNoOpt(opts) out := ec.ports[1].Chan out <- resolve(string(cmd), ec) } func external(ec *Frame, args []types.Value, opts map[string]types.Value) { var cmd types.String ScanArgs(args, &cmd) TakeNoOpt(opts) ec.OutputChan() <- ExternalCmd{string(cmd)} } func hasExternal(ec *Frame, args []types.Value, opts map[string]types.Value) { var cmd types.String ScanArgs(args, &cmd) TakeNoOpt(opts) _, err := exec.LookPath(string(cmd)) ec.OutputChan() <- types.Bool(err == nil) } func searchExternal(ec *Frame, args []types.Value, opts map[string]types.Value) { var cmd types.String ScanArgs(args, &cmd) TakeNoOpt(opts) path, err := exec.LookPath(string(cmd)) maybeThrow(err) out := ec.ports[1].Chan out <- types.String(path) } func exit(ec *Frame, args []types.Value, opts map[string]types.Value) { var codes []int ScanArgsVariadic(args, &codes) TakeNoOpt(opts) doexit := func(i int) { preExit(ec) os.Exit(i) } switch len(codes) { case 0: doexit(0) case 1: doexit(codes[0]) default: throw(ErrArgs) } } func preExit(ec *Frame) { err := ec.DaemonClient.Close() if err != nil { fmt.Fprintln(os.Stderr, err) } } var errNotSupportedOnWindows = errors.New("not supported on Windows") func notSupportedOnWindows(ec *Frame, args []types.Value, opts map[string]types.Value) { throw(errNotSupportedOnWindows) } elvish-0.11+ds1/eval/builtin_fn_cmd_test.go000066400000000000000000000001371323000013700206670ustar00rootroot00000000000000package eval import "testing" func TestBuiltinFnCmd(t *testing.T) { runTests(t, []Test{}) } elvish-0.11+ds1/eval/builtin_fn_cmd_unix.go000066400000000000000000000031531323000013700206740ustar00rootroot00000000000000// +build !windows,!plan9 package eval import ( "os" "os/exec" "strconv" "syscall" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/sys" ) func execFn(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) var argstrings []string if len(args) == 0 { argstrings = []string{"elvish"} } else { argstrings = make([]string, len(args)) for i, a := range args { argstrings[i] = types.ToString(a) } } var err error argstrings[0], err = exec.LookPath(argstrings[0]) maybeThrow(err) preExit(ec) err = syscall.Exec(argstrings[0], argstrings, os.Environ()) maybeThrow(err) } func fg(ec *Frame, args []types.Value, opts map[string]types.Value) { var pids []int ScanArgsVariadic(args, &pids) TakeNoOpt(opts) if len(pids) == 0 { throw(ErrArgs) } var thepgid int for i, pid := range pids { pgid, err := syscall.Getpgid(pid) maybeThrow(err) if i == 0 { thepgid = pgid } else if pgid != thepgid { throw(ErrNotInSameGroup) } } err := sys.Tcsetpgrp(0, thepgid) maybeThrow(err) errors := make([]*Exception, len(pids)) for i, pid := range pids { err := syscall.Kill(pid, syscall.SIGCONT) if err != nil { errors[i] = &Exception{err, nil} } } for i, pid := range pids { if errors[i] != nil { continue } var ws syscall.WaitStatus _, err = syscall.Wait4(pid, &ws, syscall.WUNTRACED, nil) if err != nil { errors[i] = &Exception{err, nil} } else { // TODO find command name errors[i] = &Exception{NewExternalCmdExit( "[pid "+strconv.Itoa(pid)+"]", ws, pid), nil} } } maybeThrow(ComposeExceptionsFromPipeline(errors)) } elvish-0.11+ds1/eval/builtin_fn_cmd_windows.go000066400000000000000000000001261323000013700214000ustar00rootroot00000000000000package eval var ( execFn = notSupportedOnWindows fg = notSupportedOnWindows ) elvish-0.11+ds1/eval/builtin_fn_container.go000066400000000000000000000116551323000013700210560ustar00rootroot00000000000000package eval import ( "errors" "fmt" "io" "strconv" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/util" ) // Sequence, list and maps. func init() { addToBuiltinFns([]*BuiltinFn{ {"ns", nsFn}, {"range", rangeFn}, {"repeat", repeat}, {"explode", explode}, {"assoc", assoc}, {"dissoc", dissoc}, {"all", all}, {"take", take}, {"drop", drop}, {"has-key", hasKey}, {"has-value", hasValue}, {"count", count}, {"keys", keys}, }) } func nsFn(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) ec.OutputChan() <- make(Ns) } func rangeFn(ec *Frame, args []types.Value, opts map[string]types.Value) { var step float64 ScanOpts(opts, OptToScan{"step", &step, types.String("1")}) var lower, upper float64 var err error switch len(args) { case 1: upper, err = toFloat(args[0]) maybeThrow(err) case 2: lower, err = toFloat(args[0]) maybeThrow(err) upper, err = toFloat(args[1]) maybeThrow(err) default: throw(ErrArgs) } out := ec.ports[1].Chan for f := lower; f < upper; f += step { out <- floatToString(f) } } func repeat(ec *Frame, args []types.Value, opts map[string]types.Value) { var ( n int v types.Value ) ScanArgs(args, &n, &v) TakeNoOpt(opts) out := ec.OutputChan() for i := 0; i < n; i++ { out <- v } } // explode puts each element of the argument. func explode(ec *Frame, args []types.Value, opts map[string]types.Value) { var v types.IteratorValue ScanArgs(args, &v) TakeNoOpt(opts) out := ec.ports[1].Chan v.Iterate(func(e types.Value) bool { out <- e return true }) } func assoc(ec *Frame, args []types.Value, opts map[string]types.Value) { var ( a types.Assocer k, v types.Value ) ScanArgs(args, &a, &k, &v) TakeNoOpt(opts) ec.OutputChan() <- a.Assoc(k, v) } func dissoc(ec *Frame, args []types.Value, opts map[string]types.Value) { var ( a types.Dissocer k types.Value ) ScanArgs(args, &a, &k) TakeNoOpt(opts) ec.OutputChan() <- a.Dissoc(k) } func all(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) valuesDone := make(chan struct{}) go func() { for input := range ec.ports[0].Chan { ec.ports[1].Chan <- input } close(valuesDone) }() _, err := io.Copy(ec.ports[1].File, ec.ports[0].File) <-valuesDone if err != nil { throwf("cannot copy byte input: %s", err) } } func take(ec *Frame, args []types.Value, opts map[string]types.Value) { var n int iterate := ScanArgsOptionalInput(ec, args, &n) TakeNoOpt(opts) out := ec.ports[1].Chan i := 0 iterate(func(v types.Value) { if i < n { out <- v } i++ }) } func drop(ec *Frame, args []types.Value, opts map[string]types.Value) { var n int iterate := ScanArgsOptionalInput(ec, args, &n) TakeNoOpt(opts) out := ec.ports[1].Chan i := 0 iterate(func(v types.Value) { if i >= n { out <- v } i++ }) } func hasValue(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) var container, value types.Value var found bool ScanArgs(args, &container, &value) switch container := container.(type) { case types.Iterator: container.Iterate(func(v types.Value) bool { found = (v == value) return !found }) case types.MapLike: container.IterateKey(func(v types.Value) bool { found = (container.IndexOne(v) == value) return !found }) default: throw(fmt.Errorf("argument of type '%s' is not iterable", container.Kind())) } ec.ports[1].Chan <- types.Bool(found) } func hasKey(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) var container, key types.Value var found bool ScanArgs(args, &container, &key) switch container := container.(type) { case types.HasKeyer: found = container.HasKey(key) case types.Lener: // XXX(xiaq): Not all types that implement Lener have numerical indices err := util.PCall(func() { types.ParseAndFixListIndex(types.ToString(key), container.Len()) }) found = (err == nil) default: throw(fmt.Errorf("couldn't get key or index of type '%s'", container.Kind())) } ec.ports[1].Chan <- types.Bool(found) } func count(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) var n int switch len(args) { case 0: // Count inputs. ec.IterateInputs(func(types.Value) { n++ }) case 1: // Get length of argument. v := args[0] if lener, ok := v.(types.Lener); ok { n = lener.Len() } else if iterator, ok := v.(types.Iterator); ok { iterator.Iterate(func(types.Value) bool { n++ return true }) } else { throw(fmt.Errorf("cannot get length of a %s", v.Kind())) } default: throw(errors.New("want 0 or 1 argument")) } ec.ports[1].Chan <- types.String(strconv.Itoa(n)) } func keys(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) var iter types.IterateKeyer ScanArgs(args, &iter) out := ec.ports[1].Chan iter.IterateKey(func(v types.Value) bool { out <- v return true }) } elvish-0.11+ds1/eval/builtin_fn_container_test.go000066400000000000000000000034541323000013700221130ustar00rootroot00000000000000package eval import "testing" func TestBuiltinFnContainer(t *testing.T) { runTests(t, []Test{ {`range 3`, want{out: strs("0", "1", "2")}}, {`range 1 3`, want{out: strs("1", "2")}}, {`range 0 10 &step=3`, want{out: strs("0", "3", "6", "9")}}, {`repeat 4 foo`, want{out: strs("foo", "foo", "foo", "foo")}}, {`explode [foo bar]`, want{out: strs("foo", "bar")}}, {`put (assoc [0] 0 zero)[0]`, want{out: strs("zero")}}, {`put (assoc [&] k v)[k]`, want{out: strs("v")}}, {`put (assoc [&k=v] k v2)[k]`, want{out: strs("v2")}}, {`has-key (dissoc [&k=v] k) k`, wantFalse}, {`put foo bar | all`, want{out: strs("foo", "bar")}}, {`echo foobar | all`, want{bytesOut: []byte("foobar\n")}}, {`{ put foo bar; echo foobar } | all`, want{out: strs("foo", "bar"), bytesOut: []byte("foobar\n")}}, {`range 100 | take 2`, want{out: strs("0", "1")}}, {`range 100 | drop 98`, want{out: strs("98", "99")}}, {`has-key [foo bar] 0`, wantTrue}, {`has-key [foo bar] 0:1`, wantTrue}, {`has-key [foo bar] 0:20`, wantFalse}, {`has-key [&lorem=ipsum &foo=bar] lorem`, wantTrue}, {`has-key [&lorem=ipsum &foo=bar] loremwsq`, wantFalse}, {`has-value [&lorem=ipsum &foo=bar] lorem`, wantFalse}, {`has-value [&lorem=ipsum &foo=bar] bar`, wantTrue}, {`has-value [foo bar] bar`, wantTrue}, {`has-value [foo bar] badehose`, wantFalse}, {`has-value "foo" o`, wantTrue}, {`has-value "foo" d`, wantFalse}, {`range 100 | count`, want{out: strs("100")}}, {`count [(range 100)]`, want{out: strs("100")}}, {`count 1 2 3`, want{err: errAny}}, {`keys [&]`, wantNothing}, {`keys [&a=foo]`, want{out: strs("a")}}, // Windows does not have an external sort command. Disabled until we have a // builtin sort command. // {`keys [&a=foo &b=bar] | each echo | sort | each $put~`, want{out: strs("a", "b")}}, }) } elvish-0.11+ds1/eval/builtin_fn_flow.go000066400000000000000000000064371323000013700200450ustar00rootroot00000000000000package eval import ( "errors" "sync" "github.com/elves/elvish/eval/types" ) // Flow control. func init() { addToBuiltinFns([]*BuiltinFn{ {"run-parallel", runParallel}, // Iterations. {"each", each}, {"peach", peach}, // Exception and control {"fail", fail}, {"multi-error", multiErrorFn}, {"return", returnFn}, {"break", breakFn}, {"continue", continueFn}, }) } func runParallel(ec *Frame, args []types.Value, opts map[string]types.Value) { var functions []Fn ScanArgsVariadic(args, &functions) TakeNoOpt(opts) var waitg sync.WaitGroup waitg.Add(len(functions)) exceptions := make([]*Exception, len(functions)) for i, function := range functions { go func(ec *Frame, function Fn, exception **Exception) { err := ec.PCall(function, NoArgs, NoOpts) if err != nil { *exception = err.(*Exception) } waitg.Done() }(ec.fork("[run-parallel function]"), function, &exceptions[i]) } waitg.Wait() maybeThrow(ComposeExceptionsFromPipeline(exceptions)) } // each takes a single closure and applies it to all input values. func each(ec *Frame, args []types.Value, opts map[string]types.Value) { var f Fn iterate := ScanArgsOptionalInput(ec, args, &f) TakeNoOpt(opts) broken := false iterate(func(v types.Value) { if broken { return } // NOTE We don't have the position range of the closure in the source. // Ideally, it should be kept in the Closure itself. newec := ec.fork("closure of each") newec.ports[0] = DevNullClosedChan ex := newec.PCall(f, []types.Value{v}, NoOpts) ClosePorts(newec.ports) if ex != nil { switch ex.(*Exception).Cause { case nil, Continue: // nop case Break: broken = true default: throw(ex) } } }) } // peach takes a single closure and applies it to all input values in parallel. func peach(ec *Frame, args []types.Value, opts map[string]types.Value) { var f Fn iterate := ScanArgsOptionalInput(ec, args, &f) TakeNoOpt(opts) var w sync.WaitGroup broken := false var err error iterate(func(v types.Value) { if broken || err != nil { return } w.Add(1) go func() { // NOTE We don't have the position range of the closure in the source. // Ideally, it should be kept in the Closure itself. newec := ec.fork("closure of each") newec.ports[0] = DevNullClosedChan ex := newec.PCall(f, []types.Value{v}, NoOpts) ClosePorts(newec.ports) if ex != nil { switch ex.(*Exception).Cause { case nil, Continue: // nop case Break: broken = true default: err = ex } } w.Done() }() }) w.Wait() maybeThrow(err) } func fail(ec *Frame, args []types.Value, opts map[string]types.Value) { var msg types.String ScanArgs(args, &msg) TakeNoOpt(opts) throw(errors.New(string(msg))) } func multiErrorFn(ec *Frame, args []types.Value, opts map[string]types.Value) { var excs []*Exception ScanArgsVariadic(args, &excs) TakeNoOpt(opts) throw(PipelineError{excs}) } func returnFn(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) throw(Return) } func breakFn(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) throw(Break) } func continueFn(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) throw(Continue) } elvish-0.11+ds1/eval/builtin_fn_flow_test.go000066400000000000000000000012661323000013700210770ustar00rootroot00000000000000package eval import "testing" func TestBuiltinFnFlow(t *testing.T) { runTests(t, []Test{ {`run-parallel { put lorem } { echo ipsum }`, want{out: strs("lorem"), bytesOut: []byte("ipsum\n")}}, {`put 1 233 | each $put~`, want{out: strs("1", "233")}}, {`echo "1\n233" | each $put~`, want{out: strs("1", "233")}}, {`each $put~ [1 233]`, want{out: strs("1", "233")}}, {`range 10 | each [x]{ if (== $x 4) { break }; put $x }`, want{out: strs("0", "1", "2", "3")}}, {`range 10 | each [x]{ if (== $x 4) { fail haha }; put $x }`, want{out: strs("0", "1", "2", "3"), err: errAny}}, // TODO: test peach {`fail haha`, want{err: errAny}}, {`return`, want{err: Return}}, }) } elvish-0.11+ds1/eval/builtin_fn_fs.go000066400000000000000000000060101323000013700174710ustar00rootroot00000000000000package eval import ( "errors" "os" "path/filepath" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/store/storedefs" "github.com/elves/elvish/util" ) // Filesystem. var ErrStoreNotConnected = errors.New("store not connected") func init() { addToBuiltinFns([]*BuiltinFn{ // Directory {"cd", cd}, {"dir-history", dirs}, // Path {"path-abs", WrapStringToStringError(filepath.Abs)}, {"path-base", WrapStringToString(filepath.Base)}, {"path-clean", WrapStringToString(filepath.Clean)}, {"path-dir", WrapStringToString(filepath.Dir)}, {"path-ext", WrapStringToString(filepath.Ext)}, {"eval-symlinks", WrapStringToStringError(filepath.EvalSymlinks)}, {"tilde-abbr", tildeAbbr}, // File types {"-is-dir", isDir}, }) } func WrapStringToString(f func(string) string) BuiltinFnImpl { return func(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) s := mustGetOneString(args) ec.ports[1].Chan <- types.String(f(s)) } } func WrapStringToStringError(f func(string) (string, error)) BuiltinFnImpl { return func(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) s := mustGetOneString(args) result, err := f(s) maybeThrow(err) ec.ports[1].Chan <- types.String(result) } } var errMustBeOneString = errors.New("must be one string argument") func mustGetOneString(args []types.Value) string { if len(args) != 1 { throw(errMustBeOneString) } s, ok := args[0].(types.String) if !ok { throw(errMustBeOneString) } return string(s) } func cd(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) var dir string if len(args) == 0 { dir = mustGetHome("") } else if len(args) == 1 { dir = types.ToString(args[0]) } else { throw(ErrArgs) } cdInner(dir, ec) } func cdInner(dir string, ec *Frame) { maybeThrow(Chdir(dir, ec.DaemonClient)) } var dirDescriptor = types.NewStructDescriptor("path", "score") func newDirStruct(path string, score float64) *types.Struct { return types.NewStruct(dirDescriptor, []types.Value{types.String(path), floatToString(score)}) } func dirs(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) if ec.DaemonClient == nil { throw(ErrStoreNotConnected) } dirs, err := ec.DaemonClient.Dirs(storedefs.NoBlacklist) if err != nil { throw(errors.New("store error: " + err.Error())) } out := ec.ports[1].Chan for _, dir := range dirs { out <- newDirStruct(dir.Path, dir.Score) } } func tildeAbbr(ec *Frame, args []types.Value, opts map[string]types.Value) { var pathv types.String ScanArgs(args, &pathv) path := string(pathv) TakeNoOpt(opts) out := ec.ports[1].Chan out <- types.String(util.TildeAbbr(path)) } func isDir(ec *Frame, args []types.Value, opts map[string]types.Value) { var pathv types.String ScanArgs(args, &pathv) path := string(pathv) TakeNoOpt(opts) ec.OutputChan() <- types.Bool(isDirInner(path)) } func isDirInner(path string) bool { fi, err := os.Stat(path) return err == nil && fi.Mode().IsDir() } elvish-0.11+ds1/eval/builtin_fn_fs_test.go000066400000000000000000000006231323000013700205340ustar00rootroot00000000000000package eval import ( "path/filepath" "testing" ) func TestBuiltinFnFS(t *testing.T) { pathSep := string(filepath.Separator) runTests(t, []Test{ {`path-base a/b/c.png`, want{out: strs("c.png")}}, {`tilde-abbr $E:HOME` + pathSep + `foobar`, want{out: strs("~" + pathSep + "foobar")}}, {`-is-dir ~/dir`, wantTrue}, // see testmain_test.go for setup {`-is-dir ~/lorem`, wantFalse}, }) } elvish-0.11+ds1/eval/builtin_fn_io.go000066400000000000000000000077621323000013700175070ustar00rootroot00000000000000package eval import ( "encoding/json" "fmt" "io" "io/ioutil" "os" "github.com/elves/elvish/eval/types" ) // Input and output. func init() { addToBuiltinFns([]*BuiltinFn{ // Value output {"put", put}, // Bytes output {"print", print}, {"echo", echo}, {"pprint", pprint}, {"repr", repr}, // Bytes to value {"slurp", slurp}, {"from-lines", fromLines}, {"from-json", fromJSON}, // Value to bytes {"to-lines", toLines}, {"to-json", toJSON}, // File and pipe {"fopen", fopen}, {"fclose", fclose}, {"pipe", pipe}, {"prclose", prclose}, {"pwclose", pwclose}, }) } func put(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) out := ec.ports[1].Chan for _, a := range args { out <- a } } func print(ec *Frame, args []types.Value, opts map[string]types.Value) { var sepv types.String ScanOpts(opts, OptToScan{"sep", &sepv, types.String(" ")}) out := ec.ports[1].File sep := string(sepv) for i, arg := range args { if i > 0 { out.WriteString(sep) } out.WriteString(types.ToString(arg)) } } func echo(ec *Frame, args []types.Value, opts map[string]types.Value) { print(ec, args, opts) ec.ports[1].File.WriteString("\n") } func pprint(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) out := ec.ports[1].File for _, arg := range args { out.WriteString(arg.Repr(0)) out.WriteString("\n") } } func repr(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) out := ec.ports[1].File for i, arg := range args { if i > 0 { out.WriteString(" ") } out.WriteString(arg.Repr(types.NoPretty)) } out.WriteString("\n") } func slurp(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) in := ec.ports[0].File out := ec.ports[1].Chan all, err := ioutil.ReadAll(in) maybeThrow(err) out <- types.String(string(all)) } func fromLines(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) in := ec.ports[0].File out := ec.ports[1].Chan linesToChan(in, out) } // fromJSON parses a stream of JSON data into Value's. func fromJSON(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) in := ec.ports[0].File out := ec.ports[1].Chan dec := json.NewDecoder(in) var v interface{} for { err := dec.Decode(&v) if err != nil { if err == io.EOF { return } throw(err) } out <- FromJSONInterface(v) } } func toLines(ec *Frame, args []types.Value, opts map[string]types.Value) { iterate := ScanArgsOptionalInput(ec, args) TakeNoOpt(opts) out := ec.ports[1].File iterate(func(v types.Value) { fmt.Fprintln(out, types.ToString(v)) }) } // toJSON converts a stream of Value's to JSON data. func toJSON(ec *Frame, args []types.Value, opts map[string]types.Value) { iterate := ScanArgsOptionalInput(ec, args) TakeNoOpt(opts) out := ec.ports[1].File enc := json.NewEncoder(out) iterate(func(v types.Value) { err := enc.Encode(v) maybeThrow(err) }) } func fopen(ec *Frame, args []types.Value, opts map[string]types.Value) { var namev types.String ScanArgs(args, &namev) name := string(namev) TakeNoOpt(opts) // TODO support opening files for writing etc as well. out := ec.ports[1].Chan f, err := os.Open(name) maybeThrow(err) out <- types.File{f} } func fclose(ec *Frame, args []types.Value, opts map[string]types.Value) { var f types.File ScanArgs(args, &f) TakeNoOpt(opts) maybeThrow(f.Inner.Close()) } func pipe(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) r, w, err := os.Pipe() out := ec.ports[1].Chan maybeThrow(err) out <- types.Pipe{r, w} } func prclose(ec *Frame, args []types.Value, opts map[string]types.Value) { var p types.Pipe ScanArgs(args, &p) TakeNoOpt(opts) maybeThrow(p.ReadEnd.Close()) } func pwclose(ec *Frame, args []types.Value, opts map[string]types.Value) { var p types.Pipe ScanArgs(args, &p) TakeNoOpt(opts) maybeThrow(p.WriteEnd.Close()) } elvish-0.11+ds1/eval/builtin_fn_io_test.go000066400000000000000000000021771323000013700205410ustar00rootroot00000000000000package eval import ( "testing" "github.com/elves/elvish/eval/types" ) func TestBuiltinFnIO(t *testing.T) { runTests(t, []Test{ {`put foo bar`, want{out: strs("foo", "bar")}}, {`print [foo bar]`, want{bytesOut: []byte("[foo bar]")}}, {`echo [foo bar]`, want{bytesOut: []byte("[foo bar]\n")}}, {`pprint [foo bar]`, want{bytesOut: []byte("[\n foo\n bar\n]\n")}}, NewTest(`repr foo bar ['foo bar']`).WantBytesOutString("foo bar ['foo bar']\n"), {`print "a\nb" | slurp`, want{out: strs("a\nb")}}, {`print "a\nb" | from-lines`, want{out: strs("a", "b")}}, {`print "a\nb\n" | from-lines`, want{out: strs("a", "b")}}, {`echo '{"k": "v", "a": [1, 2]}' '"foo"' | from-json`, want{out: []types.Value{ types.MakeMap(map[types.Value]types.Value{ types.String("k"): types.String("v"), types.String("a"): types.MakeList(strs("1", "2")...)}), types.String("foo"), }}}, {`echo 'invalid' | from-json`, want{err: errAny}}, {`put "l\norem" ipsum | to-lines`, want{bytesOut: []byte("l\norem\nipsum\n")}}, {`put [&k=v &a=[1 2]] foo | to-json`, want{bytesOut: []byte(`{"a":["1","2"],"k":"v"} "foo" `)}}, }) } elvish-0.11+ds1/eval/builtin_fn_num.go000066400000000000000000000064701323000013700176720ustar00rootroot00000000000000package eval import ( "math" "math/rand" "strconv" "github.com/elves/elvish/eval/types" ) // Numerical operations. func init() { addToBuiltinFns([]*BuiltinFn{ // Comparison {"<", wrapNumCompare(func(a, b float64) bool { return a < b })}, {"<=", wrapNumCompare(func(a, b float64) bool { return a <= b })}, {"==", wrapNumCompare(func(a, b float64) bool { return a == b })}, {"!=", wrapNumCompare(func(a, b float64) bool { return a != b })}, {">", wrapNumCompare(func(a, b float64) bool { return a > b })}, {">=", wrapNumCompare(func(a, b float64) bool { return a >= b })}, // Arithmetics {"+", plus}, {"-", minus}, {"*", times}, {"/", slash}, {"^", pow}, {"%", mod}, // Random {"rand", randFn}, {"randint", randint}, }) } func wrapNumCompare(cmp func(a, b float64) bool) BuiltinFnImpl { return func(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) floats := make([]float64, len(args)) for i, a := range args { f, err := toFloat(a) maybeThrow(err) floats[i] = f } result := true for i := 0; i < len(floats)-1; i++ { if !cmp(floats[i], floats[i+1]) { result = false break } } ec.OutputChan() <- types.Bool(result) } } func plus(ec *Frame, args []types.Value, opts map[string]types.Value) { var nums []float64 ScanArgsVariadic(args, &nums) TakeNoOpt(opts) out := ec.ports[1].Chan sum := 0.0 for _, f := range nums { sum += f } out <- floatToString(sum) } func minus(ec *Frame, args []types.Value, opts map[string]types.Value) { var ( sum float64 nums []float64 ) ScanArgsVariadic(args, &sum, &nums) TakeNoOpt(opts) out := ec.ports[1].Chan if len(nums) == 0 { // Unary - sum = -sum } else { for _, f := range nums { sum -= f } } out <- floatToString(sum) } func times(ec *Frame, args []types.Value, opts map[string]types.Value) { var nums []float64 ScanArgsVariadic(args, &nums) TakeNoOpt(opts) out := ec.ports[1].Chan prod := 1.0 for _, f := range nums { prod *= f } out <- floatToString(prod) } func slash(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) if len(args) == 0 { // cd / cdInner("/", ec) return } // Division divide(ec, args, opts) } func divide(ec *Frame, args []types.Value, opts map[string]types.Value) { var ( prod float64 nums []float64 ) ScanArgsVariadic(args, &prod, &nums) TakeNoOpt(opts) out := ec.ports[1].Chan for _, f := range nums { prod /= f } out <- floatToString(prod) } func pow(ec *Frame, args []types.Value, opts map[string]types.Value) { var b, p float64 ScanArgs(args, &b, &p) TakeNoOpt(opts) out := ec.ports[1].Chan out <- floatToString(math.Pow(b, p)) } func mod(ec *Frame, args []types.Value, opts map[string]types.Value) { var a, b int ScanArgs(args, &a, &b) TakeNoOpt(opts) out := ec.ports[1].Chan out <- types.String(strconv.Itoa(a % b)) } func randFn(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoArg(args) TakeNoOpt(opts) out := ec.ports[1].Chan out <- floatToString(rand.Float64()) } func randint(ec *Frame, args []types.Value, opts map[string]types.Value) { var low, high int ScanArgs(args, &low, &high) TakeNoOpt(opts) if low >= high { throw(ErrArgs) } out := ec.ports[1].Chan i := low + rand.Intn(high-low) out <- types.String(strconv.Itoa(i)) } elvish-0.11+ds1/eval/builtin_fn_num_test.go000066400000000000000000000010751323000013700207250ustar00rootroot00000000000000package eval import "testing" func TestBuiltinFnNum(t *testing.T) { runTests(t, []Test{ {`== 1 1.0`, wantTrue}, {`== 10 0xa`, wantTrue}, {`== a a`, want{err: errAny}}, {`> 0x10 1`, wantTrue}, // TODO test more edge cases {"+ 233100 233", want{out: strs("233333")}}, {"- 233333 233100", want{out: strs("233")}}, {"- 233", want{out: strs("-233")}}, {"* 353 661", want{out: strs("233333")}}, {"/ 233333 353", want{out: strs("661")}}, {"/ 1 0", want{out: strs("+Inf")}}, {"^ 16 2", want{out: strs("256")}}, {"% 23 7", want{out: strs("2")}}, }) } elvish-0.11+ds1/eval/builtin_fn_str.go000066400000000000000000000131251323000013700176760ustar00rootroot00000000000000package eval import ( "bytes" "errors" "regexp" "strconv" "strings" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/util" ) // String operations. var ErrInput = errors.New("input error") func init() { addToBuiltinFns([]*BuiltinFn{ {"s", wrapStrCompare(func(a, b string) bool { return a > b })}, {">=s", wrapStrCompare(func(a, b string) bool { return a >= b })}, {"to-string", toString}, {"joins", joins}, {"splits", splits}, {"replaces", replaces}, {"ord", ord}, {"base", base}, {"wcswidth", wcswidth}, {"-override-wcwidth", overrideWcwidth}, {"has-prefix", hasPrefix}, {"has-suffix", hasSuffix}, {"eawk", eawk}, }) } func wrapStrCompare(cmp func(a, b string) bool) BuiltinFnImpl { return func(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) for _, a := range args { if _, ok := a.(types.String); !ok { throw(ErrArgs) } } result := true for i := 0; i < len(args)-1; i++ { if !cmp(string(args[i].(types.String)), string(args[i+1].(types.String))) { result = false break } } ec.OutputChan() <- types.Bool(result) } } // toString converts all arguments to strings. func toString(ec *Frame, args []types.Value, opts map[string]types.Value) { TakeNoOpt(opts) out := ec.OutputChan() for _, a := range args { out <- types.String(types.ToString(a)) } } // joins joins all input strings with a delimiter. func joins(ec *Frame, args []types.Value, opts map[string]types.Value) { var sepv types.String iterate := ScanArgsOptionalInput(ec, args, &sepv) sep := string(sepv) TakeNoOpt(opts) var buf bytes.Buffer iterate(func(v types.Value) { if s, ok := v.(types.String); ok { if buf.Len() > 0 { buf.WriteString(sep) } buf.WriteString(string(s)) } else { throwf("join wants string input, got %s", v.Kind()) } }) out := ec.ports[1].Chan out <- types.String(buf.String()) } // splits splits an argument strings by a delimiter and writes all pieces. func splits(ec *Frame, args []types.Value, opts map[string]types.Value) { var ( s, sep types.String optMax int ) ScanArgs(args, &sep, &s) ScanOpts(opts, OptToScan{"max", &optMax, types.String("-1")}) out := ec.ports[1].Chan parts := strings.SplitN(string(s), string(sep), optMax) for _, p := range parts { out <- types.String(p) } } func replaces(ec *Frame, args []types.Value, opts map[string]types.Value) { var ( old, repl, s types.String optMax int ) ScanArgs(args, &old, &repl, &s) ScanOpts(opts, OptToScan{"max", &optMax, types.String("-1")}) ec.ports[1].Chan <- types.String(strings.Replace(string(s), string(old), string(repl), optMax)) } func ord(ec *Frame, args []types.Value, opts map[string]types.Value) { var s types.String ScanArgs(args, &s) TakeNoOpt(opts) out := ec.ports[1].Chan for _, r := range s { out <- types.String("0x" + strconv.FormatInt(int64(r), 16)) } } // ErrBadBase is thrown by the "base" builtin if the base is smaller than 2 or // greater than 36. var ErrBadBase = errors.New("bad base") func base(ec *Frame, args []types.Value, opts map[string]types.Value) { var ( b int nums []int ) ScanArgsVariadic(args, &b, &nums) TakeNoOpt(opts) if b < 2 || b > 36 { throw(ErrBadBase) } out := ec.ports[1].Chan for _, num := range nums { out <- types.String(strconv.FormatInt(int64(num), b)) } } func wcswidth(ec *Frame, args []types.Value, opts map[string]types.Value) { var s types.String ScanArgs(args, &s) TakeNoOpt(opts) out := ec.ports[1].Chan out <- types.String(strconv.Itoa(util.Wcswidth(string(s)))) } func overrideWcwidth(ec *Frame, args []types.Value, opts map[string]types.Value) { var ( s types.String w int ) ScanArgs(args, &s, &w) TakeNoOpt(opts) r, err := toRune(s) maybeThrow(err) util.OverrideWcwidth(r, w) } func hasPrefix(ec *Frame, args []types.Value, opts map[string]types.Value) { var s, prefix types.String ScanArgs(args, &s, &prefix) TakeNoOpt(opts) ec.OutputChan() <- types.Bool(strings.HasPrefix(string(s), string(prefix))) } func hasSuffix(ec *Frame, args []types.Value, opts map[string]types.Value) { var s, suffix types.String ScanArgs(args, &s, &suffix) TakeNoOpt(opts) ec.OutputChan() <- types.Bool(strings.HasSuffix(string(s), string(suffix))) } var eawkWordSep = regexp.MustCompile("[ \t]+") // eawk takes a function. For each line in the input stream, it calls the // function with the line and the words in the line. The words are found by // stripping the line and splitting the line by whitespaces. The function may // call break and continue. Overall this provides a similar functionality to // awk, hence the name. func eawk(ec *Frame, args []types.Value, opts map[string]types.Value) { var f Fn iterate := ScanArgsOptionalInput(ec, args, &f) TakeNoOpt(opts) broken := false iterate(func(v types.Value) { if broken { return } line, ok := v.(types.String) if !ok { throw(ErrInput) } args := []types.Value{line} for _, field := range eawkWordSep.Split(strings.Trim(string(line), " \t"), -1) { args = append(args, types.String(field)) } newec := ec.fork("fn of eawk") // TODO: Close port 0 of newec. ex := newec.PCall(f, args, NoOpts) ClosePorts(newec.ports) if ex != nil { switch ex.(*Exception).Cause { case nil, Continue: // nop case Break: broken = true default: throw(ex) } } }) } elvish-0.11+ds1/eval/builtin_fn_str_test.go000066400000000000000000000021341323000013700207330ustar00rootroot00000000000000package eval import "testing" func TestBuiltinFnStr(t *testing.T) { runTests(t, []Test{ {`==s haha haha`, wantTrue}, {`==s 10 10.0`, wantFalse}, {`\n"), {"nop", wantNothing}, {"nop a b", wantNothing}, {"nop &k=v", wantNothing}, {"nop a b &k=v", wantNothing}, {"kind-of bare 'str' [] [&] []{ }", want{out: strs("string", "string", "list", "map", "fn")}}, {`bool $true`, wantTrue}, {`bool a`, wantTrue}, {`bool [a]`, wantTrue}, // "Empty" values are also true in Elvish {`bool []`, wantTrue}, {`bool [&]`, wantTrue}, {`bool 0`, wantTrue}, {`bool ""`, wantTrue}, // Only errors and $false are false {`bool ?(fail x)`, wantFalse}, {`bool $false`, wantFalse}, {`not $false`, wantTrue}, {`not ?(fail x)`, wantTrue}, {`not $true`, wantFalse}, {`not 0`, wantFalse}, {`is 1 1`, wantTrue}, {`is a b`, wantFalse}, {`is [] []`, wantTrue}, {`is [1] [1]`, wantFalse}, {`eq 1 1`, wantTrue}, {`eq a b`, wantFalse}, {`eq [] []`, wantTrue}, {`eq [1] [1]`, wantTrue}, {`not-eq a b`, wantTrue}, {`f=(constantly foo); $f; $f`, want{out: strs("foo", "foo")}}, {`(constantly foo) bad`, want{err: errAny}}, } func TestBuiltinFn(t *testing.T) { runTests(t, builtinFnTests) } elvish-0.11+ds1/eval/builtin_ns.go000066400000000000000000000014271323000013700170250ustar00rootroot00000000000000package eval import ( "strconv" "strings" "syscall" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" ) func makeBuiltinNs() Ns { ns := Ns{ "_": vartypes.NewBlackhole(), "pid": vartypes.NewRo(types.String(strconv.Itoa(syscall.Getpid()))), "ok": vartypes.NewRo(OK), "true": vartypes.NewRo(types.Bool(true)), "false": vartypes.NewRo(types.Bool(false)), "paths": &EnvList{envName: "PATH"}, "pwd": PwdVariable{}, } AddBuiltinFns(ns, builtinFns...) return ns } // AddBuiltinFns adds builtin functions to a namespace. func AddBuiltinFns(ns Ns, fns ...*BuiltinFn) { for _, b := range fns { name := b.Name if i := strings.IndexRune(b.Name, ':'); i != -1 { name = b.Name[i+1:] } ns[name+FnSuffix] = vartypes.NewRo(b) } } elvish-0.11+ds1/eval/builtin_special.go000066400000000000000000000351441323000013700200300ustar00rootroot00000000000000package eval // Builtin special forms. Special forms behave mostly like ordinary commands - // they are valid commands syntactically, and can take part in pipelines - but // they have special rules for the evaluation of their arguments and can affect // the compilation phase (whereas ordinary commands can only affect the // evaluation phase). // // For instance, the "and" special form evaluates its arguments from left to // right, and stops as soon as one booleanly false value is obtained: the // command "and $false (fail haha)" does not produce an exception. // // As another instance, the "del" special form removes a variable, affecting the // compiler. // // Flow control structures are also implemented as special forms in elvish, with // closures functioning as code blocks. import ( "errors" "fmt" "os" "path/filepath" "strings" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/parse" ) type compileBuiltin func(*compiler, *parse.Form) OpFunc var ( // ErrNoLibDir is thrown by "use" when the Evaler does not have a library // directory. ErrNoLibDir = errors.New("Evaler does not have a lib directory") // ErrRelativeUseNotFromMod is thrown by "use" when relative use is used // not from a module ErrRelativeUseNotFromMod = errors.New("Relative use not from module") // ErrRelativeUseGoesOutsideLib is thrown when a relative use goes out of // the library directory. ErrRelativeUseGoesOutsideLib = errors.New("Module outside library directory") ) var builtinSpecials map[string]compileBuiltin // IsBuiltinSpecial is the set of all names of builtin special forms. It is // intended for external consumption, e.g. the syntax highlighter. var IsBuiltinSpecial = map[string]bool{} func init() { // Needed to avoid initialization loop builtinSpecials = map[string]compileBuiltin{ "del": compileDel, "fn": compileFn, "use": compileUse, "and": compileAnd, "or": compileOr, "if": compileIf, "while": compileWhile, "for": compileFor, "try": compileTry, } for name := range builtinSpecials { IsBuiltinSpecial[name] = true } } const delArgMsg = "arguments to del must be variable or variable elements" // DelForm = 'del' { VariablePrimary } func compileDel(cp *compiler, fn *parse.Form) OpFunc { var ops []Op for _, cn := range fn.Args { cp.compiling(cn) if len(cn.Indexings) != 1 { cp.errorf(delArgMsg) continue } head, indicies := cn.Indexings[0].Head, cn.Indexings[0].Indicies if head.Type != parse.Bareword { if head.Type == parse.Variable { cp.errorf("arguments to del must drop $") } else { cp.errorf(delArgMsg) } continue } explode, ns, name := ParseVariable(head.Value) if explode { cp.errorf("arguments to del may be have a leading @") continue } var f OpFunc if len(indicies) == 0 { switch ns { case "", "local": if !cp.thisScope().has(name) { cp.errorf("no variable $%s in local scope", name) continue } cp.thisScope().del(name) f = newDelLocalVariableOp(name) case "E": f = newDelEnvVariableOp(name) default: cp.errorf("only variables in local: or E: can be deleted") continue } } else { if !cp.registerVariableGet(ns, name) { cp.errorf("no variable $%s", head.Value) continue } f = newDelElementOp(ns, name, head.Begin(), head.End(), cp.arrayOps(indicies)) } ops = append(ops, Op{f, cn.Begin(), cn.End()}) } return func(f *Frame) { for _, op := range ops { op.Exec(f) } } } func newDelLocalVariableOp(name string) OpFunc { return func(f *Frame) { delete(f.local, name) } } func newDelElementOp(ns, name string, begin, headEnd int, indexOps []ValuesOp) OpFunc { ends := make([]int, len(indexOps)+1) ends[0] = headEnd for i, op := range indexOps { ends[i+1] = op.End } return func(f *Frame) { var indicies []types.Value for _, indexOp := range indexOps { indexValues := indexOp.Exec(f) if len(indexValues) != 1 { f.errorpf(indexOp.Begin, indexOp.End, "index must evaluate to a single value in argument to del") } indicies = append(indicies, indexValues[0]) } err := vartypes.DelElement(f.ResolveVar(ns, name), indicies) if err != nil { if level := vartypes.GetElementErrorLevel(err); level >= 0 { f.errorpf(begin, ends[level], "%s", err.Error()) } throw(err) } } } func newDelEnvVariableOp(name string) OpFunc { return func(*Frame) { maybeThrow(os.Unsetenv(name)) } } // makeFnOp wraps an op such that a return is converted to an ok. func makeFnOp(op Op) Op { return Op{func(ec *Frame) { err := ec.PEval(op) if err != nil && err.(*Exception).Cause != Return { // rethrow throw(err) } }, op.Begin, op.End} } // FnForm = 'fn' StringPrimary LambdaPrimary // // fn f []{foobar} is a shorthand for set '&'f = []{foobar}. func compileFn(cp *compiler, fn *parse.Form) OpFunc { args := cp.walkArgs(fn) nameNode := args.next() varName := mustString(cp, nameNode, "must be a literal string") + FnSuffix bodyNode := args.nextMustLambda() args.mustEnd() cp.registerVariableSetQname(":" + varName) op := cp.lambda(bodyNode) return func(ec *Frame) { // Initialize the function variable with the builtin nop // function. This step allows the definition of recursive // functions; the actual function will never be called. ec.local[varName] = vartypes.NewPtr(&BuiltinFn{"", nop}) closure := op(ec)[0].(*Closure) closure.Op = makeFnOp(closure.Op) err := ec.local[varName].Set(closure) maybeThrow(err) } } // UseForm = 'use' StringPrimary func compileUse(cp *compiler, fn *parse.Form) OpFunc { if len(fn.Args) == 0 { end := fn.Head.End() cp.errorpf(end, end, "lack module name") } else if len(fn.Args) >= 2 { cp.errorpf(fn.Args[1].Begin(), fn.Args[len(fn.Args)-1].End(), "superfluous argument(s)") } spec := mustString(cp, fn.Args[0], "should be a literal string") // When modspec = "a/b/c:d", modname is c:d, and modpath is a/b/c/d modname := spec[strings.LastIndexByte(spec, '/')+1:] modpath := strings.Replace(spec, ":", "/", -1) cp.thisScope().set(modname + NsSuffix) return func(ec *Frame) { use(ec, modname, modpath) } } func use(ec *Frame, modname, modpath string) { resolvedPath := "" if strings.HasPrefix(modpath, "./") || strings.HasPrefix(modpath, "../") { if ec.srcMeta.typ != SrcModule { throw(ErrRelativeUseNotFromMod) } // Resolve relative modpath. resolvedPath = filepath.Clean(filepath.Dir(ec.srcMeta.name) + "/" + modpath) } else { resolvedPath = filepath.Clean(modpath) } if strings.HasPrefix(resolvedPath, "../") { throw(ErrRelativeUseGoesOutsideLib) } // Put the just loaded module into local scope. ec.local[modname+NsSuffix] = vartypes.NewPtr(loadModule(ec, resolvedPath)) } func loadModule(ec *Frame, name string) Ns { if ns, ok := ec.Evaler.modules[name]; ok { // Module already loaded. return ns } // Load the source. var path, code string if ec.libDir == "" { throw(ErrNoLibDir) } path = filepath.Join(ec.libDir, name+".elv") if _, err := os.Stat(path); os.IsNotExist(err) { // File does not exist. Try loading from the table of builtin // modules. var ok bool if code, ok = ec.bundled[name]; ok { // Source is loaded. Do nothing more. path = "" } else { throw(fmt.Errorf("cannot load %s: %s does not exist", name, path)) } } else { // File exists. Load it. code, err = readFileUTF8(path) maybeThrow(err) } n, err := parse.Parse(name, code) maybeThrow(err) // Make an empty scope to evaluate the module in. meta := NewModuleSource(name, path, code) modGlobal := Ns{} newEc := &Frame{ ec.Evaler, meta, modGlobal, make(Ns), ec.ports, 0, len(code), ec.addTraceback(), false, } op, err := newEc.Compile(n, meta) maybeThrow(err) // Load the namespace before executing. This avoids mutual and self use's to // result in an infinite recursion. ec.Evaler.modules[name] = modGlobal err = newEc.PEval(op) if err != nil { // Unload the namespace. delete(ec.modules, name) throw(err) } return modGlobal } // compileAnd compiles the "and" special form. // The and special form evaluates arguments until a false-ish values is found // and outputs it; the remaining arguments are not evaluated. If there are no // false-ish values, the last value is output. If there are no arguments, it // outputs $true, as if there is a hidden $true before actual arguments. func compileAnd(cp *compiler, fn *parse.Form) OpFunc { return compileAndOr(cp, fn, true, false) } // compileOr compiles the "or" special form. // The or special form evaluates arguments until a true-ish values is found and // outputs it; the remaining arguments are not evaluated. If there are no // true-ish values, the last value is output. If there are no arguments, it // outputs $false, as if there is a hidden $false before actual arguments. func compileOr(cp *compiler, fn *parse.Form) OpFunc { return compileAndOr(cp, fn, false, true) } func compileAndOr(cp *compiler, fn *parse.Form, init, stopAt bool) OpFunc { argOps := cp.compoundOps(fn.Args) return func(ec *Frame) { var lastValue types.Value = types.Bool(init) for _, op := range argOps { values := op.Exec(ec) for _, value := range values { if types.ToBool(value) == stopAt { ec.OutputChan() <- value return } lastValue = value } } ec.OutputChan() <- lastValue } } func compileIf(cp *compiler, fn *parse.Form) OpFunc { args := cp.walkArgs(fn) var condNodes []*parse.Compound var bodyNodes []*parse.Primary for { condNodes = append(condNodes, args.next()) bodyNodes = append(bodyNodes, args.nextMustLambda()) if !args.nextIs("elif") { break } } elseNode := args.nextMustLambdaIfAfter("else") args.mustEnd() condOps := cp.compoundOps(condNodes) bodyOps := cp.primaryOps(bodyNodes) var elseOp ValuesOp if elseNode != nil { elseOp = cp.primaryOp(elseNode) } return func(ec *Frame) { bodies := make([]Callable, len(bodyOps)) for i, bodyOp := range bodyOps { bodies[i] = bodyOp.execlambdaOp(ec) } else_ := elseOp.execlambdaOp(ec) for i, condOp := range condOps { if allTrue(condOp.Exec(ec.fork("if cond"))) { bodies[i].Call(ec.fork("if body"), NoArgs, NoOpts) return } } if elseOp.Func != nil { else_.Call(ec.fork("if else"), NoArgs, NoOpts) } } } func compileWhile(cp *compiler, fn *parse.Form) OpFunc { args := cp.walkArgs(fn) condNode := args.next() bodyNode := args.nextMustLambda() args.mustEnd() condOp := cp.compoundOp(condNode) bodyOp := cp.primaryOp(bodyNode) return func(ec *Frame) { body := bodyOp.execlambdaOp(ec) for { cond := condOp.Exec(ec.fork("while cond")) if !allTrue(cond) { break } err := ec.fork("while").PCall(body, NoArgs, NoOpts) if err != nil { exc := err.(*Exception) if exc.Cause == Continue { // do nothing } else if exc.Cause == Break { continue } else { throw(err) } } } } } func compileFor(cp *compiler, fn *parse.Form) OpFunc { args := cp.walkArgs(fn) varNode := args.next() iterNode := args.next() bodyNode := args.nextMustLambda() elseNode := args.nextMustLambdaIfAfter("else") args.mustEnd() varOp, restOp := cp.lvaluesOp(varNode.Indexings[0]) if restOp.Func != nil { cp.errorpf(restOp.Begin, restOp.End, "rest not allowed") } iterOp := cp.compoundOp(iterNode) bodyOp := cp.primaryOp(bodyNode) var elseOp ValuesOp if elseNode != nil { elseOp = cp.primaryOp(elseNode) } return func(ec *Frame) { variables := varOp.Exec(ec) if len(variables) != 1 { ec.errorpf(varOp.Begin, varOp.End, "only one variable allowed") } variable := variables[0] iterable := ec.ExecAndUnwrap("value being iterated", iterOp).One().Iterable() body := bodyOp.execlambdaOp(ec) elseBody := elseOp.execlambdaOp(ec) iterated := false iterable.Iterate(func(v types.Value) bool { iterated = true err := variable.Set(v) maybeThrow(err) err = ec.fork("for").PCall(body, NoArgs, NoOpts) if err != nil { exc := err.(*Exception) if exc.Cause == Continue { // do nothing } else if exc.Cause == Break { return false } else { throw(err) } } return true }) if !iterated && elseBody != nil { elseBody.Call(ec.fork("for else"), NoArgs, NoOpts) } } } func compileTry(cp *compiler, fn *parse.Form) OpFunc { logger.Println("compiling try") args := cp.walkArgs(fn) bodyNode := args.nextMustLambda() logger.Printf("body is %q", bodyNode.SourceText()) var exceptVarNode *parse.Indexing var exceptNode *parse.Primary if args.nextIs("except") { logger.Println("except-ing") n := args.peek() // Is this a variable? if len(n.Indexings) == 1 && n.Indexings[0].Head.Type == parse.Bareword { exceptVarNode = n.Indexings[0] args.next() } exceptNode = args.nextMustLambda() } elseNode := args.nextMustLambdaIfAfter("else") finallyNode := args.nextMustLambdaIfAfter("finally") args.mustEnd() var exceptVarOp LValuesOp var bodyOp, exceptOp, elseOp, finallyOp ValuesOp bodyOp = cp.primaryOp(bodyNode) if exceptVarNode != nil { var restOp LValuesOp exceptVarOp, restOp = cp.lvaluesOp(exceptVarNode) if restOp.Func != nil { cp.errorpf(restOp.Begin, restOp.End, "may not use @rest in except variable") } } if exceptNode != nil { exceptOp = cp.primaryOp(exceptNode) } if elseNode != nil { elseOp = cp.primaryOp(elseNode) } if finallyNode != nil { finallyOp = cp.primaryOp(finallyNode) } return func(ec *Frame) { body := bodyOp.execlambdaOp(ec) exceptVar := exceptVarOp.execMustOne(ec) except := exceptOp.execlambdaOp(ec) else_ := elseOp.execlambdaOp(ec) finally := finallyOp.execlambdaOp(ec) err := ec.fork("try body").PCall(body, NoArgs, NoOpts) if err != nil { if except != nil { if exceptVar != nil { err := exceptVar.Set(err.(*Exception)) maybeThrow(err) } err = ec.fork("try except").PCall(except, NoArgs, NoOpts) } } else { if else_ != nil { err = ec.fork("try else").PCall(else_, NoArgs, NoOpts) } } if finally != nil { finally.Call(ec.fork("try finally"), NoArgs, NoOpts) } if err != nil { throw(err) } } } // execLambdaOp executes a ValuesOp that is known to yield a lambda and returns // the lambda. If the ValuesOp is empty, it returns a nil. func (op ValuesOp) execlambdaOp(ec *Frame) Callable { if op.Func == nil { return nil } return op.Exec(ec)[0].(Callable) } // execMustOne executes the LValuesOp and raises an exception if it does not // evaluate to exactly one Variable. If the given LValuesOp is empty, it returns // nil. func (op LValuesOp) execMustOne(ec *Frame) vartypes.Variable { if op.Func == nil { return nil } variables := op.Exec(ec) if len(variables) != 1 { ec.errorpf(op.Begin, op.End, "should be one variable") } return variables[0] } elvish-0.11+ds1/eval/builtin_special_test.go000066400000000000000000000050211323000013700210560ustar00rootroot00000000000000package eval import "testing" var builtinSpecialTests = []Test{ // del NewTest("x = [&k=v &k2=v2]; del x[k2]; keys $x").WantOutStrings("k"), NewTest("x = [[&k=v &k2=v2]]; del x[0][k2]; keys $x[0]").WantOutStrings("k"), // if {"if true { put then }", want{out: strs("then")}}, {"if $false { put then } else { put else }", want{out: strs("else")}}, {"if $false { put 1 } elif $false { put 2 } else { put 3 }", want{out: strs("3")}}, {"if $false { put 2 } elif true { put 2 } else { put 3 }", want{out: strs("2")}}, // try {"try { nop } except { put bad } else { put good }", want{out: strs("good")}}, {"try { e:false } except - { put bad } else { put good }", want{out: strs("bad")}}, // while {"x=0; while (< $x 4) { put $x; x=(+ $x 1) }", want{out: strs("0", "1", "2", "3")}}, // for {"for x [tempora mores] { put 'O '$x }", want{out: strs("O tempora", "O mores")}}, // break {"for x [a] { break } else { put $x }", wantNothing}, // else {"for x [a] { put $x } else { put $x }", want{out: strs("a")}}, // continue {"for x [a b] { put $x; continue; put $x; }", want{out: strs("a", "b")}}, // fn. {"fn f [x]{ put x=$x'.' }; f lorem; f ipsum", want{out: strs("x=lorem.", "x=ipsum.")}}, // return. {"fn f []{ put a; return; put b }; f", want{out: strs("a")}}, // Modules (see setup_datadir_test.go for setup) // "use" imports a module. {`use lorem; put $lorem:name`, want{out: strs("lorem")}}, // imports are lexically scoped // TODO: Support testing for compilation error // {`{ use lorem }; put $lorem:name`, want{err: errAny}}, // use of imported variable is captured in upvalue {`({ use lorem; put { { put $lorem:name } } })`, want{out: strs("lorem")}}, // use of imported function is also captured in upvalue {`{ use lorem; { lorem:put-name } }`, want{out: strs("lorem")}}, // multi-level module names {`use a:b:c:d; put $a:b:c:d:name`, want{out: strs("a/b/c/d")}}, // shortening module names by using slashes for some path prefix {`use a:b/c:d; put $c:d:name`, want{out: strs("a/b/c/d")}}, // importing the same module under different names {`use a/b/c/d; use a/b/c:d; eq $d:name $c:d:name`, wantTrue}, // module is cached after first use {`use has/init; use has:init`, want{out: strs("has/init")}}, // overriding module {`use d; put $d:name; use a/b/c/d; put $d:name`, want{out: strs("d", "a/b/c/d")}}, // relative uses {`use a/b/c/x; put $x:d $x:lorem`, want{out: strs("a/b/c/d", "lorem")}}, // TODO: Test module namespace } func TestBuiltinSpecial(t *testing.T) { runTests(t, builtinSpecialTests) } elvish-0.11+ds1/eval/bundled/000077500000000000000000000000001323000013700157415ustar00rootroot00000000000000elvish-0.11+ds1/eval/bundled/binding.elv.go000066400000000000000000000104401323000013700204660ustar00rootroot00000000000000package bundled const bindingElv = ` fn install { edit:insert:binding = (edit:binding-table [ &Default= $edit:insert:default~ &F2= $edit:toggle-quote-paste~ &Up= $edit:history:start~ &Down= $edit:end-of-history~ &Right= $edit:move-dot-right~ &Left= $edit:move-dot-left~ &Home= $edit:move-dot-sol~ &Delete= $edit:kill-rune-right~ &End= $edit:move-dot-eol~ &Tab= $edit:completion:smart-start~ &Enter= $edit:smart-enter~ &Backspace= $edit:kill-rune-left~ &Alt-Up= $edit:move-dot-up~ &Alt-Down= $edit:move-dot-down~ &Alt-Enter= $edit:insert-key~ &Alt-.= $edit:insert-last-word~ &Alt-1= $edit:lastcmd:start~ &Alt-b= $edit:move-dot-left-word~ &Alt-f= $edit:move-dot-right-word~ &Ctrl-Right= $edit:move-dot-right-word~ &Ctrl-Left= $edit:move-dot-left-word~ &Ctrl-D= $edit:return-eof~ &Ctrl-H= $edit:kill-rune-left~ &Ctrl-K= $edit:kill-line-right~ &Ctrl-L= $edit:location:start~ &Ctrl-N= $edit:navigation:start~ &Ctrl-R= $edit:histlist:start~ &Ctrl-U= $edit:kill-line-left~ &Ctrl-V= $edit:insert-raw~ &Ctrl-W= $edit:kill-word-left~ ]) edit:command:binding = (edit:binding-table [ &Default= $edit:command:default~ &'$'= $edit:move-dot-eol~ &0= $edit:move-dot-sol~ &D= $edit:kill-line-right~ &b= $edit:move-dot-left-word~ &h= $edit:move-dot-left~ &i= $edit:insert:start~ &j= $edit:move-dot-down~ &k= $edit:move-dot-up~ &l= $edit:move-dot-right~ &w= $edit:move-dot-right-word~ &x= $edit:kill-rune-right~ ]) edit:history:binding = (edit:binding-table [ &Default= $edit:history:default~ &Up= $edit:history:up~ &Down= $edit:history:down-or-quit~ &Ctrl-R= $edit:history:switch-to-histlist~ &'Ctrl-['= $edit:insert:start~ ]) edit:completion:binding = (edit:binding-table [ &Default= $edit:completion:default~ &Up= $edit:completion:up~ &Down= $edit:completion:down~ &Right= $edit:completion:right~ &Left= $edit:completion:left~ &Tab= $edit:completion:down-cycle~ &Enter= $edit:completion:accept~ &Shift-Tab= $edit:completion:up-cycle~ &Ctrl-F= $edit:completion:trigger-filter~ &'Ctrl-['= $edit:insert:start~ ]) edit:listing:binding = (edit:binding-table [ &Default= $edit:listing:default~ &Up= $edit:listing:up~ &Down= $edit:listing:down~ &PageUp= $edit:listing:page-up~ &PageDown= $edit:listing:page-down~ &Tab= $edit:listing:down-cycle~ &Enter= $edit:listing:accept-close~ &Backspace= $edit:listing:backspace~ &Shift-Tab= $edit:listing:up-cycle~ &Alt-Enter= $edit:listing:accept~ &'Ctrl-['= $edit:insert:start~ ]) edit:histlist:binding = (edit:binding-table [ &Ctrl-D= $edit:histlist:toggle-dedup~ &Ctrl-G= $edit:histlist:toggle-case-sensitivity~ ]) edit:location:binding = (edit:binding-table [&]) edit:lastcmd:binding = (edit:binding-table [ &Default= $edit:lastcmd:alt-default~ ]) edit:navigation:binding = (edit:binding-table [ &Default= $edit:navigation:default~ &Up= $edit:navigation:up~ &Down= $edit:navigation:down~ &Right= $edit:navigation:right~ &Left= $edit:navigation:left~ &PageUp= $edit:navigation:page-up~ &PageDown= $edit:navigation:page-down~ &Enter= $edit:navigation:insert-selected-and-quit~ &Alt-Up= $edit:navigation:file-preview-up~ &Alt-Down= $edit:navigation:file-preview-down~ &Alt-Enter= $edit:navigation:insert-selected~ &Ctrl-F= $edit:navigation:trigger-filter~ &Ctrl-H= $edit:navigation:trigger-shown-hidden~ &'Ctrl-['= $edit:insert:start~ ]) edit:narrow:binding = (edit:binding-table [&]) } ` elvish-0.11+ds1/eval/bundled/bundled.go000066400000000000000000000004561323000013700177120ustar00rootroot00000000000000// Package bundled keeps bundled modules. package bundled // Get returns a map of bundled modules. func Get() map[string]string { return map[string]string{ "binding": bindingElv, "epm": epmElv, "narrow": narrowElv, "readline-binding": readlineBindingElv, } } elvish-0.11+ds1/eval/bundled/bundled_test.go000066400000000000000000000002061323000013700207420ustar00rootroot00000000000000package bundled import "testing" func TestGet(t *testing.T) { if Get()["epm"] != epmElv { t.Error(`Get()["epm"] != epmElv`) } } elvish-0.11+ds1/eval/bundled/epm.elv.go000066400000000000000000000041201323000013700176330ustar00rootroot00000000000000package bundled const epmElv = ` -data-dir = ~/.elvish -installed = $-data-dir/epm-installed fn -info [text]{ print (edit:styled '=> ' green) echo $text } fn -error [text]{ print (edit:styled '=> ' red) echo $text } fn add-installed [@pkgs]{ if (eq $pkgs []) { -error 'Must specify at least one package.' return } for pkg $pkgs { echo $pkg >> $-installed } } fn -get-url [pkg]{ put https://$pkg } fn -install-one [pkg]{ dest = $-data-dir/lib/$pkg if ?(test -e $dest) { -error 'Package '$pkg' already exists locally.' return } -info 'Installing '$pkg mkdir -p $dest git clone (-get-url $pkg) $dest add-installed $pkg } fn install [@pkgs]{ if (eq $pkgs []) { -error 'Must specify at least one package.' return } for pkg $pkgs { -install-one $pkg } } fn installed { if ?(test -f $-installed) { cat $-installed } } fn -upgrade-one [pkg]{ dest = $-data-dir/lib/$pkg if (not ?(test -d $dest)) { -error 'Package '$pkg' not installed locally.' return } -info 'Upgrading package '$pkg git -C $dest pull } fn upgrade [@pkgs]{ if (eq $pkgs []) { pkgs = [(installed)] -info 'Upgrading all installed packages' } for pkg $pkgs { -upgrade-one $pkg } } fn -uninstall-one [pkg]{ installed-pkgs = [(installed)] if (not (has-value $installed-pkgs $pkg)) { -error 'Package '$pkg' is not registered as installed.' return } dest = $-data-dir/lib/$pkg if (not ?(test -d $dest)) { -error 'Package '$pkg' does not exist locally.' return } -info 'Removing package '$pkg rm -rf $dest # issue #486 { for installed $installed-pkgs { if (not-eq $installed $pkg) { echo $installed } } } > $-installed } fn uninstall [@pkgs]{ if (eq $pkgs []) { -error 'Must specify at least one package.' return } for pkg $pkgs { -uninstall-one $pkg } } ` elvish-0.11+ds1/eval/bundled/narrow.elv.go000066400000000000000000000073771323000013700204030ustar00rootroot00000000000000package bundled const narrowElv = ` # Implementation of location, history and lastcmd mode using the new # -narrow-read mode. One advantage of this is that it allows the # execution of arbitrary hooks before or after each mode. # # Usage: # use narrow # narrow:bind-trigger-keys # # narrow:bind-trigger-keys binds keys for location, history and lastcmd # modes. Without options, it uses the default bindings (same as the # default bindings for edit:location, edit:history and edit:lastcmd), # but different keys can be specified with the options. To disable a # binding, specify its key as "". # Example: # narrow:bind-trigger-keys &location=Alt-l &lastcmd="" # Hooks # Each hook variable is an array which must contain lambdas, all of # which will be executed in sequence before and after the # corresponding mode. # Example (list the new directory after switching to it in location mode): # narrow:after-location = [ $@narrow:after-location { edit:insert-at-dot "ls"; edit:smart-enter } ] before-location = [] after-location = [] before-history = [] after-history = [] before-lastcmd = [] after-lastcmd = [] fn location { for hook $before-location { $hook } candidates = [(dir-history | each [arg]{ score = (splits . $arg[score] | take 1) put [ &content=$arg[path] &display=$score" "$arg[path] ] })] edit:-narrow-read { put $@candidates } [arg]{ cd $arg[content] for hook $after-location { $hook } } &modeline="[narrow] Location " &ignore-case=$true } fn history { for hook $before-history { $hook } candidates = [(edit:command-history | each [arg]{ put [ &content=$arg[cmd] &display=$arg[id]" "(replaces "\t" " " (replaces "\n" " " $arg[cmd])) ] })] edit:-narrow-read { put $@candidates } [arg]{ edit:replace-input $arg[content] for hook $after-history { $hook } } &modeline="[narrow] History " &keep-bottom=$true &ignore-case=$true } fn lastcmd { for hook $before-lastcmd { $hook } last = (edit:command-history -1) cmd = [ &content=$last[cmd] &display="M-1 "$last[cmd] &filter-text="" ] index = 0 candidates = [$cmd ( edit:wordify $last[cmd] | each [arg]{ put [ &content=$arg &display=$index" "$arg &filter-text=$index ] index = (+ $index 1) })] edit:-narrow-read { put $@candidates } [arg]{ edit:insert-at-dot $arg[content] for hook $after-lastcmd { $hook } } &modeline="[narrow] Lastcmd " &auto-commit=$true &bindings=[&M-1={ edit:narrow:accept-close }] &ignore-case=$true } fn -bind-insert [k f]{ edit:insert:binding[$k] = $f } fn -bind [k f]{ edit:narrow:binding[$k] = $f } # Bind keys for location, history and lastcmd modes. Without # options, it uses the default bindings, but different keys # can be specified with the options. To disable a binding, # specify its key as "". # Example: # narrow:bind-trigger-keys &location=Alt-l &lastcmd="" fn bind-trigger-keys [&location=C-l &history=C-r &lastcmd=M-1]{ if (not-eq $location "") { -bind-insert $location $location~ } if (not-eq $history "") { -bind-insert $history $history~ } if (not-eq $lastcmd "") { -bind-insert $lastcmd $lastcmd~ } } # Set up some default useful bindings for narrow mode -bind Up $edit:narrow:up~ -bind PageUp $edit:narrow:page-up~ -bind Down $edit:narrow:down~ -bind PageDown $edit:narrow:page-down~ -bind Tab $edit:narrow:down-cycle~ -bind S-Tab $edit:narrow:up-cycle~ -bind Backspace $edit:narrow:backspace~ -bind Enter $edit:narrow:accept-close~ -bind M-Enter $edit:narrow:accept~ -bind Default $edit:narrow:default~ -bind "C-[" $edit:insert:start~ -bind C-G $edit:narrow:toggle-ignore-case~ -bind C-D $edit:narrow:toggle-ignore-duplication~ ` elvish-0.11+ds1/eval/bundled/readline-binding.elv.go000066400000000000000000000035111323000013700222500ustar00rootroot00000000000000package bundled const readlineBindingElv = ` b=[k f]{ edit:insert:binding[$k] = $f } { $b Ctrl-A $edit:move-dot-sol~ $b Ctrl-B $edit:move-dot-left~ $b Ctrl-D { if (> (count $edit:current-command) 0) { edit:kill-rune-right } else { edit:return-eof } } $b Ctrl-E $edit:move-dot-eol~ $b Ctrl-F $edit:move-dot-right~ $b Ctrl-H $edit:kill-rune-left~ $b Ctrl-L { clear > /dev/tty } $b Ctrl-N $edit:end-of-history~ # TODO: ^O $b Ctrl-P $edit:history:start~ # TODO: ^S ^T ^X family ^Y ^_ $b Alt-b $edit:move-dot-left-word~ # TODO Alt-c Alt-d $b Alt-f $edit:move-dot-right-word~ # TODO Alt-l Alt-r Alt-u # Ctrl-N and Ctrl-L occupied by readline binding, $b to Alt- instead. $b Alt-n $edit:navigation:start~ $b Alt-l $edit:location:start~ } b=[k f]{ edit:completion:binding[$k] = $f } { $b Ctrl-B $edit:completion:left~ $b Ctrl-F $edit:completion:right~ $b Ctrl-N $edit:completion:down~ $b Ctrl-P $edit:completion:up~ $b Alt-f $edit:completion:trigger-filter~ } b=[k f]{ edit:navigation:binding[$k] = $f } { $b Ctrl-B $edit:navigation:left~ $b Ctrl-F $edit:navigation:right~ $b Ctrl-N $edit:navigation:down~ $b Ctrl-P $edit:navigation:up~ $b Alt-f $edit:navigation:trigger-filter~ } b=[k f]{ edit:history:binding[$k] = $f } { $b Ctrl-N $edit:history:down-or-quit~ $b Ctrl-P $edit:history:up~ $b Ctrl-G $edit:insert:start~ } b=[k f]{ edit:listing:binding[$k] = $f } { $b Ctrl-N $edit:listing:down~ $b Ctrl-P $edit:listing:up~ $b Ctrl-V $edit:listing:page-down~ $b Alt-v $edit:listing:page-up~ $b Ctrl-G $edit:insert:start~ } b=[k f]{ edit:histlist:binding[$k] = $f } { $b Alt-g $edit:histlist:toggle-case-sensitivity~ $b Alt-d $edit:histlist:toggle-dedup~ } ` elvish-0.11+ds1/eval/chdir.go000066400000000000000000000014711323000013700157470ustar00rootroot00000000000000package eval import ( "os" ) // AddDirer wraps the AddDir function. type AddDirer interface { // AddDir adds a directory with the given weight to some storage. AddDir(dir string, weight float64) error } // Chdir changes the current directory. On success it also updates the PWD // environment variable and records the new directory in the directory history. // It returns nil as long as the directory changing part succeeds. func Chdir(path string, store AddDirer) error { err := os.Chdir(path) if err != nil { return err } pwd, err := os.Getwd() if err != nil { logger.Println("getwd after cd:", err) return nil } os.Setenv("PWD", pwd) if store != nil { go func() { err := store.AddDir(pwd, 1) if err != nil { logger.Println("Failed to save dir to history:", err) } }() } return nil } elvish-0.11+ds1/eval/chdir_test.go000066400000000000000000000030471323000013700170070ustar00rootroot00000000000000package eval import ( "errors" "os" "testing" "time" "github.com/elves/elvish/util" ) type testAddDirer func(string, float64) error func (t testAddDirer) AddDir(dir string, weight float64) error { return t(dir, weight) } func TestChdir(t *testing.T) { util.WithTempDir(func(destDir string) { pwd, err := os.Getwd() if err != nil { panic(err) } defer func() { err = os.Chdir(pwd) if err != nil { panic(err) } }() chanAddedDir := make(chan string) testAddDirer := testAddDirer(func(dir string, weight float64) error { chanAddedDir <- dir // Error returned from here should not affect the return value of // Chdir return errors.New("fake error") }) err = Chdir(destDir, testAddDirer) if err != nil { t.Errorf("Chdir => error %v", err) } if env := os.Getenv("PWD"); env != destDir { t.Errorf("$PWD is %q after Chdir, want %q", env, destDir) } select { case addedDir := <-chanAddedDir: if addedDir != destDir { t.Errorf("Chdir called AddDir %q, want %q", addedDir[0], destDir) } case <-time.After(100 * time.Millisecond): t.Errorf("Chdir did not call AddDir within 100ms") } }) } const badDir = "/i/dont/exist" func TestChdirError(t *testing.T) { testAddDirer := testAddDirer(func(dir string, weight float64) error { t.Errorf("Chdir called AddDir when os.Chdir errors") return nil }) if _, err := os.Stat(badDir); err == nil { panic(badDir + " exists") } err := Chdir(badDir, testAddDirer) if err == nil { t.Errorf("Chdir => no error when dir does not exist") } } elvish-0.11+ds1/eval/closure.go000066400000000000000000000047741323000013700163430ustar00rootroot00000000000000package eval import ( "errors" "fmt" "unsafe" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/parse" "github.com/xiaq/persistent/hash" ) // ErrArityMismatch is thrown by a closure when the number of arguments the user // supplies does not match with what is required. var ErrArityMismatch = errors.New("arity mismatch") // Closure is a closure defined in elvish script. type Closure struct { ArgNames []string // The name for the rest argument. If empty, the function has fixed arity. RestArg string OptNames []string OptDefaults []types.Value Op Op Captured Ns SrcMeta *Source } var _ Fn = &Closure{} // Kind returns "fn". func (*Closure) Kind() string { return "fn" } // Equal compares by identity. func (c *Closure) Equal(rhs interface{}) bool { return c == rhs } func (c *Closure) Hash() uint32 { return hash.Pointer(unsafe.Pointer(c)) } // Repr returns an opaque representation "". func (c *Closure) Repr(int) string { return fmt.Sprintf("", c) } // Call calls a closure. func (c *Closure) Call(ec *Frame, args []types.Value, opts map[string]types.Value) { if c.RestArg != "" { if len(c.ArgNames) > len(args) { throwf("need %d or more arguments, got %d", len(c.ArgNames), len(args)) } } else { if len(c.ArgNames) != len(args) { throwf("need %d arguments, got %d", len(c.ArgNames), len(args)) } } // This evalCtx is dedicated to the current form, so we modify it in place. // BUG(xiaq): When evaluating closures, async access to global variables // and ports can be problematic. // Make upvalue namespace and capture variables. // TODO(xiaq): Is it safe to simply assign ec.up = c.Captured? ec.up = make(Ns) for name, variable := range c.Captured { ec.up[name] = variable } // Populate local scope with arguments, possibly a rest argument, and // options. ec.local = make(Ns) for i, name := range c.ArgNames { ec.local[name] = vartypes.NewPtr(args[i]) } if c.RestArg != "" { ec.local[c.RestArg] = vartypes.NewPtr(types.MakeList(args[len(c.ArgNames):]...)) } optUsed := make(map[string]struct{}) for i, name := range c.OptNames { v, ok := opts[name] if ok { optUsed[name] = struct{}{} } else { v = c.OptDefaults[i] } ec.local[name] = vartypes.NewPtr(v) } for name := range opts { _, used := optUsed[name] if !used { throwf("unknown option %s", parse.Quote(name)) } } ec.traceback = ec.addTraceback() ec.srcMeta = c.SrcMeta c.Op.Exec(ec) } elvish-0.11+ds1/eval/closure_test.go000066400000000000000000000006261323000013700173720ustar00rootroot00000000000000package eval import "testing" func TestClosure(t *testing.T) { runTests(t, []Test{ NewTest("kind-of { }").WantOutStrings("fn"), NewTest("eq { } { }").WantOutBools(false), NewTest("x = { }; put [&$x= foo][$x]").WantOutStrings("foo"), NewTest("[x]{ } a b").WantAnyErr(), NewTest("[x y]{ } a").WantAnyErr(), NewTest("[x y @rest]{ } a").WantAnyErr(), NewTest("[]{ } &k=v").WantAnyErr(), }) } elvish-0.11+ds1/eval/compilation_error.go000066400000000000000000000012511323000013700204010ustar00rootroot00000000000000package eval import ( "bytes" "fmt" "github.com/elves/elvish/util" ) // CompilationError represents a compilation error and can pretty print it. type CompilationError struct { Message string Context util.SourceRange } func (ce *CompilationError) Error() string { return fmt.Sprintf("compilation error: %d-%d in %s: %s", ce.Context.Begin, ce.Context.End, ce.Context.Name, ce.Message) } // Pprint pretty-prints a compilation error. func (ce *CompilationError) Pprint(indent string) string { var buf bytes.Buffer fmt.Fprintf(&buf, "Compilation error: \033[31;1m%s\033[m\n", ce.Message) buf.WriteString(ce.Context.PprintCompact(indent + " ")) return buf.String() } elvish-0.11+ds1/eval/compile_lvalue.go000066400000000000000000000116131323000013700176550ustar00rootroot00000000000000package eval import ( "errors" "strings" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/parse" ) // LValuesOp is an operation on an EvalCtx that produce Variable's. type LValuesOp struct { Func LValuesOpFunc Begin, End int } // LValuesOpFunc is the body of an LValuesOp. type LValuesOpFunc func(*Frame) []vartypes.Variable // Exec executes an LValuesOp, producing Variable's. func (op LValuesOp) Exec(ec *Frame) []vartypes.Variable { // Empty value is considered to generate no lvalues. if op.Func == nil { return []vartypes.Variable{} } ec.begin, ec.end = op.Begin, op.End return op.Func(ec) } // lvaluesOp compiles lvalues, returning the fixed part and, optionally a rest // part. // // In the AST an lvalue is either an Indexing node where the head is a string // literal, or a braced list of such Indexing nodes. The last Indexing node may // be prefixed by @, in which case they become the rest part. For instance, in // {a[x],b,@c[z]}, "a[x],b" is the fixed part and "c[z]" is the rest part. func (cp *compiler) lvaluesOp(n *parse.Indexing) (LValuesOp, LValuesOp) { if n.Head.Type == parse.Braced { // Braced list of variable specs, possibly with indicies. if len(n.Indicies) > 0 { cp.errorf("may not have indicies") } return cp.lvaluesMulti(n.Head.Braced) } rest, opFunc := cp.lvalueBase(n, "must be an lvalue or a braced list of those") op := LValuesOp{opFunc, n.Begin(), n.End()} if rest { return LValuesOp{}, op } return op, LValuesOp{} } func (cp *compiler) lvaluesMulti(nodes []*parse.Compound) (LValuesOp, LValuesOp) { opFuncs := make([]LValuesOpFunc, len(nodes)) var restNode *parse.Indexing var restOpFunc LValuesOpFunc // Compile each spec inside the brace. fixedEnd := 0 for i, cn := range nodes { if len(cn.Indexings) != 1 { cp.errorpf(cn.Begin(), cn.End(), "must be an lvalue") } var rest bool rest, opFuncs[i] = cp.lvalueBase(cn.Indexings[0], "must be an lvalue ") // Only the last one may a rest part. if rest { if i == len(nodes)-1 { restNode = cn.Indexings[0] restOpFunc = opFuncs[i] } else { cp.errorpf(cn.Begin(), cn.End(), "only the last lvalue may have @") } } else { fixedEnd = cn.End() } } var restOp LValuesOp // If there is a rest part, make LValuesOp for it and remove it from opFuncs. if restOpFunc != nil { restOp = LValuesOp{restOpFunc, restNode.Begin(), restNode.End()} opFuncs = opFuncs[:len(opFuncs)-1] } var op LValuesOp // If there is still anything left in opFuncs, make LValuesOp for the fixed part. if len(opFuncs) > 0 { op = LValuesOp{func(ec *Frame) []vartypes.Variable { var variables []vartypes.Variable for _, opFunc := range opFuncs { variables = append(variables, opFunc(ec)...) } return variables }, nodes[0].Begin(), fixedEnd} } return op, restOp } func (cp *compiler) lvalueBase(n *parse.Indexing, msg string) (bool, LValuesOpFunc) { qname := cp.literal(n.Head, msg) explode, ns, name := ParseVariable(qname) if len(n.Indicies) == 0 { return explode, cp.lvalueVariable(ns, name) } return explode, cp.lvalueElement(ns, name, n) } func (cp *compiler) lvalueVariable(ns, name string) LValuesOpFunc { cp.registerVariableSet(ns, name) return func(ec *Frame) []vartypes.Variable { variable := ec.ResolveVar(ns, name) if variable == nil { if ns == "" || ns == "local" { // New variable. // XXX We depend on the fact that this variable will // immeidately be set. if strings.HasSuffix(name, FnSuffix) { variable = vartypes.NewValidatedPtr(nil, ShouldBeFn) } else if strings.HasSuffix(name, NsSuffix) { variable = vartypes.NewValidatedPtr(nil, ShouldBeNs) } else { variable = vartypes.NewPtr(nil) } ec.local[name] = variable } else { throwf("new variables can only be created in local scope") } } return []vartypes.Variable{variable} } } func (cp *compiler) lvalueElement(ns, name string, n *parse.Indexing) LValuesOpFunc { cp.registerVariableGet(ns, name) begin, end := n.Begin(), n.End() ends := make([]int, len(n.Indicies)+1) ends[0] = n.Head.End() for i, idx := range n.Indicies { ends[i+1] = idx.End() } indexOps := cp.arrayOps(n.Indicies) return func(ec *Frame) []vartypes.Variable { variable := ec.ResolveVar(ns, name) if variable == nil { throwf("variable $%s:%s does not exist, compiler bug", ns, name) } indicies := make([]types.Value, len(indexOps)) for i, op := range indexOps { values := op.Exec(ec) // TODO: Implement multi-indexing. if len(values) != 1 { throw(errors.New("multi indexing not implemented")) } indicies[i] = values[0] } elemVar, err := vartypes.MakeElement(variable, indicies) if err != nil { level := vartypes.GetElementErrorLevel(err) if level < 0 { ec.errorpf(begin, end, "%s", err) } else { ec.errorpf(begin, ends[level], "%s", err) } } return []vartypes.Variable{elemVar} } } elvish-0.11+ds1/eval/compile_lvalue_test.go000066400000000000000000000005141323000013700207120ustar00rootroot00000000000000package eval import "testing" func TestAssignment(t *testing.T) { runTests(t, []Test{ NewTest("x = a; put $x").WantOutStrings("a"), NewTest("x = [a]; x[0] = b; put $x[0]").WantOutStrings("b"), NewTest("x = a; { x = b }; put $x").WantOutStrings("b"), NewTest("x = [a]; { x[0] = b }; put $x[0]").WantOutStrings("b"), }) } elvish-0.11+ds1/eval/compile_op.go000066400000000000000000000266431323000013700170140ustar00rootroot00000000000000package eval import ( "errors" "os" "sync" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/parse" ) // Op is an operation on an EvalCtx. type Op struct { Func OpFunc Begin, End int } // OpFunc is the body of an Op. type OpFunc func(*Frame) // Exec executes an Op. func (op Op) Exec(ec *Frame) { ec.begin, ec.end = op.Begin, op.End op.Func(ec) } func (cp *compiler) chunk(n *parse.Chunk) OpFunc { ops := cp.pipelineOps(n.Pipelines) return func(ec *Frame) { for _, op := range ops { op.Exec(ec) } // Check for interrupts after the chunk. // We also check for interrupts before each pipeline, so there is no // need to check it before the chunk or after each pipeline. ec.CheckInterrupts() } } const pipelineChanBufferSize = 32 func (cp *compiler) pipeline(n *parse.Pipeline) OpFunc { ops := cp.formOps(n.Forms) return func(ec *Frame) { ec.CheckInterrupts() bg := n.Background if bg { ec = ec.fork("background job " + n.SourceText()) ec.intCh = nil ec.background = true if ec.Editor != nil { // TODO: Redirect output in interactive mode so that the line // editor does not get messed up. } } nforms := len(ops) var wg sync.WaitGroup wg.Add(nforms) errors := make([]*Exception, nforms) var nextIn *Port // For each form, create a dedicated evalCtx and run asynchronously for i, op := range ops { hasChanInput := i > 0 newEc := ec.fork("[form op]") if i > 0 { newEc.ports[0] = nextIn } if i < nforms-1 { // Each internal port pair consists of a (byte) pipe pair and a // channel. // os.Pipe sets O_CLOEXEC, which is what we want. reader, writer, e := os.Pipe() if e != nil { throwf("failed to create pipe: %s", e) } ch := make(chan types.Value, pipelineChanBufferSize) newEc.ports[1] = &Port{ File: writer, Chan: ch, CloseFile: true, CloseChan: true} nextIn = &Port{ File: reader, Chan: ch, CloseFile: true, CloseChan: false} } thisOp := op thisError := &errors[i] go func() { err := newEc.PEval(thisOp) // Logger.Printf("closing ports of %s", newEc.context) ClosePorts(newEc.ports) if err != nil { *thisError = err.(*Exception) } wg.Done() if hasChanInput { // If the command has channel input, drain it. This // mitigates the effect of erroneous pipelines like // "range 100 | cat"; without draining the pipeline will // lock up. for range newEc.ports[0].Chan { } } }() } if bg { // Background job, wait for form termination asynchronously. go func() { wg.Wait() msg := "job " + n.SourceText() + " finished" err := ComposeExceptionsFromPipeline(errors) if err != nil { msg += ", errors = " + err.Error() } if ec.Editor != nil { m := ec.Editor.ActiveMutex() m.Lock() defer m.Unlock() if ec.Editor.Active() { ec.Editor.Notify("%s", msg) } else { ec.ports[2].File.WriteString(msg + "\n") } } else { ec.ports[2].File.WriteString(msg + "\n") } }() } else { wg.Wait() maybeThrow(ComposeExceptionsFromPipeline(errors)) } } } func (cp *compiler) form(n *parse.Form) OpFunc { var saveVarsOps []LValuesOp var assignmentOps []Op if len(n.Assignments) > 0 { assignmentOps = cp.assignmentOps(n.Assignments) if n.Head == nil && n.Vars == nil { // Permanent assignment. return func(ec *Frame) { for _, op := range assignmentOps { op.Exec(ec) } } } for _, a := range n.Assignments { v, r := cp.lvaluesOp(a.Left) saveVarsOps = append(saveVarsOps, v, r) } logger.Println("temporary assignment of", len(n.Assignments), "pairs") } // Depending on the type of the form, exactly one of the three below will be // set. var ( specialOpFunc OpFunc headOp ValuesOp spaceyAssignOp Op ) // Forward declaration; needed when compiling assignment forms. var argOps []ValuesOp if n.Head != nil { headStr, ok := oneString(n.Head) if ok { compileForm, ok := builtinSpecials[headStr] if ok { // Special form. specialOpFunc = compileForm(cp, n) } else { var headOpFunc ValuesOpFunc explode, ns, name := ParseVariable(headStr) if !explode && cp.registerVariableGet(ns, name+FnSuffix) { // $head~ resolves. headOpFunc = variable(headStr + FnSuffix) } else { // Fall back to $e:head~. headOpFunc = func(f *Frame) []types.Value { return []types.Value{ExternalCmd{headStr}} } } headOp = ValuesOp{headOpFunc, n.Head.Begin(), n.Head.End()} } } else { // Head exists and is not a literal string. Evaluate as a normal // expression. headOp = cp.compoundOp(n.Head) } } else { // Assignment form. varsOp, restOp := cp.lvaluesMulti(n.Vars) argsOp := ValuesOp{ func(ec *Frame) []types.Value { var vs []types.Value for _, op := range argOps { vs = append(vs, op.Exec(ec)...) } return vs }, -1, -1, } if len(argOps) > 0 { argsOp.Begin = argOps[0].Begin argsOp.End = argOps[len(argOps)-1].End } spaceyAssignOp = Op{ makeAssignmentOpFunc(varsOp, restOp, argsOp), n.Begin(), argsOp.End, } } argOps = cp.compoundOps(n.Args) optsOp := cp.mapPairs(n.Opts) redirOps := cp.redirOps(n.Redirs) // TODO: n.ErrorRedir begin, end := n.Begin(), n.End() // ec here is always a subevaler created in compiler.pipeline, so it can // be safely modified. return func(ec *Frame) { // Temporary assignment. if len(saveVarsOps) > 0 { // There is a temporary assignment. // Save variables. var saveVars []vartypes.Variable var saveVals []types.Value for _, op := range saveVarsOps { saveVars = append(saveVars, op.Exec(ec)...) } for i, v := range saveVars { // XXX(xiaq): If the variable to save is a elemVariable, save // the outermost variable instead. if u := vartypes.GetHeadOfElement(v); u != nil { v = u saveVars[i] = v } val := v.Get() saveVals = append(saveVals, val) logger.Printf("saved %s = %s", v, val) } // Do assignment. for _, op := range assignmentOps { op.Exec(ec) } // Defer variable restoration. Will be executed even if an error // occurs when evaling other part of the form. defer func() { for i, v := range saveVars { val := saveVals[i] if val == nil { // XXX Old value is nonexistent. We should delete the // variable. However, since the compiler now doesn't delete // it, we don't delete it in the evaler either. val = types.String("") } err := v.Set(val) maybeThrow(err) logger.Printf("restored %s = %s", v, val) } }() } // redirs for _, redirOp := range redirOps { redirOp.Exec(ec) } if specialOpFunc != nil { specialOpFunc(ec) } else { var headFn Callable var args []types.Value if headOp.Func != nil { // head headFn = ec.ExecAndUnwrap("head of command", headOp).One().Callable() // args for _, argOp := range argOps { args = append(args, argOp.Exec(ec)...) } } // opts // XXX This conversion should be avoided. opts := optsOp(ec)[0].(types.Map) convertedOpts := make(map[string]types.Value) opts.IteratePair(func(k, v types.Value) bool { if ks, ok := k.(types.String); ok { convertedOpts[string(ks)] = v } else { throwf("Option key must be string, got %s", k.Kind()) } return true }) ec.begin, ec.end = begin, end if headFn != nil { headFn.Call(ec, args, convertedOpts) } else { spaceyAssignOp.Exec(ec) } } } } func allTrue(vs []types.Value) bool { for _, v := range vs { if !types.ToBool(v) { return false } } return true } func (cp *compiler) assignment(n *parse.Assignment) OpFunc { variablesOp, restOp := cp.lvaluesOp(n.Left) valuesOp := cp.compoundOp(n.Right) return makeAssignmentOpFunc(variablesOp, restOp, valuesOp) } // ErrMoreThanOneRest is thrown when the LHS of an assignment contains more than // one rest variables. var ErrMoreThanOneRest = errors.New("more than one @ lvalue") func makeAssignmentOpFunc(variablesOp, restOp LValuesOp, valuesOp ValuesOp) OpFunc { return func(ec *Frame) { variables := variablesOp.Exec(ec) rest := restOp.Exec(ec) // If any LHS ends up being nil, assign an empty string to all of them. // // This is to fix #176, which only happens in the top level of REPL; in // other cases, a failure in the evaluation of the RHS causes this // level to fail, making the variables unaccessible. // // XXX(xiaq): Should think about how to get rid of this. defer fixNilVariables(variables) defer fixNilVariables(rest) values := valuesOp.Exec(ec) if len(rest) > 1 { throw(ErrMoreThanOneRest) } if len(rest) == 1 { if len(variables) > len(values) { throw(ErrArityMismatch) } } else { if len(variables) != len(values) { throw(ErrArityMismatch) } } for i, variable := range variables { err := variable.Set(values[i]) maybeThrow(err) } if len(rest) == 1 { err := rest[0].Set(types.MakeList(values[len(variables):]...)) maybeThrow(err) } } } func fixNilVariables(vs []vartypes.Variable) { for _, v := range vs { if vartypes.IsBlackhole(v) { continue } if v.Get() == nil { err := v.Set(types.String("")) maybeThrow(err) } } } func (cp *compiler) literal(n *parse.Primary, msg string) string { switch n.Type { case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted: return n.Value default: cp.compiling(n) cp.errorf(msg) return "" // not reached } } const defaultFileRedirPerm = 0644 // redir compiles a Redir into a op. func (cp *compiler) redir(n *parse.Redir) OpFunc { var dstOp ValuesOp if n.Left != nil { dstOp = cp.compoundOp(n.Left) } srcOp := cp.compoundOp(n.Right) sourceIsFd := n.RightIsFd mode := n.Mode flag := makeFlag(mode) if flag == -1 { // TODO: Record and get redirection sign position cp.errorf("bad redirection sign") } return func(ec *Frame) { var dst int if dstOp.Func == nil { // use default dst fd switch mode { case parse.Read: dst = 0 case parse.Write, parse.ReadWrite, parse.Append: dst = 1 default: // XXX should report parser bug panic("bad RedirMode; parser bug") } } else { // dst must be a valid fd dst = ec.ExecAndUnwrap("Fd", dstOp).One().NonNegativeInt() } ec.growPorts(dst + 1) // Logger.Printf("closing old port %d of %s", dst, ec.context) ec.ports[dst].Close() srcUnwrap := ec.ExecAndUnwrap("redirection source", srcOp).One() if sourceIsFd { src := srcUnwrap.FdOrClose() if src == -1 { // close ec.ports[dst] = &Port{} } else { ec.ports[dst] = ec.ports[src].Fork() } } else { switch src := srcUnwrap.Any().(type) { case types.String: f, err := os.OpenFile(string(src), flag, defaultFileRedirPerm) if err != nil { throwf("failed to open file %s: %s", src.Repr(types.NoPretty), err) } ec.ports[dst] = &Port{ File: f, Chan: BlackholeChan, CloseFile: true, } case types.File: ec.ports[dst] = &Port{ File: src.Inner, Chan: BlackholeChan, CloseFile: false, } case types.Pipe: var f *os.File switch mode { case parse.Read: f = src.ReadEnd case parse.Write: f = src.WriteEnd default: cp.errorf("can only use < or > with pipes") } ec.ports[dst] = &Port{ File: f, Chan: BlackholeChan, CloseFile: false, } default: srcUnwrap.error("string or file", "%s", src.Kind()) } } } } elvish-0.11+ds1/eval/compile_op_test.go000066400000000000000000000052161323000013700200440ustar00rootroot00000000000000package eval import "testing" var opTests = []Test{ // Chunks // ------ // Empty chunk {"", wantNothing}, // Outputs of pipelines in a chunk are concatenated {"put x; put y; put z", want{out: strs("x", "y", "z")}}, // A failed pipeline cause the whole chunk to fail {"put a; e:false; put b", want{out: strs("a"), err: errAny}}, // Pipelines // --------- // Pure byte pipeline {`echo "Albert\nAllan\nAlbraham\nBerlin" | sed s/l/1/g | grep e`, want{bytesOut: []byte("A1bert\nBer1in\n")}}, // Pure channel pipeline {`put 233 42 19 | each [x]{+ $x 10}`, want{out: strs("243", "52", "29")}}, // Pipeline draining. {`range 100 | put x`, want{out: strs("x")}}, // TODO: Add a useful hybrid pipeline sample // Assignments // ----------- // List element assignment {"li=[foo bar]; li[0]=233; put $@li", want{out: strs("233", "bar")}}, // Map element assignment {"di=[&k=v]; di[k]=lorem; di[k2]=ipsum; put $di[k] $di[k2]", want{out: strs("lorem", "ipsum")}}, {"d=[&a=[&b=v]]; put $d[a][b]; d[a][b]=u; put $d[a][b]", want{out: strs("v", "u")}}, // Multi-assignments. {"{a,b}=(put a b); put $a $b", want{out: strs("a", "b")}}, {"@a=(put a b); put $@a", want{out: strs("a", "b")}}, {"{a,@b}=(put a b c); put $@b", want{out: strs("b", "c")}}, //{"di=[&]; di[a b]=(put a b); put $di[a] $di[b]", want{out: strs("a", "b")}}, // Temporary assignment. {"a=alice b=bob; {a,@b}=(put amy ben) put $a $@b; put $a $b", want{out: strs("amy", "ben", "alice", "bob")}}, // Temporary assignment of list element. {"l = [a]; l[0]=x put $l[0]; put $l[0]", want{out: strs("x", "a")}}, // Temporary assignment of map element. {"m = [&k=v]; m[k]=v2 put $m[k]; put $m[k]", want{out: strs("v2", "v")}}, // Temporary assignment before special form. {"li=[foo bar] for x $li { put $x }", want{out: strs("foo", "bar")}}, // Spacey assignment. {"a @b = 2 3 foo; put $a $b[1]", want{out: strs("2", "foo")}}, // Spacey assignment with temporary assignment {"x = 1; x=2 y = (+ 1 $x); put $x $y", want{out: strs("1", "3")}}, // Redirections // ------------ {"f=(mktemp elvXXXXXX); echo 233 > $f; cat < $f; rm $f", want{bytesOut: []byte("233\n")}}, // Redirections from special form. {`f = (mktemp elvXXXXXX); for x [lorem ipsum] { echo $x } > $f cat $f rm $f`, want{bytesOut: []byte("lorem\nipsum\n")}}, // Redirections from File object. {`fname=(mktemp elvXXXXXX); echo haha > $fname; f=(fopen $fname); cat <$f; fclose $f; rm $fname`, want{bytesOut: []byte("haha\n")}}, // Redirections from Pipe object. {`p=(pipe); echo haha > $p; pwclose $p; cat < $p; prclose $p`, want{bytesOut: []byte("haha\n")}}, } func TestOp(t *testing.T) { runTests(t, opTests) } elvish-0.11+ds1/eval/compile_value.go000066400000000000000000000311351323000013700175020ustar00rootroot00000000000000package eval import ( "bufio" "errors" "fmt" "io" "os" "path" "strings" "sync" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/glob" "github.com/elves/elvish/parse" "github.com/xiaq/persistent/hashmap" ) var outputCaptureBufferSize = 16 // ValuesOp is an operation on an EvalCtx that produce Value's. type ValuesOp struct { Func ValuesOpFunc Begin, End int } // ValuesOpFunc is the body of ValuesOp. type ValuesOpFunc func(*Frame) []types.Value // Exec executes a ValuesOp and produces Value's. func (op ValuesOp) Exec(ec *Frame) []types.Value { ec.begin, ec.end = op.Begin, op.End return op.Func(ec) } func (cp *compiler) compound(n *parse.Compound) ValuesOpFunc { if len(n.Indexings) == 0 { return literalStr("") } tilde := false indexings := n.Indexings if n.Indexings[0].Head.Type == parse.Tilde { // A lone ~. if len(n.Indexings) == 1 { return func(ec *Frame) []types.Value { return []types.Value{types.String(mustGetHome(""))} } } tilde = true indexings = indexings[1:] } ops := cp.indexingOps(indexings) return func(ec *Frame) []types.Value { // Accumulator. vs := ops[0].Exec(ec) // Logger.Printf("concatenating %v with %d more", vs, len(ops)-1) for _, op := range ops[1:] { us := op.Exec(ec) vs = outerProduct(vs, us, cat) // Logger.Printf("with %v => %v", us, vs) } if tilde { newvs := make([]types.Value, len(vs)) for i, v := range vs { newvs[i] = doTilde(v) } vs = newvs } hasGlob := false for _, v := range vs { if _, ok := v.(GlobPattern); ok { hasGlob = true break } } if hasGlob { newvs := make([]types.Value, 0, len(vs)) for _, v := range vs { if gp, ok := v.(GlobPattern); ok { // Logger.Printf("globbing %v", gp) newvs = append(newvs, doGlob(gp, ec.Interrupts())...) } else { newvs = append(newvs, v) } } vs = newvs } return vs } } func cat(lhs, rhs types.Value) types.Value { switch lhs := lhs.(type) { case types.String: switch rhs := rhs.(type) { case types.String: return lhs + rhs case GlobPattern: segs := stringToSegments(string(lhs)) // We know rhs contains exactly one segment. segs = append(segs, rhs.Segments[0]) return GlobPattern{glob.Pattern{segs, ""}, rhs.Flags, rhs.Buts} } case GlobPattern: // NOTE Modifies lhs in place. switch rhs := rhs.(type) { case types.String: lhs.append(stringToSegments(string(rhs))...) return lhs case GlobPattern: // We know rhs contains exactly one segment. lhs.append(rhs.Segments[0]) lhs.Flags |= rhs.Flags lhs.Buts = append(lhs.Buts, rhs.Buts...) return lhs } } throw(fmt.Errorf("unsupported concat: %s and %s", lhs.Kind(), rhs.Kind())) panic("unreachable") } func outerProduct(vs []types.Value, us []types.Value, f func(types.Value, types.Value) types.Value) []types.Value { ws := make([]types.Value, len(vs)*len(us)) nu := len(us) for i, v := range vs { for j, u := range us { ws[i*nu+j] = f(v, u) } } return ws } // Errors thrown when globbing. var ( ErrBadGlobPattern = errors.New("bad GlobPattern; elvish bug") ErrCannotDetermineUsername = errors.New("cannot determine user name from glob pattern") ) func doTilde(v types.Value) types.Value { switch v := v.(type) { case types.String: s := string(v) i := strings.Index(s, "/") var uname, rest string if i == -1 { uname = s } else { uname = s[:i] rest = s[i+1:] } dir := mustGetHome(uname) return types.String(path.Join(dir, rest)) case GlobPattern: if len(v.Segments) == 0 { throw(ErrBadGlobPattern) } switch seg := v.Segments[0].(type) { case glob.Literal: s := seg.Data // Find / in the first segment to determine the username. i := strings.Index(s, "/") if i == -1 { throw(ErrCannotDetermineUsername) } uname := s[:i] dir := mustGetHome(uname) // Replace ~uname in first segment with the found path. v.Segments[0] = glob.Literal{dir + s[i:]} case glob.Slash: v.DirOverride = mustGetHome("") default: throw(ErrCannotDetermineUsername) } return v default: throw(fmt.Errorf("tilde doesn't work on value of type %s", v.Kind())) panic("unreachable") } } func (cp *compiler) array(n *parse.Array) ValuesOpFunc { return catValuesOps(cp.compoundOps(n.Compounds)) } func catValuesOps(ops []ValuesOp) ValuesOpFunc { return func(ec *Frame) []types.Value { // Use number of compound expressions as an estimation of the number // of values vs := make([]types.Value, 0, len(ops)) for _, op := range ops { us := op.Exec(ec) vs = append(vs, us...) } return vs } } func (cp *compiler) indexing(n *parse.Indexing) ValuesOpFunc { if len(n.Indicies) == 0 { return cp.primary(n.Head) } headOp := cp.primaryOp(n.Head) indexOps := cp.arrayOps(n.Indicies) return func(ec *Frame) []types.Value { vs := headOp.Exec(ec) for _, indexOp := range indexOps { indicies := indexOp.Exec(ec) newvs := make([]types.Value, 0, len(vs)*len(indicies)) for _, v := range vs { newvs = append(newvs, mustIndexer(v, ec).Index(indicies)...) } vs = newvs } return vs } } func literalValues(v ...types.Value) ValuesOpFunc { return func(e *Frame) []types.Value { return v } } func literalStr(text string) ValuesOpFunc { return literalValues(types.String(text)) } func variable(qname string) ValuesOpFunc { explode, ns, name := ParseVariable(qname) return func(ec *Frame) []types.Value { variable := ec.ResolveVar(ns, name) if variable == nil { throwf("variable $%s not found", qname) } value := variable.Get() if explode { iterator, ok := value.(types.Iterator) if !ok { // Use qname[1:] to skip the leading "@" throwf("variable $%s (kind %s) cannot be exploded", qname[1:], value.Kind()) } return types.CollectFromIterator(iterator) } return []types.Value{value} } } func (cp *compiler) primary(n *parse.Primary) ValuesOpFunc { switch n.Type { case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted: return literalStr(n.Value) case parse.Variable: qname := n.Value if !cp.registerVariableGetQname(qname) { cp.errorf("variable $%s not found", n.Value) } return variable(qname) case parse.Wildcard: seg, err := wildcardToSegment(n.SourceText()) if err != nil { cp.errorf("%s", err) } vs := []types.Value{ GlobPattern{glob.Pattern{[]glob.Segment{seg}, ""}, 0, nil}} return func(ec *Frame) []types.Value { return vs } case parse.Tilde: cp.errorf("compiler bug: Tilde not handled in .compound") return literalStr("~") case parse.ExceptionCapture: return cp.exceptionCapture(n.Chunk) case parse.OutputCapture: return cp.outputCapture(n) case parse.List: return cp.list(n) case parse.Lambda: return cp.lambda(n) case parse.Map: return cp.map_(n) case parse.Braced: return cp.braced(n) default: cp.errorf("bad PrimaryType; parser bug") return literalStr(n.SourceText()) } } func (cp *compiler) list(n *parse.Primary) ValuesOpFunc { // TODO(xiaq): Use Vector.Cons to build the list, instead of building a // slice and converting to Vector. op := catValuesOps(cp.compoundOps(n.Elements)) return func(ec *Frame) []types.Value { return []types.Value{types.MakeList(op(ec)...)} } } func (cp *compiler) exceptionCapture(n *parse.Chunk) ValuesOpFunc { op := cp.chunkOp(n) return func(ec *Frame) []types.Value { err := ec.PEval(op) if err == nil { return []types.Value{OK} } return []types.Value{err.(*Exception)} } } func (cp *compiler) outputCapture(n *parse.Primary) ValuesOpFunc { op := cp.chunkOp(n.Chunk) return func(ec *Frame) []types.Value { return captureOutput(ec, op) } } func captureOutput(ec *Frame, op Op) []types.Value { vs, err := pcaptureOutput(ec, op) maybeThrow(err) return vs } func pcaptureOutput(ec *Frame, op Op) ([]types.Value, error) { vs := []types.Value{} var m sync.Mutex valueCb := func(ch <-chan types.Value) { for v := range ch { m.Lock() vs = append(vs, v) m.Unlock() } } bytesCb := func(r *os.File) { buffered := bufio.NewReader(r) for { line, err := buffered.ReadString('\n') if line != "" { v := types.String(strings.TrimSuffix(line, "\n")) m.Lock() vs = append(vs, v) m.Unlock() } if err != nil { if err != io.EOF { logger.Println("error on reading:", err) } break } } } err := pcaptureOutputInner(ec, op, valueCb, bytesCb) return vs, err } func pcaptureOutputInner(ec *Frame, op Op, valuesCb func(<-chan types.Value), bytesCb func(*os.File)) error { newEc := ec.fork("[output capture]") ch := make(chan types.Value, outputCaptureBufferSize) pipeRead, pipeWrite, err := os.Pipe() if err != nil { return fmt.Errorf("failed to create pipe: %v", err) } newEc.ports[1] = &Port{ Chan: ch, CloseChan: true, File: pipeWrite, CloseFile: true, } bytesCollected := make(chan struct{}) chCollected := make(chan struct{}) go func() { valuesCb(ch) close(chCollected) }() go func() { bytesCb(pipeRead) pipeRead.Close() close(bytesCollected) }() err = newEc.PEval(op) ClosePorts(newEc.ports) <-bytesCollected <-chCollected return err } func (cp *compiler) lambda(n *parse.Primary) ValuesOpFunc { // Parse signature. var ( argNames []string restArgName string optNames []string optDefaultOps []ValuesOp ) if len(n.Elements) > 0 { // Argument list. argNames = make([]string, len(n.Elements)) for i, arg := range n.Elements { qname := mustString(cp, arg, "argument name must be literal string") explode, ns, name := ParseVariable(qname) if ns != "" { cp.errorpf(arg.Begin(), arg.End(), "argument name must be unqualified") } if name == "" { cp.errorpf(arg.Begin(), arg.End(), "argument name must not be empty") } if explode { if i != len(n.Elements)-1 { cp.errorpf(arg.Begin(), arg.End(), "only the last argument may have @") } restArgName = name argNames = argNames[:i] } else { argNames[i] = name } } } if len(n.MapPairs) > 0 { optNames = make([]string, len(n.MapPairs)) optDefaultOps = make([]ValuesOp, len(n.MapPairs)) for i, opt := range n.MapPairs { qname := mustString(cp, opt.Key, "option name must be literal string") _, ns, name := ParseVariable(qname) if ns != "" { cp.errorpf(opt.Key.Begin(), opt.Key.End(), "option name must be unqualified") } if name == "" { cp.errorpf(opt.Key.Begin(), opt.Key.End(), "option name must not be empty") } optNames[i] = name if opt.Value == nil { cp.errorpf(opt.End(), opt.End(), "option must have default value") } else { optDefaultOps[i] = cp.compoundOp(opt.Value) } } } thisScope := cp.pushScope() for _, argName := range argNames { thisScope.set(argName) } if restArgName != "" { thisScope.set(restArgName) } for _, optName := range optNames { thisScope.set(optName) } thisScope.set("opts") op := cp.chunkOp(n.Chunk) // XXX The fiddlings with cp.capture is error-prone. capture := cp.capture cp.capture = make(staticNs) cp.popScope() for name := range capture { cp.registerVariableGetQname(name) } srcMeta := cp.srcMeta return func(ec *Frame) []types.Value { evCapture := make(Ns) for name := range capture { evCapture[name] = ec.ResolveVar("", name) } optDefaults := make([]types.Value, len(optDefaultOps)) for i, op := range optDefaultOps { defaultValue := ec.ExecAndUnwrap("option default value", op).One().Any() optDefaults[i] = defaultValue } // XXX(xiaq): Capture uses. return []types.Value{&Closure{argNames, restArgName, optNames, optDefaults, op, evCapture, srcMeta}} } } func (cp *compiler) map_(n *parse.Primary) ValuesOpFunc { return cp.mapPairs(n.MapPairs) } func (cp *compiler) mapPairs(pairs []*parse.MapPair) ValuesOpFunc { npairs := len(pairs) keysOps := make([]ValuesOp, npairs) valuesOps := make([]ValuesOp, npairs) begins, ends := make([]int, npairs), make([]int, npairs) for i, pair := range pairs { keysOps[i] = cp.compoundOp(pair.Key) if pair.Value == nil { p := pair.End() valuesOps[i] = ValuesOp{literalValues(types.Bool(true)), p, p} } else { valuesOps[i] = cp.compoundOp(pairs[i].Value) } begins[i], ends[i] = pair.Begin(), pair.End() } return func(ec *Frame) []types.Value { m := hashmap.Empty for i := 0; i < npairs; i++ { keys := keysOps[i].Exec(ec) values := valuesOps[i].Exec(ec) if len(keys) != len(values) { ec.errorpf(begins[i], ends[i], "%d keys but %d values", len(keys), len(values)) } for j, key := range keys { m = m.Assoc(key, values[j]) } } return []types.Value{types.NewMap(m)} } } func (cp *compiler) braced(n *parse.Primary) ValuesOpFunc { ops := cp.compoundOps(n.Braced) // TODO: n.IsRange // isRange := n.IsRange return catValuesOps(ops) } elvish-0.11+ds1/eval/compile_value_test.go000066400000000000000000000057661323000013700205540ustar00rootroot00000000000000package eval import ( "testing" "github.com/elves/elvish/eval/types" ) var valueTests = []Test{ // Compounding // ----------- {"put {fi,elvi}sh{1.0,1.1}", want{out: strs("fish1.0", "fish1.1", "elvish1.0", "elvish1.1")}}, // List, Map and Indexing // ---------------------- {"echo [a b c] [&key=value] | each $put~", want{out: strs("[a b c] [&key=value]")}}, {"put [a b c][2]", want{out: strs("c")}}, {"put [&key=value][key]", want{out: strs("value")}}, // String Literals // --------------- {`put 'such \"''literal'`, want{out: strs(`such \"'literal`)}}, {`put "much \n\033[31;1m$cool\033[m"`, want{out: strs("much \n\033[31;1m$cool\033[m")}}, // Captures // --------- // Output capture {"put (put lorem ipsum)", want{out: strs("lorem", "ipsum")}}, {"put (print \"lorem\nipsum\")", want{out: strs("lorem", "ipsum")}}, // Exception capture {"bool ?(nop); bool ?(e:false)", want{out: bools(true, false)}}, // Variable Use // ------------ // Compounding {"x='SHELL'\nput 'WOW, SUCH '$x', MUCH COOL'\n", want{out: strs("WOW, SUCH SHELL, MUCH COOL")}}, // Splicing {"x=[elvish rules]; put $@x", want{out: strs("elvish", "rules")}}, // Wildcard; see testmain_test.go for FS setup // ------------------------------------------- {"put *", want{out: strs(fileListing...)}}, {"put a/b/nonexistent*", want{err: ErrWildcardNoMatch}}, {"put a/b/nonexistent*[nomatch-ok]", wantNothing}, // Character set and range {"put ?[set:ab]*", want{out: strs(getFilesWithPrefix("a", "b")...)}}, {"put ?[range:a-c]*", want{out: strs(getFilesWithPrefix("a", "b", "c")...)}}, {"put ?[range:a~c]*", want{out: strs(getFilesWithPrefix("a", "b")...)}}, {"put *[range:a-z]", want{out: strs("bar", "dir", "foo", "ipsum", "lorem")}}, // Exclusion {"put *[but:foo but:lorem]", want{out: strs(getFilesBut("foo", "lorem")...)}}, // Tilde // ----- {"h=$E:HOME; E:HOME=/foo; put ~ ~/src; E:HOME=$h", want{out: strs("/foo", "/foo/src")}}, // Closure // ------- {"[]{ }", wantNothing}, {"[x]{put $x} foo", want{out: strs("foo")}}, // Variable capture {"x=lorem; []{x=ipsum}; put $x", want{out: strs("ipsum")}}, {"x=lorem; []{ put $x; x=ipsum }; put $x", want{out: strs("lorem", "ipsum")}}, // Shadowing {"x=ipsum; []{ local:x=lorem; put $x }; put $x", want{out: strs("lorem", "ipsum")}}, // Shadowing by argument {"x=ipsum; [x]{ put $x; x=BAD } lorem; put $x", want{out: strs("lorem", "ipsum")}}, // Closure captures new local variables every time {`fn f []{ x=0; put []{x=(+ $x 1)} []{put $x} } {inc1,put1}=(f); $put1; $inc1; $put1 {inc2,put2}=(f); $put2; $inc2; $put2`, want{out: strs("0", "1", "0", "1")}}, // Rest argument. {"[x @xs]{ put $x $xs } a b c", want{out: []types.Value{types.String("a"), types.MakeList(types.String("b"), types.String("c"))}}}, // Options. {"[a &k=v]{ put $a $k } foo &k=bar", want{out: strs("foo", "bar")}}, // Option default value. {"[a &k=v]{ put $a $k } foo", want{out: strs("foo", "v")}}, } func TestValue(t *testing.T) { runTests(t, valueTests) } elvish-0.11+ds1/eval/compiler.go000066400000000000000000000066641323000013700165010ustar00rootroot00000000000000package eval //go:generate ./boilerplate.py import ( "fmt" "strconv" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" ) // compiler maintains the set of states needed when compiling a single source // file. type compiler struct { // Builtin namespace. builtin staticNs // Lexical namespaces. scopes []staticNs // Variables captured from outer scopes. capture staticNs // Position of what is being compiled. begin, end int // Information about the source. srcMeta *Source } func compile(b, g staticNs, n *parse.Chunk, src *Source) (op Op, err error) { cp := &compiler{b, []staticNs{g}, make(staticNs), 0, 0, src} defer util.Catch(&err) return cp.chunkOp(n), nil } func (cp *compiler) compiling(n parse.Node) { cp.begin, cp.end = n.Begin(), n.End() } func (cp *compiler) errorpf(begin, end int, format string, args ...interface{}) { throw(&CompilationError{fmt.Sprintf(format, args...), *util.NewSourceRange(cp.srcMeta.describePath(), cp.srcMeta.code, begin, end, nil)}) } func (cp *compiler) errorf(format string, args ...interface{}) { cp.errorpf(cp.begin, cp.end, format, args...) } func (cp *compiler) thisScope() staticNs { return cp.scopes[len(cp.scopes)-1] } func (cp *compiler) pushScope() staticNs { sc := make(staticNs) cp.scopes = append(cp.scopes, sc) return sc } func (cp *compiler) popScope() { cp.scopes[len(cp.scopes)-1] = make(staticNs) cp.scopes = cp.scopes[:len(cp.scopes)-1] } func (cp *compiler) registerVariableGetQname(qname string) bool { _, ns, name := ParseVariable(qname) return cp.registerVariableGet(ns, name) } func (cp *compiler) registerVariableGet(ns, name string) bool { switch ns { case "", "local", "up": // Handled below case "e", "E", "shared": return true default: return cp.registerModAccess(ns) } _, err := strconv.Atoi(name) isnum := err == nil // Find in local scope if ns == "" || ns == "local" { if cp.thisScope().has(name) || isnum { return true } } // Find in upper scopes if ns == "" || ns == "up" { for i := len(cp.scopes) - 2; i >= 0; i-- { if cp.scopes[i].has(name) || isnum { // Existing name: record capture and return. cp.capture.set(name) return true } } } // Find in builtin scope if ns == "" || ns == "builtin" { if cp.builtin.has(name) { return true } } return false } func (cp *compiler) registerVariableSetQname(qname string) bool { _, ns, name := ParseVariable(qname) return cp.registerVariableSet(ns, name) } func (cp *compiler) registerVariableSet(ns, name string) bool { switch ns { case "local": cp.thisScope().set(name) return true case "up": for i := len(cp.scopes) - 2; i >= 0; i-- { if cp.scopes[i].has(name) { // Existing name: record capture and return. cp.capture.set(name) return true } } return false case "builtin": cp.errorf("cannot set builtin variable") return false case "": if cp.thisScope().has(name) { // A name on current scope. Do nothing. return true } // Walk up the upper scopes for i := len(cp.scopes) - 2; i >= 0; i-- { if cp.scopes[i].has(name) { // Existing name. Do nothing cp.capture.set(name) return true } } // New name. Register on this scope! cp.thisScope().set(name) return true case "e", "E", "shared": // Special namespaces, do nothing return true default: return cp.registerModAccess(ns) } } func (cp *compiler) registerModAccess(name string) bool { return cp.registerVariableGet("", name+NsSuffix) } elvish-0.11+ds1/eval/conversion.go000066400000000000000000000040421323000013700170400ustar00rootroot00000000000000package eval import ( "fmt" "reflect" "strconv" "unicode/utf8" "github.com/elves/elvish/eval/types" ) // Conversion between Go value and Value. func toFloat(arg types.Value) (float64, error) { if _, ok := arg.(types.String); !ok { return 0, fmt.Errorf("must be string") } s := string(arg.(types.String)) num, err := strconv.ParseFloat(s, 64) if err != nil { num, err2 := strconv.ParseInt(s, 0, 64) if err2 != nil { return 0, err } return float64(num), nil } return num, nil } func floatToString(f float64) types.String { return types.String(strconv.FormatFloat(f, 'g', -1, 64)) } func toInt(arg types.Value) (int, error) { arg, ok := arg.(types.String) if !ok { return 0, fmt.Errorf("must be string") } num, err := strconv.ParseInt(string(arg.(types.String)), 0, 0) if err != nil { return 0, err } return int(num), nil } func toRune(arg types.Value) (rune, error) { ss, ok := arg.(types.String) if !ok { return -1, fmt.Errorf("must be string") } s := string(ss) r, size := utf8.DecodeRuneInString(s) if r == utf8.RuneError { return -1, fmt.Errorf("string is not valid UTF-8") } if size != len(s) { return -1, fmt.Errorf("string has multiple runes") } return r, nil } // scanValueToGo converts Value to Go data, depending on the type of the // destination. func scanValueToGo(src types.Value, dstPtr interface{}) { switch dstPtr := dstPtr.(type) { case *string: s, ok := src.(types.String) if !ok { throwf("cannot convert %T to string", src) } *dstPtr = string(s) case *int: i, err := toInt(src) maybeThrow(err) *dstPtr = i case *float64: f, err := toFloat(src) maybeThrow(err) *dstPtr = f default: ptr := reflect.ValueOf(dstPtr) if ptr.Kind() != reflect.Ptr { throwf("internal bug: %T to ScanArgs, need pointer", dstPtr) } dstReflect := reflect.Indirect(ptr) if reflect.TypeOf(src).ConvertibleTo(dstReflect.Type()) { dstReflect.Set(reflect.ValueOf(src).Convert(dstReflect.Type())) } else { throwf("need %s argument, got %s", dstReflect.Type().Name(), src.Kind()) } } } elvish-0.11+ds1/eval/daemon/000077500000000000000000000000001323000013700155675ustar00rootroot00000000000000elvish-0.11+ds1/eval/daemon/daemon.go000066400000000000000000000023771323000013700173720ustar00rootroot00000000000000// Package daemon implements the builtin daemon: module. package daemon import ( "errors" "strconv" "github.com/elves/elvish/daemon" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" daemonp "github.com/elves/elvish/program/daemon" "github.com/elves/elvish/util" ) // errDontKnowHowToSpawnDaemon is thrown by daemon:spawn when the Evaler's // DaemonSpawner field is nil. var errDontKnowHowToSpawnDaemon = errors.New("don't know how to spawn daemon") // Ns makes the daemon: namespace. func Ns(daemon *daemon.Client, spawner *daemonp.Daemon) eval.Ns { // Obtain process ID daemonPid := func() types.Value { pid, err := daemon.Pid() if err != nil { util.Throw(err) } return types.String(strconv.Itoa(pid)) } daemonSpawn := func(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { eval.TakeNoArg(args) eval.TakeNoOpt(opts) if spawner == nil { util.Throw(errDontKnowHowToSpawnDaemon) } err := spawner.Spawn() if err != nil { util.Throw(err) } } return eval.Ns{ "pid": vartypes.NewRoCallback(daemonPid), "sock": vartypes.NewRo(types.String(daemon.SockPath())), "spawn" + eval.FnSuffix: vartypes.NewRo(&eval.BuiltinFn{"daemon:spawn", daemonSpawn}), } } elvish-0.11+ds1/eval/daemon/daemon_test.go000066400000000000000000000001151323000013700204150ustar00rootroot00000000000000package daemon import "testing" func TestDaemon(t *testing.T) { // TODO } elvish-0.11+ds1/eval/editor.go000066400000000000000000000004131323000013700161370ustar00rootroot00000000000000package eval import "sync" // Editor is the interface that the line editor has to satisfy. It is needed so // that this package does not depend on the edit package. type Editor interface { Active() bool ActiveMutex() *sync.Mutex Notify(string, ...interface{}) } elvish-0.11+ds1/eval/env_list.go000066400000000000000000000041161323000013700165000ustar00rootroot00000000000000package eval import ( "errors" "os" "strings" "sync" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/xiaq/persistent/vector" ) var ( pathListSeparator = string(os.PathListSeparator) forbiddenInPath = pathListSeparator + "\x00" ) // Errors var ( ErrCanOnlyAssignList = errors.New("can only assign compatible values") ErrPathMustBeString = errors.New("path must be string") ErrPathCannotContainColonZero = errors.New(`path cannot contain colon or \0`) ) // EnvList is a variable whose value is constructed from an environment variable // by splitting at pathListSeparator. Changes to it are also propagated to the // corresponding environment variable. Its elements cannot contain // pathListSeparator or \0; attempting to put any in its elements will result in // an error. type EnvList struct { sync.RWMutex envName string cacheFor string cacheValue types.Value } var ( _ vartypes.Variable = (*EnvList)(nil) ) // Get returns a Value for an EnvPathList. func (envli *EnvList) Get() types.Value { envli.Lock() defer envli.Unlock() value := os.Getenv(envli.envName) if value == envli.cacheFor { return envli.cacheValue } envli.cacheFor = value v := vector.Empty for _, path := range strings.Split(value, pathListSeparator) { v = v.Cons(types.String(path)) } envli.cacheValue = types.NewList(v) return envli.cacheValue } // Set sets an EnvPathList. The underlying environment variable is set. func (envli *EnvList) Set(v types.Value) error { iterator, ok := v.(types.Iterator) if !ok { return ErrCanOnlyAssignList } var paths []string var err error iterator.Iterate(func(v types.Value) bool { s, ok := v.(types.String) if !ok { err = ErrPathMustBeString return false } path := string(s) if strings.ContainsAny(path, forbiddenInPath) { err = ErrPathCannotContainColonZero return false } paths = append(paths, string(s)) return true }) if err != nil { return err } envli.Lock() defer envli.Unlock() os.Setenv(envli.envName, strings.Join(paths, pathListSeparator)) return nil } elvish-0.11+ds1/eval/eval.go000066400000000000000000000145271323000013700156130ustar00rootroot00000000000000// Package eval handles evaluation of parsed Elvish code and provides runtime // facilities. package eval import ( "fmt" "io/ioutil" "os" "os/signal" "strings" "syscall" "unicode/utf8" "github.com/elves/elvish/daemon" "github.com/elves/elvish/eval/bundled" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" "github.com/elves/elvish/parse" "github.com/elves/elvish/sys" "github.com/elves/elvish/util" "github.com/xiaq/persistent/vector" ) var logger = util.GetLogger("[eval] ") const ( // FnSuffix is the suffix for the variable names of functions. Defining a // function "foo" is equivalent to setting a variable named "foo~", and vice // versa. FnSuffix = "~" // NsSuffix is the suffix for the variable names of namespaces. Defining a // namespace foo is equivalent to setting a variable named "foo:", and vice // versa. NsSuffix = ":" ) const ( defaultValueOutIndicator = "▶ " initIndent = types.NoPretty ) // Evaler is used to evaluate elvish sources. It maintains runtime context // shared among all evalCtx instances. type Evaler struct { evalerScopes evalerPorts DaemonClient *daemon.Client modules map[string]Ns // bundled modules bundled map[string]string Editor Editor libDir string intCh chan struct{} } type evalerScopes struct { Global Ns Builtin Ns } // NewEvaler creates a new Evaler. func NewEvaler() *Evaler { builtin := makeBuiltinNs() ev := &Evaler{ evalerScopes: evalerScopes{ Global: make(Ns), Builtin: builtin, }, modules: map[string]Ns{ "builtin": builtin, }, bundled: bundled.Get(), Editor: nil, intCh: nil, } valueOutIndicator := defaultValueOutIndicator ev.evalerPorts = newEvalerPorts(os.Stdin, os.Stdout, os.Stderr, &valueOutIndicator) builtin["value-out-indicator"] = vartypes.NewString(&valueOutIndicator) return ev } // Close releases resources allocated when creating this Evaler. func (ev *Evaler) Close() { ev.evalerPorts.close() } // InstallDaemonClient installs a daemon client to the Evaler. func (ev *Evaler) InstallDaemonClient(client *daemon.Client) { ev.DaemonClient = client // XXX This is really brittle ev.Builtin["pwd"] = PwdVariable{client} } // InstallModule installs a module to the Evaler so that it can be used with // "use $name" from script. func (ev *Evaler) InstallModule(name string, mod Ns) { ev.modules[name] = mod } // InstallBundled installs a bundled module to the Evaler. func (ev *Evaler) InstallBundled(name, src string) { ev.bundled[name] = src } // SetArgs sets the $args builtin variable. func (ev *Evaler) SetArgs(args []string) { v := vector.Empty for _, arg := range args { v = v.Cons(types.String(arg)) } ev.Builtin["args"] = vartypes.NewRo(types.NewList(v)) } // SetLibDir sets the library directory, in which external modules are to be // found. func (ev *Evaler) SetLibDir(libDir string) { ev.libDir = libDir } func searchPaths() []string { return strings.Split(os.Getenv("PATH"), ":") } // growPorts makes the size of ec.ports at least n, adding nil's if necessary. func (ec *Frame) growPorts(n int) { if len(ec.ports) >= n { return } ports := ec.ports ec.ports = make([]*Port, n) copy(ec.ports, ports) } // eval evaluates a chunk node n. The supplied name and text are used in // diagnostic messages. func (ev *Evaler) eval(op Op, ports []*Port, src *Source) error { ec := NewTopFrame(ev, src, ports) return ec.PEval(op) } // Eval sets up the Evaler with standard ports and evaluates an Op. The supplied // name and text are used in diagnostic messages. func (ev *Evaler) Eval(op Op, src *Source) error { return ev.EvalWithPorts(ev.ports[:], op, src) } // EvalWithPorts sets up the Evaler with the given ports and evaluates an Op. // The supplied name and text are used in diagnostic messages. func (ev *Evaler) EvalWithPorts(ports []*Port, op Op, src *Source) error { // Ignore TTOU. // // When a subprocess in its own process group puts itself in the foreground, // Elvish will be put in the background. When the code finishes execution, // Elvish will attempt to move itself back to the foreground by calling // tcsetpgrp. However, whenever a background process calls tcsetpgrp (or // otherwise attempts to modify the terminal configuration), TTOU will be // sent, whose default handler is to stop the process. Or, if the process // lives in an orphaned process group (which is often the case for Elvish), // the call will outright fail. Therefore, for Elvish to be able to move // itself back to the foreground later, we need to ignore TTOU now. ignoreTTOU() defer unignoreTTOU() stopSigGoroutine := make(chan struct{}) sigGoRoutineDone := make(chan struct{}) // Set up intCh. ev.intCh = make(chan struct{}) sigCh := make(chan os.Signal) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGQUIT) go func() { closedIntCh := false loop: for { select { case <-sigCh: if !closedIntCh { close(ev.intCh) closedIntCh = true } case <-stopSigGoroutine: break loop } } ev.intCh = nil signal.Stop(sigCh) close(sigGoRoutineDone) }() err := ev.eval(op, ports, src) close(stopSigGoroutine) <-sigGoRoutineDone // Put myself in foreground, in case some command has put me in background. // XXX Should probably use fd of /dev/tty instead of 0. if sys.IsATTY(os.Stdin) { err := putSelfInFg() if err != nil { fmt.Println("failed to put myself in foreground:", err) } } return err } // Compile compiles elvish code in the global scope. If the error is not nil, it // always has type CompilationError. func (ev *Evaler) Compile(n *parse.Chunk, src *Source) (Op, error) { return compile(ev.Builtin.static(), ev.Global.static(), n, src) } // SourceText evaluates a chunk of elvish source. func (ev *Evaler) SourceText(src *Source) error { n, err := parse.Parse(src.name, src.code) if err != nil { return err } op, err := ev.Compile(n, src) if err != nil { return err } return ev.Eval(op, src) } func readFileUTF8(fname string) (string, error) { bytes, err := ioutil.ReadFile(fname) if err != nil { return "", err } if !utf8.Valid(bytes) { return "", fmt.Errorf("%s: source is not valid UTF-8", fname) } return string(bytes), nil } // Source evaluates the content of a file. func (ev *Evaler) Source(name, path string) error { code, err := readFileUTF8(path) if err != nil { return err } return ev.SourceText(NewScriptSource(name, path, code)) } elvish-0.11+ds1/eval/eval_test.go000066400000000000000000000037721323000013700166520ustar00rootroot00000000000000package eval import ( "reflect" "strconv" "syscall" "testing" "github.com/elves/elvish/eval/types" ) func TestBuiltinPid(t *testing.T) { pid := strconv.Itoa(syscall.Getpid()) builtinPid := types.ToString(makeBuiltinNs()["pid"].Get()) if builtinPid != pid { t.Errorf(`ev.builtin["pid"] = %v, want %v`, builtinPid, pid) } } var miscEvalTests = []Test{ // Pseudo-namespaces local: and up: {"x=lorem; []{local:x=ipsum; put $up:x $local:x}", want{out: strs("lorem", "ipsum")}}, {"x=lorem; []{up:x=ipsum; put $x}; put $x", want{out: strs("ipsum", "ipsum")}}, // Pseudo-namespace E: {"E:FOO=lorem; put $E:FOO", want{out: strs("lorem")}}, {"del E:FOO; put $E:FOO", want{out: strs("")}}, } func TestMiscEval(t *testing.T) { runTests(t, miscEvalTests) } func TestMultipleEval(t *testing.T) { texts := []string{"x=hello", "put $x"} outs, _, err := evalAndCollect(t, NewEvaler(), texts, 1) wanted := strs("hello") if err != nil { t.Errorf("eval %s => %v, want nil", texts, err) } if !reflect.DeepEqual(outs, wanted) { t.Errorf("eval %s outputs %v, want %v", texts, outs, wanted) } } func BenchmarkOutputCaptureOverhead(b *testing.B) { op := Op{func(*Frame) {}, 0, 0} benchmarkOutputCapture(op, b.N) } func BenchmarkOutputCaptureValues(b *testing.B) { op := Op{func(ec *Frame) { ec.ports[1].Chan <- types.String("test") }, 0, 0} benchmarkOutputCapture(op, b.N) } func BenchmarkOutputCaptureBytes(b *testing.B) { bytesToWrite := []byte("test") op := Op{func(ec *Frame) { ec.ports[1].File.Write(bytesToWrite) }, 0, 0} benchmarkOutputCapture(op, b.N) } func BenchmarkOutputCaptureMixed(b *testing.B) { bytesToWrite := []byte("test") op := Op{func(ec *Frame) { ec.ports[1].Chan <- types.Bool(false) ec.ports[1].File.Write(bytesToWrite) }, 0, 0} benchmarkOutputCapture(op, b.N) } func benchmarkOutputCapture(op Op, n int) { ev := NewEvaler() defer ev.Close() ec := NewTopFrame(ev, NewInternalSource("[benchmark]"), []*Port{{}, {}, {}}) for i := 0; i < n; i++ { pcaptureOutput(ec, op) } } elvish-0.11+ds1/eval/evaler_ports.go000066400000000000000000000021221323000013700173550ustar00rootroot00000000000000package eval import ( "os" "sync" "github.com/elves/elvish/eval/types" ) const ( stdoutChanSize = 32 stderrChanSize = 32 ) type evalerPorts struct { ports [3]*Port relayeWait *sync.WaitGroup } func newEvalerPorts(stdin, stdout, stderr *os.File, prefix *string) evalerPorts { stdoutChan := make(chan types.Value, stdoutChanSize) stderrChan := make(chan types.Value, stderrChanSize) var relayerWait sync.WaitGroup relayerWait.Add(2) go relayChanToFile(stdoutChan, stdout, prefix, &relayerWait) go relayChanToFile(stderrChan, stderr, prefix, &relayerWait) return evalerPorts{ [3]*Port{ {File: stdin, Chan: ClosedChan}, {File: stdout, Chan: stdoutChan, CloseChan: true}, {File: stderr, Chan: stderrChan, CloseChan: true}, }, &relayerWait, } } func relayChanToFile(ch <-chan types.Value, file *os.File, prefix *string, w *sync.WaitGroup) { for v := range ch { file.WriteString(*prefix) file.WriteString(v.Repr(initIndent)) file.WriteString("\n") } w.Done() } func (ep *evalerPorts) close() { ep.ports[1].Close() ep.ports[2].Close() ep.relayeWait.Wait() } elvish-0.11+ds1/eval/evaler_ports_test.go000066400000000000000000000021651323000013700204230ustar00rootroot00000000000000package eval import ( "io" "io/ioutil" "os" "testing" "github.com/elves/elvish/eval/types" ) func TestEvalerPorts(t *testing.T) { stdoutReader, stdout := mustPipe() defer stdoutReader.Close() stderrReader, stderr := mustPipe() defer stderrReader.Close() prefix := "> " ep := newEvalerPorts(DevNull, stdout, stderr, &prefix) ep.ports[1].Chan <- types.String("x") ep.ports[1].Chan <- types.String("y") ep.ports[2].Chan <- types.String("bad") ep.ports[2].Chan <- types.String("err") ep.close() stdout.Close() stderr.Close() stdoutAll := mustReadAllString(stdoutReader) wantStdoutAll := "> x\n> y\n" if stdoutAll != wantStdoutAll { t.Errorf("stdout is %q, want %q", stdoutAll, wantStdoutAll) } stderrAll := mustReadAllString(stderrReader) wantStderrAll := "> bad\n> err\n" if stderrAll != wantStderrAll { t.Errorf("stderr is %q, want %q", stderrAll, wantStderrAll) } } func mustPipe() (*os.File, *os.File) { r, w, err := os.Pipe() if err != nil { panic(err) } return r, w } func mustReadAllString(r io.Reader) string { b, err := ioutil.ReadAll(r) if err != nil { panic(err) } return string(b) } elvish-0.11+ds1/eval/exception.go000066400000000000000000000125311323000013700166530ustar00rootroot00000000000000package eval import ( "bytes" "fmt" "strconv" "strings" "syscall" "unsafe" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" "github.com/xiaq/persistent/hash" ) // Exception represents an elvish exception. It is both a Value accessible to // elvishscript, and the type of error returned by public facing evaluation // methods like (*Evaler)PEval. type Exception struct { Cause error Traceback *util.SourceRange } // OK is a pointer to the zero value of Exception, representing the absence of // exception. var OK = &Exception{} func (exc *Exception) Error() string { return exc.Cause.Error() } func (exc *Exception) Pprint(indent string) string { buf := new(bytes.Buffer) var causeDescription string if pprinter, ok := exc.Cause.(util.Pprinter); ok { causeDescription = pprinter.Pprint(indent) } else { causeDescription = "\033[31;1m" + exc.Cause.Error() + "\033[m" } fmt.Fprintf(buf, "Exception: %s\n", causeDescription) if exc.Traceback.Next == nil { buf.WriteString(exc.Traceback.PprintCompact(indent)) } else { buf.WriteString(indent + "Traceback:") for tb := exc.Traceback; tb != nil; tb = tb.Next { buf.WriteString("\n" + indent + " ") buf.WriteString(tb.Pprint(indent + " ")) } } if pipeExcs, ok := exc.Cause.(PipelineError); ok { buf.WriteString("\n" + indent + "Caused by:") for _, e := range pipeExcs.Errors { if e == OK { continue } buf.WriteString("\n" + indent + " " + e.Pprint(indent+" ")) } } return buf.String() } func (exc *Exception) Kind() string { return "exception" } func (exc *Exception) Repr(indent int) string { if exc.Cause == nil { return "$ok" } if r, ok := exc.Cause.(types.Reprer); ok { return r.Repr(indent) } return "?(fail " + parse.Quote(exc.Cause.Error()) + ")" } // Equal compares by identity. func (exc *Exception) Equal(rhs interface{}) bool { return exc == rhs } func (exc *Exception) Hash() uint32 { return hash.Pointer(unsafe.Pointer(exc)) } func (exc *Exception) Bool() bool { return exc.Cause == nil } // PipelineError represents the errors of pipelines, in which multiple commands // may error. type PipelineError struct { Errors []*Exception } func (pe PipelineError) Repr(indent int) string { // TODO Make a more generalized ListReprBuilder and use it here. b := new(bytes.Buffer) b.WriteString("?(multi-error") elemIndent := indent + len("?(multi-error ") for _, e := range pe.Errors { if indent > 0 { b.WriteString("\n" + strings.Repeat(" ", elemIndent)) } else { b.WriteString(" ") } b.WriteString(e.Repr(elemIndent)) } b.WriteString(")") return b.String() } func (pe PipelineError) Error() string { b := new(bytes.Buffer) b.WriteString("(") for i, e := range pe.Errors { if i > 0 { b.WriteString(" | ") } if e == nil || e.Cause == nil { b.WriteString("") } else { b.WriteString(e.Error()) } } b.WriteString(")") return b.String() } // ComposeExceptionsFromPipeline takes a slice of Exception pointers and // composes a suitable error. If all elements of the slice are either nil or OK, // a nil is returned. If there is exactly non-nil non-OK Exception, it is // returned. Otherwise, a PipelineError built from the slice is returned, with // nil items turned into OK's for easier access from elvishscript. func ComposeExceptionsFromPipeline(excs []*Exception) error { newexcs := make([]*Exception, len(excs)) notOK, lastNotOK := 0, 0 for i, e := range excs { if e == nil { newexcs[i] = OK } else { newexcs[i] = e if e.Cause != nil { notOK++ lastNotOK = i } } } switch notOK { case 0: return nil case 1: return newexcs[lastNotOK] default: return PipelineError{newexcs} } } // Flow is a special type of error used for control flows. type Flow uint // Control flows. const ( Return Flow = iota Break Continue ) var flowNames = [...]string{ "return", "break", "continue", } func (f Flow) Repr(int) string { return "?(" + f.Error() + ")" } func (f Flow) Error() string { if f >= Flow(len(flowNames)) { return fmt.Sprintf("!(BAD FLOW: %v)", f) } return flowNames[f] } func (f Flow) Pprint(string) string { return "\033[33;1m" + f.Error() + "\033[m" } // ExternalCmdExit contains the exit status of external commands. If the // command was stopped rather than terminated, the Pid field contains the pid // of the process. type ExternalCmdExit struct { syscall.WaitStatus CmdName string Pid int } func NewExternalCmdExit(name string, ws syscall.WaitStatus, pid int) error { if ws.Exited() && ws.ExitStatus() == 0 { return nil } if !ws.Stopped() { pid = 0 } return ExternalCmdExit{ws, name, pid} } func (exit ExternalCmdExit) Error() string { ws := exit.WaitStatus quotedName := parse.Quote(exit.CmdName) switch { case ws.Exited(): return quotedName + " exited with " + strconv.Itoa(ws.ExitStatus()) case ws.Signaled(): causeDescription := quotedName + " killed by signal " + ws.Signal().String() if ws.CoreDump() { causeDescription += " (core dumped)" } return causeDescription case ws.Stopped(): causeDescription := quotedName + " stopped by signal " + fmt.Sprintf("%s (pid=%d)", ws.StopSignal(), exit.Pid) trap := ws.TrapCause() if trap != -1 { causeDescription += fmt.Sprintf(" (trapped %v)", trap) } return causeDescription default: return fmt.Sprint(quotedName, " has unknown WaitStatus ", ws) } } elvish-0.11+ds1/eval/exception_test.go000066400000000000000000000002341323000013700177070ustar00rootroot00000000000000package eval import "testing" func TestException(t *testing.T) { runTests(t, []Test{ NewTest("kind-of ?(fail foo)").WantOutStrings("exception"), }) } elvish-0.11+ds1/eval/external_cmd.go000066400000000000000000000042001323000013700173140ustar00rootroot00000000000000package eval import ( "errors" "io/ioutil" "os" "os/exec" "syscall" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" "github.com/xiaq/persistent/hash" ) var ( ErrExternalCmdOpts = errors.New("external commands don't accept elvish options") ErrCdNoArg = errors.New("implicit cd accepts no arguments") ) // ExternalCmd is an external command. type ExternalCmd struct { Name string } func (ExternalCmd) Kind() string { return "fn" } func (e ExternalCmd) Equal(a interface{}) bool { return e == a } func (e ExternalCmd) Hash() uint32 { return hash.String(e.Name) } func (e ExternalCmd) Repr(int) string { return "" } // Call calls an external command. func (e ExternalCmd) Call(ec *Frame, argVals []types.Value, opts map[string]types.Value) { if len(opts) > 0 { throw(ErrExternalCmdOpts) } if util.DontSearch(e.Name) { stat, err := os.Stat(e.Name) if err == nil && stat.IsDir() { // implicit cd if len(argVals) > 0 { throw(ErrCdNoArg) } cdInner(e.Name, ec) return } } files := make([]*os.File, len(ec.ports)) for i, port := range ec.ports { files[i] = port.File } args := make([]string, len(argVals)+1) for i, a := range argVals { // NOTE Maybe we should enfore string arguments instead of coercing all // args into string args[i+1] = types.ToString(a) } path, err := exec.LookPath(e.Name) if err != nil { throw(err) } args[0] = path sys := makeSysProcAttr(ec.background) proc, err := os.StartProcess(path, args, &os.ProcAttr{Files: files, Sys: sys}) if err != nil { throw(err) } state, err := proc.Wait() if err != nil { throw(err) } else { maybeThrow(NewExternalCmdExit(e.Name, state.Sys().(syscall.WaitStatus), proc.Pid)) } } // EachExternal calls f for each name that can resolve to an external // command. // TODO(xiaq): Windows support func EachExternal(f func(string)) { for _, dir := range searchPaths() { // XXX Ignore error infos, _ := ioutil.ReadDir(dir) for _, info := range infos { if !info.IsDir() && (info.Mode()&0111 != 0) { f(info.Name()) } } } } elvish-0.11+ds1/eval/frame.go000066400000000000000000000104351323000013700157500ustar00rootroot00000000000000package eval import ( "bufio" "io" "os" "strings" "sync" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/util" ) // Frame contains information of the current running function, aknin to a call // frame in native CPU execution. A Frame is only modified during and very // shortly after creation; new Frame's are "forked" when needed. type Frame struct { *Evaler srcMeta *Source local, up Ns ports []*Port begin, end int traceback *util.SourceRange background bool } // NewTopFrame creates a top-level Frame. func NewTopFrame(ev *Evaler, src *Source, ports []*Port) *Frame { return &Frame{ ev, src, ev.Global, make(Ns), ports, 0, len(src.code), nil, false, } } // InputChan returns a channel from which input can be read. func (ec *Frame) InputChan() chan types.Value { return ec.ports[0].Chan } // InputFile returns a file from which input can be read. func (ec *Frame) InputFile() *os.File { return ec.ports[0].File } // OutputChan returns a channel onto which output can be written. func (ec *Frame) OutputChan() chan<- types.Value { return ec.ports[1].Chan } // OutputFile returns a file onto which output can be written. func (ec *Frame) OutputFile() *os.File { return ec.ports[1].File } // IterateInputs calls the passed function for each input element. func (ec *Frame) IterateInputs(f func(types.Value)) { var w sync.WaitGroup inputs := make(chan types.Value) w.Add(2) go func() { linesToChan(ec.ports[0].File, inputs) w.Done() }() go func() { for v := range ec.ports[0].Chan { inputs <- v } w.Done() }() go func() { w.Wait() close(inputs) }() for v := range inputs { f(v) } } func linesToChan(r io.Reader, ch chan<- types.Value) { filein := bufio.NewReader(r) for { line, err := filein.ReadString('\n') if line != "" { ch <- types.String(strings.TrimSuffix(line, "\n")) } if err != nil { if err != io.EOF { logger.Println("error on reading:", err) } break } } } // fork returns a modified copy of ec. The ports are forked, and the name is // changed to the given value. Other fields are copied shallowly. func (ec *Frame) fork(name string) *Frame { newPorts := make([]*Port, len(ec.ports)) for i, p := range ec.ports { newPorts[i] = p.Fork() } return &Frame{ ec.Evaler, ec.srcMeta, ec.local, ec.up, newPorts, ec.begin, ec.end, ec.traceback, ec.background, } } // PEval evaluates an op in a protected environment so that calls to errorf are // wrapped in an Error. func (ec *Frame) PEval(op Op) (err error) { defer catch(&err, ec) op.Exec(ec) return nil } func (ec *Frame) PCall(f Callable, args []types.Value, opts map[string]types.Value) (err error) { defer catch(&err, ec) f.Call(ec, args, opts) return nil } func (ec *Frame) PCaptureOutput(f Callable, args []types.Value, opts map[string]types.Value) (vs []types.Value, err error) { // XXX There is no source. return pcaptureOutput(ec, Op{ func(newec *Frame) { f.Call(newec, args, opts) }, -1, -1}) } func (ec *Frame) PCaptureOutputInner(f Callable, args []types.Value, opts map[string]types.Value, valuesCb func(<-chan types.Value), bytesCb func(*os.File)) error { // XXX There is no source. return pcaptureOutputInner(ec, Op{ func(newec *Frame) { f.Call(newec, args, opts) }, -1, -1}, valuesCb, bytesCb) } func catch(perr *error, ec *Frame) { // NOTE: We have to duplicate instead of calling util.Catch here, since // recover can only catch a panic when called directly from a deferred // function. r := recover() if r == nil { return } if exc, ok := r.(util.Thrown); ok { err := exc.Wrapped if _, ok := err.(*Exception); !ok { err = ec.makeException(err) } *perr = err } else if r != nil { panic(r) } } // makeException turns an error into an Exception by adding traceback. func (ec *Frame) makeException(e error) *Exception { return &Exception{e, ec.addTraceback()} } func (ec *Frame) addTraceback() *util.SourceRange { return &util.SourceRange{ Name: ec.srcMeta.describePath(), Source: ec.srcMeta.code, Begin: ec.begin, End: ec.end, Next: ec.traceback, } } // errorpf stops the ec.eval immediately by panicking with a diagnostic message. // The panic is supposed to be caught by ec.eval. func (ec *Frame) errorpf(begin, end int, format string, args ...interface{}) { ec.begin, ec.end = begin, end throwf(format, args...) } elvish-0.11+ds1/eval/glob.go000066400000000000000000000113121323000013700155740ustar00rootroot00000000000000package eval import ( "errors" "fmt" "reflect" "strings" "unicode" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/glob" "github.com/elves/elvish/parse" ) // GlobPattern is en ephemeral Value generated when evaluating tilde and // wildcards. type GlobPattern struct { glob.Pattern Flags GlobFlag Buts []string } type GlobFlag uint const ( NoMatchOK GlobFlag = 1 << iota ) func (f GlobFlag) Has(g GlobFlag) bool { return (f & g) == g } var ( _ types.Value = GlobPattern{} _ types.Indexer = GlobPattern{} ) var ( ErrMustFollowWildcard = errors.New("must follow wildcard") ErrModifierMustBeString = errors.New("modifier must be string") ErrWildcardNoMatch = errors.New("wildcard has no match") ) var runeMatchers = map[string]func(rune) bool{ "control": unicode.IsControl, "digit": unicode.IsDigit, "graphic": unicode.IsGraphic, "letter": unicode.IsLetter, "lower": unicode.IsDigit, "mark": unicode.IsMark, "number": unicode.IsNumber, "print": unicode.IsPrint, "punct": unicode.IsPunct, "space": unicode.IsSpace, "symbol": unicode.IsSymbol, "title": unicode.IsTitle, "upper": unicode.IsUpper, } func (GlobPattern) Kind() string { return "glob-pattern" } func (gp GlobPattern) Equal(a interface{}) bool { return reflect.DeepEqual(gp, a) } func (gp GlobPattern) Hash() uint32 { // GlobPattern is not a first-class value. return 0 } func (gp GlobPattern) Repr(int) string { return fmt.Sprintf("", gp) } func (gp GlobPattern) Index(modifiers []types.Value) []types.Value { for _, value := range modifiers { modifierv, ok := value.(types.String) if !ok { throw(ErrModifierMustBeString) } modifier := string(modifierv) switch { case modifier == "nomatch-ok": gp.Flags |= NoMatchOK case strings.HasPrefix(modifier, "but:"): gp.Buts = append(gp.Buts, modifier[len("but:"):]) case modifier == "match-hidden": lastSeg := gp.mustGetLastWildSeg() gp.Segments[len(gp.Segments)-1] = glob.Wild{ lastSeg.Type, true, lastSeg.Matchers, } default: if matcher, ok := runeMatchers[modifier]; ok { gp.addMatcher(matcher) } else if strings.HasPrefix(modifier, "set:") { set := modifier[len("set:"):] gp.addMatcher(func(r rune) bool { return strings.ContainsRune(set, r) }) } else if strings.HasPrefix(modifier, "range:") { rangeExpr := modifier[len("range:"):] badRangeExpr := fmt.Errorf("bad range modifier: %s", parse.Quote(rangeExpr)) runes := []rune(rangeExpr) if len(runes) != 3 { throw(badRangeExpr) } from, sep, to := runes[0], runes[1], runes[2] switch sep { case '-': gp.addMatcher(func(r rune) bool { return from <= r && r <= to }) case '~': gp.addMatcher(func(r rune) bool { return from <= r && r < to }) default: throw(badRangeExpr) } } else { throw(fmt.Errorf("unknown modifier %s", modifierv.Repr(types.NoPretty))) } } } return []types.Value{gp} } func (gp *GlobPattern) mustGetLastWildSeg() glob.Wild { if len(gp.Segments) == 0 { throw(ErrBadGlobPattern) } if !glob.IsWild(gp.Segments[len(gp.Segments)-1]) { throw(ErrMustFollowWildcard) } return gp.Segments[len(gp.Segments)-1].(glob.Wild) } func (gp *GlobPattern) addMatcher(matcher func(rune) bool) { lastSeg := gp.mustGetLastWildSeg() gp.Segments[len(gp.Segments)-1] = glob.Wild{ lastSeg.Type, lastSeg.MatchHidden, append(lastSeg.Matchers, matcher), } } func (gp *GlobPattern) append(segs ...glob.Segment) { gp.Segments = append(gp.Segments, segs...) } func wildcardToSegment(s string) (glob.Segment, error) { switch s { case "*": return glob.Wild{glob.Star, false, nil}, nil case "**": return glob.Wild{glob.StarStar, false, nil}, nil case "?": return glob.Wild{glob.Question, false, nil}, nil default: return nil, fmt.Errorf("bad wildcard: %q", s) } } func stringToSegments(s string) []glob.Segment { segs := []glob.Segment{} for i := 0; i < len(s); { j := i for ; j < len(s) && s[j] != '/'; j++ { } if j > i { segs = append(segs, glob.Literal{s[i:j]}) } if j < len(s) { for ; j < len(s) && s[j] == '/'; j++ { } segs = append(segs, glob.Slash{}) i = j } else { break } } return segs } func doGlob(gp GlobPattern, abort <-chan struct{}) []types.Value { but := make(map[string]struct{}) for _, s := range gp.Buts { but[s] = struct{}{} } vs := make([]types.Value, 0) if !gp.Glob(func(name string) bool { select { case <-abort: logger.Println("glob aborted") return false default: } if _, b := but[name]; !b { vs = append(vs, types.String(name)) } return true }) { throw(ErrInterrupted) } if len(vs) == 0 && !gp.Flags.Has(NoMatchOK) { throw(ErrWildcardNoMatch) } return vs } elvish-0.11+ds1/eval/interrupts.go000066400000000000000000000007011323000013700170700ustar00rootroot00000000000000package eval import "errors" // Interrupts returns a channel that is closed when an interrupt signal comes. func (ec *Frame) Interrupts() <-chan struct{} { return ec.intCh } var ErrInterrupted = errors.New("interrupted") // CheckInterrupts checks whether there has been an interrupt, and throws // ErrInterrupted if that is the case func (ec *Frame) CheckInterrupts() { select { case <-ec.Interrupts(): throw(ErrInterrupted) default: } } elvish-0.11+ds1/eval/must.go000066400000000000000000000015461323000013700156510ustar00rootroot00000000000000package eval import ( "github.com/elves/elvish/parse" ) func onePrimary(cn *parse.Compound) *parse.Primary { if len(cn.Indexings) == 1 && len(cn.Indexings[0].Indicies) == 0 { return cn.Indexings[0].Head } return nil } func oneString(cn *parse.Compound) (string, bool) { pn := onePrimary(cn) if pn != nil { switch pn.Type { case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted: return pn.Value, true } } return "", false } func mustPrimary(cp *compiler, cn *parse.Compound, msg string) *parse.Primary { p := onePrimary(cn) if p == nil { cp.errorpf(cn.Begin(), cn.End(), msg) } return p } // mustString musts that a Compound contains exactly one Primary of type // Variable. func mustString(cp *compiler, cn *parse.Compound, msg string) string { s, ok := oneString(cn) if !ok { cp.errorpf(cn.Begin(), cn.End(), msg) } return s } elvish-0.11+ds1/eval/names.go000066400000000000000000000052551323000013700157650ustar00rootroot00000000000000package eval import ( "errors" "os" "strings" "github.com/elves/elvish/eval/vartypes" ) // Resolution and iteration of variables and namespaces. // ErrStoreUnconnected is thrown by ResolveVar when a shared: variable needs to // be resolved but the store is not connected. var ErrStoreUnconnected = errors.New("store unconnected") // EachVariableInTop calls the passed function for each variable name in // namespace ns that can be found from the top context. func (ev *evalerScopes) EachVariableInTop(ns string, f func(s string)) { switch ns { case "builtin": for name := range ev.Builtin { f(name) } case "": for name := range ev.Global { f(name) } for name := range ev.Builtin { f(name) } case "e": EachExternal(func(cmd string) { f(cmd + FnSuffix) }) case "E": for _, s := range os.Environ() { if i := strings.IndexByte(s, '='); i > 0 { f(s[:i]) } } case "shared": // TODO Add daemon RPC for enumerating shared variables. default: mod := ev.Global[ns+NsSuffix] if mod == nil { mod = ev.Builtin[ns+NsSuffix] } if mod != nil { for name := range mod.Get().(Ns) { f(name) } } } } // EachModInTop calls the passed function for each module that can be found from // the top context. func (ev *evalerScopes) EachModInTop(f func(s string)) { for name := range ev.Global { if strings.HasSuffix(name, NsSuffix) { f(name[:len(name)-len(NsSuffix)]) } } for name := range ev.Builtin { if strings.HasSuffix(name, NsSuffix) { f(name[:len(name)-len(NsSuffix)]) } } } // EachNsInTop calls the passed function for each namespace that can be used // from the top context. func (ev *evalerScopes) EachNsInTop(f func(s string)) { f("builtin") f("e") f("E") f("shared") ev.EachModInTop(f) } // ResolveVar resolves a variable. When the variable cannot be found, nil is // returned. func (ec *Frame) ResolveVar(ns, name string) vartypes.Variable { switch ns { case "local": return ec.local[name] case "up": return ec.up[name] case "builtin": return ec.Builtin[name] case "": if v := ec.local[name]; v != nil { return v } if v, ok := ec.up[name]; ok { return v } return ec.Builtin[name] case "e": if strings.HasSuffix(name, FnSuffix) { return vartypes.NewRo(ExternalCmd{name[:len(name)-len(FnSuffix)]}) } case "E": return vartypes.NewEnv(name) case "shared": if ec.DaemonClient == nil { throw(ErrStoreUnconnected) } return sharedVariable{ec.DaemonClient, name} default: ns := ec.ResolveMod(ns) if ns != nil { return ns[name] } } return nil } func (ec *Frame) ResolveMod(name string) Ns { ns := ec.ResolveVar("", name+NsSuffix) if ns == nil { return nil } return ns.Get().(Ns) } elvish-0.11+ds1/eval/ns.go000066400000000000000000000020011323000013700152640ustar00rootroot00000000000000package eval import ( "fmt" "reflect" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" ) // Ns is a map from names to variables. type Ns map[string]vartypes.Variable var _ types.Value = Ns(nil) func (Ns) Kind() string { return "ns" } func (ns Ns) Hash() uint32 { return uint32(addrOf(ns)) } func (ns Ns) Equal(rhs interface{}) bool { if ns2, ok := rhs.(Ns); ok { return addrOf(ns) == addrOf(ns2) } return false } func (ns Ns) Repr(int) string { return fmt.Sprintf("", addrOf(ns)) } func addrOf(a interface{}) uintptr { return reflect.ValueOf(a).Pointer() } func (ns Ns) static() staticNs { static := make(staticNs) for name := range ns { static.set(name) } return static } // staticNs represents static information of an Ns. type staticNs map[string]struct{} func (ns staticNs) set(name string) { ns[name] = struct{}{} } func (ns staticNs) del(name string) { delete(ns, name) } func (ns staticNs) has(name string) bool { _, ok := ns[name] return ok } elvish-0.11+ds1/eval/ns_test.go000066400000000000000000000004751323000013700163400ustar00rootroot00000000000000package eval import "testing" func TestNs(t *testing.T) { runTests(t, []Test{ NewTest("kind-of (ns)").WantOutStrings("ns"), // A Ns is only equal to itself NewTest("ns = (ns); eq $ns $ns").WantOutBools(true), NewTest("eq (ns) (ns)").WantOutBools(false), NewTest("eq (ns) [&]").WantOutBools(false), }) } elvish-0.11+ds1/eval/port.go000066400000000000000000000027731323000013700156500ustar00rootroot00000000000000package eval import ( "os" "github.com/elves/elvish/eval/types" ) // Port conveys data stream. It always consists of a byte band and a channel band. type Port struct { File *os.File Chan chan types.Value CloseFile bool CloseChan bool } // Fork returns a copy of a Port with the Close* flags unset. func (p *Port) Fork() *Port { return &Port{p.File, p.Chan, false, false} } // Close closes a Port. func (p *Port) Close() { if p == nil { return } if p.CloseFile { p.File.Close() } if p.CloseChan { // Logger.Printf("closing channel %v", p.Chan) close(p.Chan) } } // ClosePorts closes a list of Ports. func ClosePorts(ports []*Port) { for _, port := range ports { // Logger.Printf("closing port %d", i) port.Close() } } var ( // ClosedChan is a closed channel, suitable for use as placeholder channel input. ClosedChan = make(chan types.Value) // BlackholeChan is channel writes onto which disappear, suitable for use as // placeholder channel output. BlackholeChan = make(chan types.Value) // DevNull is /dev/null. DevNull *os.File // DevNullClosedInput is a port made up from DevNull and ClosedChan, // suitable as placeholder input port. DevNullClosedChan *Port ) func init() { close(ClosedChan) go func() { for range BlackholeChan { } }() var err error DevNull, err = os.Open(os.DevNull) if err != nil { os.Stderr.WriteString("cannot open " + os.DevNull + ", shell might not function normally\n") } DevNullClosedChan = &Port{File: DevNull, Chan: ClosedChan} } elvish-0.11+ds1/eval/process_unix.go000066400000000000000000000006561323000013700174030ustar00rootroot00000000000000// +build !windows,!plan9 package eval import ( "os/signal" "syscall" "github.com/elves/elvish/sys" ) // Process control functions in Unix. func ignoreTTOU() { signal.Ignore(syscall.SIGTTOU) } func unignoreTTOU() { signal.Reset(syscall.SIGTTOU) } func putSelfInFg() error { return sys.Tcsetpgrp(0, syscall.Getpgrp()) } func makeSysProcAttr(bg bool) *syscall.SysProcAttr { return &syscall.SysProcAttr{Setpgid: bg} } elvish-0.11+ds1/eval/process_windows.go000066400000000000000000000006111323000013700201010ustar00rootroot00000000000000package eval import "syscall" // Process control functions in Windows. These are all NOPs. func ignoreTTOU() {} func unignoreTTOU() {} func putSelfInFg() error { return nil } const DETACHED_PROCESS = 0x00000008 func makeSysProcAttr(bg bool) *syscall.SysProcAttr { flags := uint32(0) if bg { flags |= DETACHED_PROCESS } return &syscall.SysProcAttr{CreationFlags: flags} } elvish-0.11+ds1/eval/purely_eval.go000066400000000000000000000036701323000013700172100ustar00rootroot00000000000000package eval import ( "errors" "strings" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" ) var ErrImpure = errors.New("expression is impure") func PurelyEvalCompound(cn *parse.Compound) (string, error) { return (*Evaler)(nil).PurelyEvalCompound(cn) } func (ev *Evaler) PurelyEvalCompound(cn *parse.Compound) (string, error) { return ev.PurelyEvalPartialCompound(cn, nil) } func (ev *Evaler) PurelyEvalPartialCompound(cn *parse.Compound, upto *parse.Indexing) (string, error) { tilde := false head := "" for _, in := range cn.Indexings { if len(in.Indicies) > 0 { return "", ErrImpure } switch in.Head.Type { case parse.Tilde: tilde = true case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted: head += in.Head.Value case parse.Variable: if ev == nil { return "", ErrImpure } v := ev.PurelyEvalPrimary(in.Head) if s, ok := v.(types.String); ok { head += string(s) } else { return "", ErrImpure } default: return "", ErrImpure } if in == upto { break } } if tilde { i := strings.Index(head, "/") if i == -1 { i = len(head) } uname := head[:i] home, err := util.GetHome(uname) if err != nil { return "", err } head = home + head[i:] } return head, nil } // PurelyEvalPrimary evaluates a primary node without causing any side effects. // If this cannot be done, it returns nil. // // Currently, only string literals and variables with no @ can be evaluated. func (ev *Evaler) PurelyEvalPrimary(pn *parse.Primary) types.Value { switch pn.Type { case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted: return types.String(pn.Value) case parse.Variable: explode, ns, name := ParseVariable(pn.Value) if explode { return nil } ec := NewTopFrame(ev, NewInternalSource("[purely eval]"), nil) variable := ec.ResolveVar(ns, name) if variable != nil { return variable.Get() } } return nil } elvish-0.11+ds1/eval/pwd.go000066400000000000000000000011431323000013700154440ustar00rootroot00000000000000package eval import ( "os" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/eval/vartypes" ) // PwdVariable is a variable whose value always reflects the current working // directory. Setting it changes the current working directory. type PwdVariable struct { store AddDirer } var _ vartypes.Variable = PwdVariable{} func (PwdVariable) Get() types.Value { pwd, err := os.Getwd() maybeThrow(err) return types.String(pwd) } func (pwd PwdVariable) Set(v types.Value) error { path, ok := v.(types.String) if !ok { return ErrPathMustBeString } return Chdir(string(path), pwd.store) } elvish-0.11+ds1/eval/re/000077500000000000000000000000001323000013700147325ustar00rootroot00000000000000elvish-0.11+ds1/eval/re/match.go000066400000000000000000000013631323000013700163600ustar00rootroot00000000000000package re import ( "strconv" "github.com/elves/elvish/eval/types" "github.com/xiaq/persistent/vector" ) var ( matchDescriptor = types.NewStructDescriptor("text", "start", "end", "groups") submatchDescriptor = types.NewStructDescriptor("text", "start", "end") ) func newMatch(text string, start, end int, groups vector.Vector) *types.Struct { return types.NewStruct(matchDescriptor, []types.Value{ types.String(text), types.String(strconv.Itoa(start)), types.String(strconv.Itoa(end)), types.NewList(groups), }) } func newSubmatch(text string, start, end int) *types.Struct { return types.NewStruct(submatchDescriptor, []types.Value{ types.String(text), types.String(strconv.Itoa(start)), types.String(strconv.Itoa(end))}) } elvish-0.11+ds1/eval/re/re.go000066400000000000000000000114041323000013700156670ustar00rootroot00000000000000// Package re implements the re: module for using regular expressions. package re import ( "fmt" "regexp" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/util" "github.com/xiaq/persistent/vector" ) func Ns() eval.Ns { ns := eval.Ns{} eval.AddBuiltinFns(ns, fns...) return ns } var fns = []*eval.BuiltinFn{ {"quote", eval.WrapStringToString(regexp.QuoteMeta)}, {"match", match}, {"find", find}, {"replace", replace}, {"split", split}, } func match(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { out := ec.OutputChan() var ( argPattern types.String argSource types.String optPOSIX types.Bool ) eval.ScanArgs(args, &argPattern, &argSource) eval.ScanOpts(opts, eval.OptToScan{"posix", &optPOSIX, types.Bool(false)}) pattern := makePattern(argPattern, optPOSIX, types.Bool(false)) matched := pattern.MatchString(string(argSource)) out <- types.Bool(matched) } func find(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { out := ec.OutputChan() var ( argPattern types.String argSource types.String optPOSIX types.Bool optLongest types.Bool optMax int ) eval.ScanArgs(args, &argPattern, &argSource) eval.ScanOpts(opts, eval.OptToScan{"posix", &optPOSIX, types.Bool(false)}, eval.OptToScan{"longest", &optLongest, types.Bool(false)}, eval.OptToScan{"max", &optMax, types.String("-1")}) pattern := makePattern(argPattern, optPOSIX, optLongest) source := string(argSource) matches := pattern.FindAllSubmatchIndex([]byte(argSource), optMax) for _, match := range matches { start, end := match[0], match[1] groups := vector.Empty for i := 0; i < len(match); i += 2 { start, end := match[i], match[i+1] text := "" // FindAllSubmatchIndex may return negative indicies to indicate // that the pattern didn't appear in the text. if start >= 0 && end >= 0 { text = source[start:end] } groups = groups.Cons(newSubmatch(text, start, end)) } out <- newMatch(source[start:end], start, end, groups) } } func replace(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { out := ec.OutputChan() var ( argPattern types.String argRepl types.Value argSource types.String optPOSIX types.Bool optLongest types.Bool optLiteral types.Bool ) eval.ScanArgs(args, &argPattern, &argRepl, &argSource) eval.ScanOpts(opts, eval.OptToScan{"posix", &optPOSIX, types.Bool(false)}, eval.OptToScan{"longest", &optLongest, types.Bool(false)}, eval.OptToScan{"literal", &optLiteral, types.Bool(false)}) pattern := makePattern(argPattern, optPOSIX, optLongest) var result string if optLiteral { repl, ok := argRepl.(types.String) if !ok { throwf("replacement must be string when literal is set, got %s", argRepl.Kind()) } result = pattern.ReplaceAllLiteralString(string(argSource), string(repl)) } else { switch repl := argRepl.(type) { case types.String: result = pattern.ReplaceAllString(string(argSource), string(repl)) case eval.Fn: replFunc := func(s string) string { values, err := ec.PCaptureOutput(repl, []types.Value{types.String(s)}, eval.NoOpts) maybeThrow(err) if len(values) != 1 { throwf("replacement function must output exactly one value, got %d", len(values)) } output, ok := values[0].(types.String) if !ok { throwf("replacement function must output one string, got %s", values[0].Kind()) } return string(output) } result = pattern.ReplaceAllStringFunc(string(argSource), replFunc) default: throwf("replacement must be string or function, got %s", argRepl.Kind()) } } out <- types.String(result) } func split(ec *eval.Frame, args []types.Value, opts map[string]types.Value) { out := ec.OutputChan() var ( argPattern types.String argSource types.String optPOSIX types.Bool optLongest types.Bool optMax int ) eval.ScanArgs(args, &argPattern, &argSource) eval.ScanOpts(opts, eval.OptToScan{"posix", &optPOSIX, types.Bool(false)}, eval.OptToScan{"longest", &optLongest, types.Bool(false)}, eval.OptToScan{"max", &optMax, types.String("-1")}) pattern := makePattern(argPattern, optPOSIX, optLongest) pieces := pattern.Split(string(argSource), optMax) for _, piece := range pieces { out <- types.String(piece) } } func makePattern(argPattern types.String, optPOSIX, optLongest types.Bool) *regexp.Regexp { var ( pattern *regexp.Regexp err error ) if optPOSIX { pattern, err = regexp.CompilePOSIX(string(argPattern)) } else { pattern, err = regexp.Compile(string(argPattern)) } maybeThrow(err) if optLongest { pattern.Longest() } return pattern } func throwf(format string, args ...interface{}) { util.Throw(fmt.Errorf(format, args...)) } func maybeThrow(err error) { if err != nil { util.Throw(err) } } elvish-0.11+ds1/eval/re/re_test.go000066400000000000000000000030651323000013700167320ustar00rootroot00000000000000package re import ( "testing" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/vartypes" "github.com/xiaq/persistent/vector" ) var tests = []eval.Test{ eval.NewTest("re:match . xyz").WantOutBools(true), eval.NewTest("re:match . ''").WantOutBools(false), eval.NewTest("re:match '[a-z]' A").WantOutBools(false), eval.NewTest("re:find . ab").WantOut( newMatch("a", 0, 1, vector.Empty.Cons(newSubmatch("a", 0, 1))), newMatch("b", 1, 2, vector.Empty.Cons(newSubmatch("b", 1, 2))), ), eval.NewTest("re:find '[A-Z]([0-9])' 'A1 B2'").WantOut( newMatch("A1", 0, 2, vector.Empty.Cons(newSubmatch("A1", 0, 2)).Cons(newSubmatch("1", 1, 2))), newMatch("B2", 3, 5, vector.Empty.Cons(newSubmatch("B2", 3, 5)).Cons(newSubmatch("2", 4, 5))), ), eval.NewTest("re:replace '(ba|z)sh' '${1}SH' 'bash and zsh'").WantOutStrings("baSH and zSH"), eval.NewTest("re:replace &literal '(ba|z)sh' '$sh' 'bash and zsh'").WantOutStrings("$sh and $sh"), eval.NewTest("re:replace '(ba|z)sh' [x]{ put [&bash=BaSh &zsh=ZsH][$x] } 'bash and zsh'").WantOutStrings("BaSh and ZsH"), eval.NewTest("re:split : /usr/sbin:/usr/bin:/bin").WantOutStrings("/usr/sbin", "/usr/bin", "/bin"), eval.NewTest("re:split &max=2 : /usr/sbin:/usr/bin:/bin").WantOutStrings("/usr/sbin", "/usr/bin:/bin"), eval.NewTest("re:quote a.txt").WantOutStrings(`a\.txt`), eval.NewTest("re:quote '(*)'").WantOutStrings(`\(\*\)`), } func TestRe(t *testing.T) { eval.RunTests(t, tests, func() *eval.Evaler { ev := eval.NewEvaler() ev.Builtin["re"+eval.NsSuffix] = vartypes.NewRo(Ns()) return ev }) } elvish-0.11+ds1/eval/resolve.go000066400000000000000000000006341323000013700163350ustar00rootroot00000000000000package eval func resolve(s string, ec *Frame) Fn { // NOTE: This needs to be kept in sync with the resolution algorithm used in // (*compiler).form. // Try variable explode, ns, name := ParseVariable(string(s)) if !explode { if v := ec.ResolveVar(ns, name+FnSuffix); v != nil { if caller, ok := v.Get().(Fn); ok { return caller } } } // External command return ExternalCmd{string(s)} } elvish-0.11+ds1/eval/scan.go000066400000000000000000000100731323000013700156000ustar00rootroot00000000000000package eval // This file implements facilities for "scanning" arguments and options. import ( "reflect" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" ) // ScanArgs scans arguments into pointers to supported argument types. If the // arguments cannot be scanned, an error is thrown. func ScanArgs(src []types.Value, dstPtrs ...interface{}) { if len(src) != len(dstPtrs) { throwf("arity mistmatch: want %d arguments, got %d", len(dstPtrs), len(src)) } for i, value := range src { scanValueToGo(value, dstPtrs[i]) } } // ScanArgsVariadic is like ScanArgs, but the last element of args should be a // pointer to a slice, and the rest of arguments will be scanned into it. func ScanArgsVariadic(src []types.Value, dstPtrs ...interface{}) { if len(src) < len(dstPtrs)-1 { throwf("arity mistmatch: want at least %d arguments, got %d", len(dstPtrs)-1, len(src)) } ScanArgs(src[:len(dstPtrs)-1], dstPtrs[:len(dstPtrs)-1]...) // Scan the rest of arguments into a slice. rest := src[len(dstPtrs)-1:] restDst := reflect.ValueOf(dstPtrs[len(dstPtrs)-1]) if restDst.Kind() != reflect.Ptr || restDst.Elem().Kind() != reflect.Slice { throwf("internal bug: %T to ScanArgsVariadic, need pointer to slice", dstPtrs[len(dstPtrs)-1]) } scanned := reflect.MakeSlice(restDst.Elem().Type(), len(rest), len(rest)) for i, value := range rest { scanValueToGo(value, scanned.Index(i).Addr().Interface()) } reflect.Indirect(restDst).Set(scanned) } // ScanArgsOptionalInput is like ScanArgs, but the argument can contain an // optional iterable value at the end containing inputs to the function. The // return value is a function that iterates the iterable value if it exists, or // the input otherwise. func ScanArgsOptionalInput(ec *Frame, src []types.Value, dstArgs ...interface{}) func(func(types.Value)) { switch len(src) { case len(dstArgs): ScanArgs(src, dstArgs...) return ec.IterateInputs case len(dstArgs) + 1: ScanArgs(src[:len(dstArgs)], dstArgs...) value := src[len(dstArgs)] iterable, ok := value.(types.Iterator) if !ok { throwf("need iterable argument, got %s", value.Kind()) } return func(f func(types.Value)) { iterable.Iterate(func(v types.Value) bool { f(v) return true }) } default: throwf("arity mistmatch: want %d or %d arguments, got %d", len(dstArgs), len(dstArgs)+1, len(src)) return nil } } // OptToScan is a data structure for an option that is intended to be used in // ScanOpts. type OptToScan struct { Name string Ptr interface{} Default types.Value } // ScanOpts scans options from a map. func ScanOpts(m map[string]types.Value, opts ...OptToScan) { scanned := make(map[string]bool) for _, opt := range opts { a := opt.Ptr value, ok := m[opt.Name] if !ok { value = opt.Default } scanValueToGo(value, a) scanned[opt.Name] = true } for key := range m { if !scanned[key] { throwf("unknown option %s", parse.Quote(key)) } } } // ScanOptsToStruct scan options from a map like ScanOpts except the destination // is a struct whose fields correspond to the options to be parsed. A field // named FieldName corresponds to the option named field-name, unless the field // has a explicit "name" tag. func ScanOptsToStruct(m map[string]types.Value, structPtr interface{}) { ptrValue := reflect.ValueOf(structPtr) if ptrValue.Kind() != reflect.Ptr || ptrValue.Elem().Kind() != reflect.Struct { throwf("internal bug: need struct ptr for ScanOptsToStruct, got %T", structPtr) } struc := ptrValue.Elem() // fieldIdxForOpt maps option name to the index of field in struc. fieldIdxForOpt := make(map[string]int) for i := 0; i < struc.Type().NumField(); i++ { // ignore unexported fields if !struc.Field(i).CanSet() { continue } f := struc.Type().Field(i) optName := f.Tag.Get("name") if optName == "" { optName = util.CamelToDashed(f.Name) } fieldIdxForOpt[optName] = i } for k, v := range m { fieldIdx, ok := fieldIdxForOpt[k] if !ok { throwf("unknown option %s", parse.Quote(k)) } scanValueToGo(v, struc.Field(fieldIdx).Addr().Interface()) } } elvish-0.11+ds1/eval/scan_test.go000066400000000000000000000121111323000013700166320ustar00rootroot00000000000000package eval import ( "reflect" "testing" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/util" ) // These are used as arguments to scanArg, since Go does not allow taking // address of literals. func intPtr() *int { var x int; return &x } func intsPtr() *[]int { var x []int; return &x } func float64Ptr() *float64 { var x float64; return &x } func stringPtr() *types.String { var x types.String; return &x } var scanArgsTestCases = []struct { variadic bool src []types.Value dstPtrs []interface{} want []interface{} bad bool }{ // Scanning an int and a String { src: []types.Value{types.String("20"), types.String("20")}, dstPtrs: []interface{}{intPtr(), stringPtr()}, want: []interface{}{20, types.String("20")}, }, // Scanning a String and any number of ints (here 2) { variadic: true, src: []types.Value{types.String("a"), types.String("1"), types.String("2")}, dstPtrs: []interface{}{stringPtr(), intsPtr()}, want: []interface{}{types.String("a"), []int{1, 2}}, }, // Scanning a String and any number of ints (here 0) { variadic: true, src: []types.Value{types.String("a")}, dstPtrs: []interface{}{stringPtr(), intsPtr()}, want: []interface{}{types.String("a"), []int{}}, }, // Arity mismatch: too few arguments (non-variadic) { bad: true, src: []types.Value{}, dstPtrs: []interface{}{stringPtr()}, }, // Arity mismatch: too few arguments (non-variadic) { bad: true, src: []types.Value{types.String("")}, dstPtrs: []interface{}{stringPtr(), intPtr()}, }, // Arity mismatch: too many arguments (non-variadic) { bad: true, src: []types.Value{types.String("1"), types.String("2")}, dstPtrs: []interface{}{stringPtr()}, }, // Type mismatch (nonvariadic) { bad: true, src: []types.Value{types.String("x")}, dstPtrs: []interface{}{intPtr()}, }, // Arity mismatch: too few arguments (variadic) { bad: true, src: []types.Value{}, dstPtrs: []interface{}{stringPtr(), intsPtr()}, variadic: true, }, // Type mismatch within rest arg { bad: true, src: []types.Value{types.String("a"), types.String("1"), types.String("lorem")}, dstPtrs: []interface{}{stringPtr(), intsPtr()}, variadic: true, }, } func TestScanArgs(t *testing.T) { for _, tc := range scanArgsTestCases { funcToTest := ScanArgs if tc.variadic { funcToTest = ScanArgsVariadic } if tc.bad { if !util.ThrowsAny(func() { funcToTest(tc.src, tc.dstPtrs...) }) { t.Errorf("ScanArgs(%v, %v) should throw an error", tc.src, tc.dstPtrs) } continue } funcToTest(tc.src, tc.dstPtrs...) dsts := make([]interface{}, len(tc.dstPtrs)) for i, ptr := range tc.dstPtrs { dsts[i] = indirect(ptr) } err := compareSlice(tc.want, dsts) if err != nil { t.Errorf("ScanArgs(%v) got %v, want %v", tc.src, dsts, tc.want) } } } var scanArgsBadTestCases = []struct { variadic bool src []types.Value dstPtrs []interface{} }{} var scanOptsTestCases = []struct { bad bool src map[string]types.Value opts []OptToScan want []interface{} }{ { src: map[string]types.Value{ "foo": types.String("bar"), }, opts: []OptToScan{ {"foo", stringPtr(), types.String("haha")}, }, want: []interface{}{ types.String("bar"), }, }, // Default values. { src: map[string]types.Value{ "foo": types.String("bar"), }, opts: []OptToScan{ {"foo", stringPtr(), types.String("haha")}, {"lorem", stringPtr(), types.String("ipsum")}, }, want: []interface{}{ types.String("bar"), types.String("ipsum"), }, }, // Unknown option { bad: true, src: map[string]types.Value{ "foo": types.String("bar"), }, opts: []OptToScan{ {"lorem", stringPtr(), types.String("ipsum")}, }, }, } func TestScanOpts(t *testing.T) { for _, tc := range scanOptsTestCases { if tc.bad { if !util.ThrowsAny(func() { ScanOpts(tc.src, tc.opts...) }) { t.Errorf("ScanOpts(%v, %v...) should throw an error", tc.src, tc.opts) } continue } ScanOpts(tc.src, tc.opts...) dsts := make([]interface{}, len(tc.opts)) for i, opt := range tc.opts { dsts[i] = indirect(opt.Ptr) } err := compareSlice(tc.want, dsts) if err != nil { t.Errorf("ScanOpts(%v, %v) got %v, want %v", tc.src, tc.opts, dsts, tc.want) } } } var scanArgTestCases = []struct { source types.Value destPtr interface{} want interface{} }{ {types.String("20"), intPtr(), 20}, {types.String("0x20"), intPtr(), 0x20}, {types.String("20"), float64Ptr(), 20.0}, {types.String("23.33"), float64Ptr(), 23.33}, {types.String(""), stringPtr(), types.String("")}, {types.String("1"), stringPtr(), types.String("1")}, {types.String("2.33"), stringPtr(), types.String("2.33")}, } func TestScanArg(t *testing.T) { for _, tc := range scanArgTestCases { scanValueToGo(tc.source, tc.destPtr) if !equals(indirect(tc.destPtr), tc.want) { t.Errorf("scanArg(%s) got %q, want %v", tc.source, indirect(tc.destPtr), tc.want) } } } func indirect(i interface{}) interface{} { return reflect.Indirect(reflect.ValueOf(i)).Interface() } elvish-0.11+ds1/eval/shared.go000066400000000000000000000006421323000013700161230ustar00rootroot00000000000000package eval import ( "github.com/elves/elvish/daemon" "github.com/elves/elvish/eval/types" ) type sharedVariable struct { store *daemon.Client name string } func (sv sharedVariable) Set(val types.Value) error { return sv.store.SetSharedVar(sv.name, types.ToString(val)) } func (sv sharedVariable) Get() types.Value { value, err := sv.store.SharedVar(sv.name) maybeThrow(err) return types.String(value) } elvish-0.11+ds1/eval/source.go000066400000000000000000000051421323000013700161550ustar00rootroot00000000000000package eval import ( "fmt" "strconv" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" "github.com/xiaq/persistent/hash" ) // Source describes a piece of source code. type Source struct { typ SrcType name string path string code string } // NewInteractiveSource returns a Source for a piece of code entered // interactively. func NewInteractiveSource(code string) *Source { return &Source{SrcInteractive, "", "", code} } // NewScriptSource returns a Source for a piece of code used as a script. func NewScriptSource(name, path, code string) *Source { return &Source{SrcScript, name, path, code} } // NewModuleSource returns a Source for a piece of code used as a module. func NewModuleSource(name, path, code string) *Source { return &Source{SrcModule, name, path, code} } func NewInternalSource(name string) *Source { return &Source{SrcInternal, name, name, ""} } func (src *Source) describePath() string { if src.typ == SrcInteractive { return "[tty]" } return src.path } var ( _ types.Value = (*Source)(nil) _ types.IndexOneer = (*Source)(nil) ) func (src *Source) Kind() string { return "map" } func (src *Source) Hash() uint32 { return hash.DJB(uint32(src.typ), hash.String(src.name), hash.String(src.path), hash.String(src.code)) } func (src *Source) Equal(other interface{}) bool { if src2, ok := other.(*Source); ok { return *src == *src2 } return false } func (src *Source) Repr(int) string { return fmt.Sprintf("", src.typ, parse.Quote(src.name), parse.Quote(src.path)) } func (src *Source) IndexOne(k types.Value) types.Value { switch k { case types.String("type"): return types.String(src.typ.String()) case types.String("name"): return types.String(src.name) case types.String("path"): return types.String(src.path) case types.String("code"): return types.String(src.code) default: throwf("No such key %s", k.Repr(types.NoPretty)) panic("unreachable") } } // SrcType records the type of a piece of source code. type SrcType int const ( // SrcInternal is a special SrcType for internal operations. SrcInternal SrcType = iota // SrcInteractive is the type of source code entered interactively. SrcInteractive // SrcScript is the type of source code used as a script. SrcScript // SrcModule is the type of source code used as a module. SrcModule ) func (t SrcType) String() string { switch t { case SrcInternal: return "internal" case SrcInteractive: return "interactive" case SrcScript: return "script" case SrcModule: return "module" default: return "bad type " + strconv.Itoa(int(t)) } } elvish-0.11+ds1/eval/testmain_test.go000066400000000000000000000042621323000013700175420ustar00rootroot00000000000000package eval import ( "io/ioutil" "os" "path/filepath" "sort" "strings" "testing" "github.com/elves/elvish/util" ) var ( filesToCreate = []string{ "a1", "a2", "a3", "a10", "b1", "b2", "b3", "c1", "c2", "foo", "bar", "lorem", "ipsum", } dirsToCreate = []string{"dir", "dir2"} fileListing = getFileListing() ) func getFileListing() []string { var x []string x = append(x, filesToCreate...) x = append(x, dirsToCreate...) sort.Strings(x) return x } func getFilesWithPrefix(prefixes ...string) []string { var x []string for _, name := range fileListing { for _, prefix := range prefixes { if strings.HasPrefix(name, prefix) { x = append(x, name) break } } } sort.Strings(x) return x } func getFilesBut(excludes ...string) []string { var x []string for _, name := range fileListing { excluded := false for _, exclude := range excludes { if name == exclude { excluded = true break } } if !excluded { x = append(x, name) } } sort.Strings(x) return x } var mods = map[string]string{ "lorem": "name = lorem; fn put-name { put $name }", "d": "name = d", "a/b/c/d": "name = a/b/c/d", "a/b/c/x": "use ./d; d = $d:name; use ../../../lorem; lorem = $lorem:name", "has/init": "put has/init", } var libDir string func TestMain(m *testing.M) { var exitCode int util.InTempDir(func(tmpHome string) { oldHome := os.Getenv("HOME") os.Setenv("HOME", tmpHome) defer os.Setenv("HOME", oldHome) for _, filename := range filesToCreate { file, err := os.Create(filename) if err != nil { panic(err) } file.Close() } for _, dirname := range dirsToCreate { err := os.Mkdir(dirname, 0700) if err != nil { panic(err) } } util.WithTempDir(func(dir string) { libDir = dir for mod, content := range mods { fname := filepath.Join(libDir, mod+".elv") os.MkdirAll(filepath.Dir(fname), 0700) err := ioutil.WriteFile(fname, []byte(content), 0600) if err != nil { panic(err) } } exitCode = m.Run() }) }) os.Exit(exitCode) } func runTests(t *testing.T, tests []Test) { RunTests(t, tests, func() *Evaler { ev := NewEvaler() ev.SetLibDir(libDir) return ev }) } elvish-0.11+ds1/eval/testutils.go000066400000000000000000000133051323000013700167150ustar00rootroot00000000000000// Common testing utilities. This file does not file a _test.go suffix so that // it can be used from other packages that also want to test the modules they // implement (e.g. edit: and re:). package eval import ( "bytes" "errors" "fmt" "os" "reflect" "testing" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" ) // Test is a test case for TestEval. type Test struct { text string want } type want struct { out []types.Value bytesOut []byte err error } // A special value for want.err to indicate that any error, as long as not nil, // is OK var errAny = errors.New("") var ( wantNothing = want{} wantTrue = want{out: bools(true)} wantFalse = want{out: bools(false)} ) // Shorthands for values in want.out func strs(ss ...string) []types.Value { vs := make([]types.Value, len(ss)) for i, s := range ss { vs[i] = types.String(s) } return vs } func bools(bs ...bool) []types.Value { vs := make([]types.Value, len(bs)) for i, b := range bs { vs[i] = types.Bool(b) } return vs } // NewTest returns a new Test with the specified source code. func NewTest(text string) Test { return Test{text: text} } // WantOut returns an altered Test that requires the source code to produce the // specified values in the value channel when evaluated. func (t Test) WantOut(vs ...types.Value) Test { t.want.out = vs return t } // WantOutStrings returns an altered Test that requires the source code to // produce the specified string values in the value channel when evaluated. func (t Test) WantOutStrings(ss ...string) Test { return t.WantOut(strs(ss...)...) } // WantOutBools returns an altered Test that requires the source code to produce // the specified boolean values in the value channel when evaluated. func (t Test) WantOutBools(bs ...bool) Test { return t.WantOut(bools(bs...)...) } // WantBytesOut returns an altered test that requires the source code to produce // the specified output in the byte pipe when evaluated. func (t Test) WantBytesOut(b []byte) Test { t.want.bytesOut = b return t } // WantBytesOutString is the same as WantBytesOut except that its argument is a // string. func (t Test) WantBytesOutString(s string) Test { return t.WantBytesOut([]byte(s)) } // WantErr returns an altered Test that requires the source code to result in // the specified error when evaluted. func (t Test) WantErr(err error) Test { t.want.err = err return t } // WantAnyErr returns an altered Test that requires the source code to result in // any error when evaluated. func (t Test) WantAnyErr() Test { return t.WantErr(errAny) } // RunTests runs test cases. For each test case, a new Evaler is made by calling // makeEvaler. func RunTests(t *testing.T, evalTests []Test, makeEvaler func() *Evaler) { for _, tt := range evalTests { // fmt.Printf("eval %q\n", tt.text) ev := makeEvaler() defer ev.Close() out, bytesOut, err := evalAndCollect(t, ev, []string{tt.text}, len(tt.want.out)) first := true errorf := func(format string, args ...interface{}) { if first { first = false t.Errorf("eval(%q) fails:", tt.text) } t.Errorf(" "+format, args...) } if !matchOut(tt.want.out, out) { errorf("got out=%v, want %v", out, tt.want.out) } if !bytes.Equal(tt.want.bytesOut, bytesOut) { errorf("got bytesOut=%q, want %q", bytesOut, tt.want.bytesOut) } if !matchErr(tt.want.err, err) { errorf("got err=%v, want %v", err, tt.want.err) } } } func evalAndCollect(t *testing.T, ev *Evaler, texts []string, chsize int) ([]types.Value, []byte, error) { // Collect byte output bytesOut := []byte{} pr, pw, _ := os.Pipe() bytesDone := make(chan struct{}) go func() { for { var buf [64]byte nr, err := pr.Read(buf[:]) bytesOut = append(bytesOut, buf[:nr]...) if err != nil { break } } close(bytesDone) }() // Channel output outs := []types.Value{} // Eval error. Only that of the last text is saved. var ex error for i, text := range texts { name := fmt.Sprintf("test%d.elv", i) src := NewScriptSource(name, name, text) op := mustParseAndCompile(t, ev, src) outCh := make(chan types.Value, chsize) outDone := make(chan struct{}) go func() { for v := range outCh { outs = append(outs, v) } close(outDone) }() ports := []*Port{ {File: os.Stdin, Chan: ClosedChan}, {File: pw, Chan: outCh}, {File: os.Stderr, Chan: BlackholeChan}, } ex = ev.eval(op, ports, src) close(outCh) <-outDone } pw.Close() <-bytesDone pr.Close() return outs, bytesOut, ex } func mustParseAndCompile(t *testing.T, ev *Evaler, src *Source) Op { n, err := parse.Parse(src.name, src.code) if err != nil { t.Fatalf("Parse(%q) error: %s", src.code, err) } op, err := ev.Compile(n, src) if err != nil { t.Fatalf("Compile(Parse(%q)) error: %s", src.code, err) } return op } func matchOut(want, got []types.Value) bool { if len(got) == 0 && len(want) == 0 { return true } return reflect.DeepEqual(got, want) } func matchErr(want, got error) bool { if got == nil { return want == nil } return want == errAny || reflect.DeepEqual(got.(*Exception).Cause, want) } // compareValues compares two slices, using equals for each element. func compareSlice(wantValues, gotValues []interface{}) error { if len(wantValues) != len(gotValues) { return fmt.Errorf("want %d values, got %d", len(wantValues), len(gotValues)) } for i, want := range wantValues { if !equals(want, gotValues[i]) { return fmt.Errorf("want [%d] = %s, got %s", i, want, gotValues[i]) } } return nil } // equals compares two values. It uses Eq if want is a Value instance, or // reflect.DeepEqual otherwise. func equals(a, b interface{}) bool { if aValue, ok := a.(types.Value); ok { return aValue.Equal(b) } return reflect.DeepEqual(a, b) } elvish-0.11+ds1/eval/types/000077500000000000000000000000001323000013700154705ustar00rootroot00000000000000elvish-0.11+ds1/eval/types/bool.go000066400000000000000000000011401323000013700167460ustar00rootroot00000000000000package types // Bool represents truthness. type Bool bool var _ Value = Bool(false) func (Bool) Kind() string { return "bool" } func (b Bool) Equal(rhs interface{}) bool { return b == rhs } func (b Bool) Hash() uint32 { if b { return 1 } return 0 } func (b Bool) Repr(int) string { if b { return "$true" } return "$false" } func (b Bool) Bool() bool { return bool(b) } // ToBool converts a Value to bool. When the Value type implements Bool(), it // is used. Otherwise it is considered true. func ToBool(v Value) bool { if b, ok := v.(Booler); ok { return b.Bool() } return true } elvish-0.11+ds1/eval/types/bool_test.go000066400000000000000000000010371323000013700200120ustar00rootroot00000000000000package types_test import ( "testing" "github.com/elves/elvish/eval" ) func TestBool(t *testing.T) { eval.RunTests(t, []eval.Test{ eval.NewTest("kind-of $true").WantOutStrings("bool"), eval.NewTest("eq $true $true").WantOutBools(true), eval.NewTest("eq $true true").WantOutBools(false), eval.NewTest("repr $true").WantBytesOutString("$true\n"), eval.NewTest("repr $false").WantBytesOutString("$false\n"), eval.NewTest("bool $true").WantOutBools(true), eval.NewTest("bool $false").WantOutBools(false), }, eval.NewEvaler) } elvish-0.11+ds1/eval/types/file.go000066400000000000000000000011151323000013700167340ustar00rootroot00000000000000package types import ( "fmt" "os" "github.com/elves/elvish/parse" "github.com/xiaq/persistent/hash" ) // File wraps a pointer to os.File. type File struct { Inner *os.File } var _ Value = File{} // NewFile creates a new File value. func NewFile(inner *os.File) File { return File{inner} } func (File) Kind() string { return "file" } func (f File) Equal(rhs interface{}) bool { return f == rhs } func (f File) Hash() uint32 { return hash.UIntPtr(f.Inner.Fd()) } func (f File) Repr(int) string { return fmt.Sprintf("", parse.Quote(f.Inner.Name()), f.Inner) } elvish-0.11+ds1/eval/types/list.go000066400000000000000000000063621323000013700170010ustar00rootroot00000000000000package types import ( "bytes" "encoding/json" "errors" "strconv" "strings" "github.com/xiaq/persistent/vector" ) // Error definitions. var ( ErrBadIndex = errors.New("bad index") ErrIndexOutOfRange = errors.New("index out of range") ErrAssocWithSlice = errors.New("assoc with slice not yet supported") ) // List is a list of Value's. type List struct { inner vector.Vector } // EmptyList is an empty list. var EmptyList = List{vector.Empty} // Make sure that List implements ListLike and Assocer at compile time. var ( _ ListLike = List{} _ Assocer = List{} ) // MakeList creates a new List from values. func MakeList(vs ...Value) List { vec := vector.Empty for _, v := range vs { vec = vec.Cons(v) } return List{vec} } // NewList creates a new List from an existing Vector. func NewList(vec vector.Vector) List { return List{vec} } func (List) Kind() string { return "list" } func (l List) Equal(rhs interface{}) bool { return eqListLike(l, rhs) } func (l List) Hash() uint32 { return hashListLike(l) } func (l List) Repr(indent int) string { var b ListReprBuilder b.Indent = indent for it := l.inner.Iterator(); it.HasElem(); it.Next() { v := it.Elem().(Value) b.WriteElem(v.Repr(indent + 1)) } return b.String() } func (l List) MarshalJSON() ([]byte, error) { var buf bytes.Buffer encoder := json.NewEncoder(&buf) buf.WriteByte('[') first := true for it := l.inner.Iterator(); it.HasElem(); it.Next() { if first { first = false } else { buf.WriteByte(',') } err := encoder.Encode(it.Elem()) if err != nil { return nil, err } } buf.WriteByte(']') return buf.Bytes(), nil } func (l List) Len() int { return l.inner.Len() } func (l List) Iterate(f func(Value) bool) { for it := l.inner.Iterator(); it.HasElem(); it.Next() { v := it.Elem().(Value) if !f(v) { break } } } func (l List) IndexOne(idx Value) Value { slice, i, j := ParseAndFixListIndex(ToString(idx), l.Len()) if slice { return List{l.inner.SubVector(i, j)} } return l.inner.Nth(i).(Value) } func (l List) Assoc(idx, v Value) Value { slice, i, _ := ParseAndFixListIndex(ToString(idx), l.Len()) if slice { throw(ErrAssocWithSlice) } return List{l.inner.AssocN(i, v)} } // ParseAndFixListIndex parses a list index and returns whether the index is a // slice and "real" (-1 becomes n-1) indicies. It throws errors when the index // is invalid or out of range. func ParseAndFixListIndex(s string, n int) (bool, int, int) { slice, i, j := parseListIndex(s, n) if i < 0 { i += n } if j < 0 { j += n } if i < 0 || i >= n || (slice && (j < 0 || j > n || i > j)) { throw(ErrIndexOutOfRange) } return slice, i, j } // ListIndex = Number | // Number ':' Number func parseListIndex(s string, n int) (slice bool, i int, j int) { atoi := func(a string) int { i, err := strconv.Atoi(a) if err != nil { if err.(*strconv.NumError).Err == strconv.ErrRange { throw(ErrIndexOutOfRange) } else { throw(ErrBadIndex) } } return i } colon := strings.IndexRune(s, ':') if colon == -1 { // A single number return false, atoi(s), 0 } if s[:colon] == "" { i = 0 } else { i = atoi(s[:colon]) } if s[colon+1:] == "" { j = n } else { j = atoi(s[colon+1:]) } // Two numbers return true, i, j } elvish-0.11+ds1/eval/types/list_test.go000066400000000000000000000044621323000013700200370ustar00rootroot00000000000000package types import ( "testing" "github.com/elves/elvish/util" ) var parseAndFixListIndexTests = []struct { name string // input expr string len int // output shouldPanic, isSlice bool begin, end int }{ { name: "stringIndex", expr: "a", len: 0, shouldPanic: true, }, { name: "floatIndex", expr: "1.0", len: 0, shouldPanic: true, }, { name: "emptyZeroIndex", expr: "0", len: 0, shouldPanic: true, }, { name: "emptyPosIndex", expr: "1", len: 0, shouldPanic: true, }, { name: "emptyNegIndex", expr: "-1", len: 0, shouldPanic: true, }, { name: "emptySliceAbbrevBoth", expr: ":", len: 0, shouldPanic: true, }, { name: "i<-n", expr: "-2", len: 1, shouldPanic: true, }, { name: "i=-n", expr: "-1", len: 1, begin: 0, end: 0, }, { name: "-nn", expr: "2", len: 1, shouldPanic: true, }, { name: "sliceAbbrevBoth", expr: ":", len: 1, isSlice: true, begin: 0, end: 1, }, { name: "sliceAbbrevBegin", expr: ":1", len: 1, isSlice: true, begin: 0, end: 1, }, { name: "sliceAbbrevEnd", expr: "0:", len: 1, isSlice: true, begin: 0, end: 1, }, { name: "sliceNegEnd", expr: "0:-1", len: 1, isSlice: true, begin: 0, end: 0, }, { name: "sliceBeginEqualEnd", expr: "1:1", len: 2, isSlice: true, begin: 1, end: 1, }, { name: "sliceBeginAboveEnd", expr: "1:0", len: 2, shouldPanic: true, }, } func TestParseAndFixListIndex(t *testing.T) { checkEqual := func(name, value string, want, got interface{}) { if want != got { t.Errorf("%s value: [%s] want: [%v] got: [%v]", name, value, want, got) } } for _, item := range parseAndFixListIndexTests { var ( isSlice bool begin, end int ) if err := util.PCall(func() { isSlice, begin, end = ParseAndFixListIndex(item.expr, item.len) }); err != nil { checkEqual(item.name, "shouldPanic", item.shouldPanic, err != nil) continue } checkEqual(item.name, "isSlice", item.isSlice, isSlice) checkEqual(item.name, "begin", item.begin, begin) checkEqual(item.name, "end", item.end, end) } } elvish-0.11+ds1/eval/types/listlike.go000066400000000000000000000022411323000013700176360ustar00rootroot00000000000000package types import ( "bytes" "strings" "github.com/xiaq/persistent/hash" ) type ListLike interface { Lener Iterator IndexOneer } func eqListLike(lhs ListLike, r interface{}) bool { rhs, ok := r.(ListLike) if !ok { return false } if lhs.Len() != rhs.Len() { return false } return true } func hashListLike(l ListLike) uint32 { h := hash.DJBInit l.Iterate(func(v Value) bool { h = hash.DJBCombine(h, v.Hash()) return true }) return h } // ListReprBuilder helps to build Repr of list-like Values. type ListReprBuilder struct { Indent int buf bytes.Buffer } func (b *ListReprBuilder) WriteElem(v string) { if b.buf.Len() == 0 { b.buf.WriteByte('[') } if b.Indent >= 0 { // Pretty printing. // // Add a newline and indent+1 spaces, so that the // starting & lines up with the first pair. b.buf.WriteString("\n" + strings.Repeat(" ", b.Indent+1)) } else if b.buf.Len() > 1 { b.buf.WriteByte(' ') } b.buf.WriteString(v) } func (b *ListReprBuilder) String() string { if b.buf.Len() == 0 { return "[]" } if b.Indent >= 0 { b.buf.WriteString("\n" + strings.Repeat(" ", b.Indent)) } b.buf.WriteByte(']') return b.buf.String() } elvish-0.11+ds1/eval/types/map.go000066400000000000000000000050261323000013700165770ustar00rootroot00000000000000package types import ( "encoding/json" "errors" "github.com/xiaq/persistent/hashmap" ) // Map is a map from string to Value. type Map struct { inner hashmap.HashMap } var _ MapLike = Map{} // EmptyMap is an empty Map. var EmptyMap = Map{hashmap.Empty} // NewMap creates a new Map from an inner HashMap. func NewMap(inner hashmap.HashMap) Map { return Map{inner} } // MakeMap converts a native Go map to Map. func MakeMap(m map[Value]Value) Map { inner := hashmap.Empty for k, v := range m { inner = inner.Assoc(k, v) } return NewMap(inner) } func (Map) Kind() string { return "map" } func (m Map) Equal(a interface{}) bool { return m == a || EqMapLike(m, a) } func (m Map) Hash() uint32 { return HashMapLike(m) } func (m Map) MarshalJSON() ([]byte, error) { // TODO(xiaq): Replace with a more efficient implementation. mm := map[string]Value{} for it := m.inner.Iterator(); it.HasElem(); it.Next() { k, v := it.Elem() mm[ToString(k.(Value))] = v.(Value) } return json.Marshal(mm) } func (m Map) Repr(indent int) string { var builder MapReprBuilder builder.Indent = indent for it := m.inner.Iterator(); it.HasElem(); it.Next() { k, v := it.Elem() builder.WritePair(k.(Value).Repr(indent+1), indent+2, v.(Value).Repr(indent+2)) } return builder.String() } func (m Map) Len() int { return m.inner.Len() } func (m Map) IndexOne(idx Value) Value { v, ok := m.inner.Get(idx) if !ok { throw(errors.New("no such key: " + idx.Repr(NoPretty))) } return v.(Value) } func (m Map) Assoc(k, v Value) Value { return Map{m.inner.Assoc(k, v)} } func (m Map) Dissoc(k Value) Value { return Map{m.inner.Without(k)} } func (m Map) IterateKey(f func(Value) bool) { for it := m.inner.Iterator(); it.HasElem(); it.Next() { k, _ := it.Elem() if !f(k.(Value)) { break } } } func (m Map) IteratePair(f func(Value, Value) bool) { for it := m.inner.Iterator(); it.HasElem(); it.Next() { k, v := it.Elem() if !f(k.(Value), v.(Value)) { break } } } func (m Map) HasKey(k Value) bool { _, ok := m.inner.Get(k) return ok } // MapReprBuilder helps building the Repr of a Map. It is also useful for // implementing other Map-like values. The zero value of a MapReprBuilder is // ready to use. type MapReprBuilder struct { ListReprBuilder } func (b *MapReprBuilder) WritePair(k string, indent int, v string) { if indent > 0 { b.WriteElem("&" + k + "=\t" + v) } else { b.WriteElem("&" + k + "=" + v) } } func (b *MapReprBuilder) String() string { s := b.ListReprBuilder.String() if s == "[]" { s = "[&]" } return s } elvish-0.11+ds1/eval/types/maplike.go000066400000000000000000000013001323000013700174330ustar00rootroot00000000000000package types import ( "github.com/xiaq/persistent/hash" ) type MapLike interface { Lener IndexOneer Assocer HasKeyer IterateKeyer IteratePairer } type HasKeyer interface { HasKey(k Value) bool } func EqMapLike(lhs MapLike, a interface{}) bool { rhs, ok := a.(MapLike) if !ok { return false } if lhs.Len() != rhs.Len() { return false } eq := true lhs.IteratePair(func(k, v Value) bool { if !v.Equal(rhs.IndexOne(k)) { eq = false return false } return true }) return eq } func HashMapLike(m MapLike) uint32 { h := hash.DJBInit m.IteratePair(func(k, v Value) bool { h = hash.DJBCombine(h, k.Hash()) h = hash.DJBCombine(h, v.Hash()) return true }) return h } elvish-0.11+ds1/eval/types/pipe.go000066400000000000000000000013131323000013700167520ustar00rootroot00000000000000package types import ( "fmt" "os" "github.com/xiaq/persistent/hash" ) // Pipe wraps a pair of pointers to os.File that are the two ends of the same // pipe. type Pipe struct { ReadEnd, WriteEnd *os.File } var _ Value = Pipe{} // NewPipe creates a new Pipe value. func NewPipe(r, w *os.File) Pipe { return Pipe{r, w} } func (Pipe) Kind() string { return "pipe" } func (p Pipe) Equal(rhs interface{}) bool { return p == rhs } func (p Pipe) Hash() uint32 { h := hash.DJBInit h = hash.DJBCombine(h, hash.UIntPtr(p.ReadEnd.Fd())) h = hash.DJBCombine(h, hash.UIntPtr(p.WriteEnd.Fd())) return h } func (p Pipe) Repr(int) string { return fmt.Sprintf("", p.ReadEnd.Fd(), p.WriteEnd.Fd()) } elvish-0.11+ds1/eval/types/rat.go000066400000000000000000000023031323000013700166030ustar00rootroot00000000000000package types import ( "errors" "fmt" "math/big" "github.com/xiaq/persistent/hash" ) var ErrOnlyStrOrRat = errors.New("only str or rat may be converted to rat") // Rat is a rational number. type Rat struct { b *big.Rat } var _ Value = Rat{} func (Rat) Kind() string { return "string" } func (r Rat) Equal(a interface{}) bool { if r == a { return true } r2, ok := a.(Rat) if !ok { return false } return r.b.Cmp(r2.b) == 0 } func (r Rat) Hash() uint32 { // TODO(xiaq): Use a more efficient implementation. return hash.String(r.String()) } func (r Rat) Repr(int) string { return "(rat " + r.String() + ")" } func (r Rat) String() string { if r.b.IsInt() { return r.b.Num().String() } return r.b.String() } // ToRat converts a Value to rat. A str can be converted to a rat if it can be // parsed. A rat is returned as-is. Other types of values cannot be converted. func ToRat(v Value) (Rat, error) { switch v := v.(type) { case Rat: return v, nil case String: r := big.Rat{} _, err := fmt.Sscanln(string(v), &r) if err != nil { return Rat{}, fmt.Errorf("%s cannot be parsed as rat", v.Repr(NoPretty)) } return Rat{&r}, nil default: return Rat{}, ErrOnlyStrOrRat } } elvish-0.11+ds1/eval/types/string.go000066400000000000000000000026021323000013700173250ustar00rootroot00000000000000package types import ( "errors" "unicode/utf8" "github.com/elves/elvish/parse" "github.com/xiaq/persistent/hash" ) // String is just a string. type String string var ( _ Value = String("") _ ListLike = String("") ) var ErrReplacementMustBeString = errors.New("replacement must be string") func (String) Kind() string { return "string" } func (s String) Repr(int) string { return parse.Quote(string(s)) } func (s String) Equal(rhs interface{}) bool { return s == rhs } func (s String) Hash() uint32 { return hash.String(string(s)) } func (s String) String() string { return string(s) } func (s String) Len() int { return len(string(s)) } func (s String) IndexOne(idx Value) Value { i, j := s.index(idx) return s[i:j] } func (s String) Assoc(idx, v Value) Value { i, j := s.index(idx) repl, ok := v.(String) if !ok { throw(ErrReplacementMustBeString) } return s[:i] + repl + s[j:] } func (s String) index(idx Value) (int, int) { slice, i, j := ParseAndFixListIndex(ToString(idx), len(s)) r, size := utf8.DecodeRuneInString(string(s[i:])) if r == utf8.RuneError { throw(ErrBadIndex) } if slice { if r, _ := utf8.DecodeLastRuneInString(string(s[:j])); r == utf8.RuneError { throw(ErrBadIndex) } return i, j } return i, i + size } func (s String) Iterate(f func(v Value) bool) { for _, r := range s { b := f(String(string(r))) if !b { break } } } elvish-0.11+ds1/eval/types/struct.go000066400000000000000000000064011323000013700173440ustar00rootroot00000000000000package types import ( "bytes" "encoding/json" "errors" "fmt" "github.com/elves/elvish/parse" ) var ( ErrIndexMustBeString = errors.New("index must be string") ) // Struct is like a Map with fixed keys. type Struct struct { descriptor *StructDescriptor fields []Value } var ( _ Value = (*Struct)(nil) _ MapLike = (*Struct)(nil) ) // NewStruct creates a new *Struct value. func NewStruct(descriptor *StructDescriptor, fields []Value) *Struct { return &Struct{descriptor, fields} } func (*Struct) Kind() string { return "map" } // Equal returns true if the rhs is MapLike and all pairs are equal. func (s *Struct) Equal(rhs interface{}) bool { return s == rhs || EqMapLike(s, rhs) } func (s *Struct) Hash() uint32 { return HashMapLike(s) } func (s *Struct) Repr(indent int) string { var builder MapReprBuilder builder.Indent = indent for i, name := range s.descriptor.fieldNames { builder.WritePair(parse.Quote(name), indent+2, s.fields[i].Repr(indent+2)) } return builder.String() } func (s *Struct) Len() int { return len(s.descriptor.fieldNames) } func (s *Struct) IndexOne(idx Value) Value { return s.fields[s.index(idx)] } func (s *Struct) Assoc(k, v Value) Value { i := s.index(k) fields := make([]Value, len(s.fields)) copy(fields, s.fields) fields[i] = v return &Struct{s.descriptor, fields} } func (s *Struct) IterateKey(f func(Value) bool) { for _, field := range s.descriptor.fieldNames { if !f(String(field)) { break } } } func (s *Struct) IteratePair(f func(Value, Value) bool) { for i, field := range s.descriptor.fieldNames { if !f(String(field), s.fields[i]) { break } } } func (s *Struct) HasKey(k Value) bool { index, ok := k.(String) if !ok { return false } _, ok = s.descriptor.fieldIndex[string(index)] return ok } func (s *Struct) index(idx Value) int { index, ok := idx.(String) if !ok { throw(ErrIndexMustBeString) } i, ok := s.descriptor.fieldIndex[string(index)] if !ok { throw(fmt.Errorf("no such field: %s", index.Repr(NoPretty))) } return i } // MarshalJSON encodes the Struct to a JSON Object. func (s *Struct) MarshalJSON() ([]byte, error) { var buf bytes.Buffer buf.WriteByte('{') for i, fieldName := range s.descriptor.fieldNames { if i > 0 { buf.WriteByte(',') } buf.Write(s.descriptor.jsonFieldNames[i]) buf.WriteByte(':') fieldJSON, err := json.Marshal(s.fields[i]) if err != nil { return nil, fmt.Errorf("cannot encode field %q: %v", fieldName, err) } buf.Write(fieldJSON) } buf.WriteByte('}') return buf.Bytes(), nil } // StructDescriptor contains information about the fields in a Struct. type StructDescriptor struct { fieldNames []string jsonFieldNames [][]byte fieldIndex map[string]int } // NewStructDescriptor creates a new struct descriptor from a list of field // names. func NewStructDescriptor(fields ...string) *StructDescriptor { fieldNames := append([]string(nil), fields...) jsonFieldNames := make([][]byte, len(fields)) fieldIndex := make(map[string]int) for i, name := range fieldNames { fieldIndex[name] = i jsonFieldName, err := json.Marshal(name) // json.Marshal should never fail on string. if err != nil { panic(err) } jsonFieldNames[i] = jsonFieldName } return &StructDescriptor{fieldNames, jsonFieldNames, fieldIndex} } elvish-0.11+ds1/eval/types/struct_test.go000066400000000000000000000024301323000013700204010ustar00rootroot00000000000000package types import ( "testing" ) var ( testStructDescriptor = NewStructDescriptor("foo", "bar") testStruct = NewStruct(testStructDescriptor, []Value{String("lorem"), String("ipsum")}) testStruct2 = NewStruct(testStructDescriptor, []Value{String("lorem"), String("dolor")}) ) func TestStructMethods(t *testing.T) { if l := testStruct.Len(); l != 2 { t.Errorf("testStruct.Len() = %d, want 2", l) } if foo := testStruct.IndexOne(String("foo")); foo != String("lorem") { t.Errorf(`testStruct.IndexOne("foo") = %q, want "lorem"`, foo) } if testStruct.Equal(testStruct2) { t.Errorf(`testStruct.Equal(testStruct2) => true, want false`) } if s2 := testStruct.Assoc(String("bar"), String("dolor")); !s2.Equal(testStruct2) { t.Errorf(`testStruct.Assoc(...) => %v, want %v`, s2, testStruct2) } wantRepr := "[&foo=lorem &bar=ipsum]" if gotRepr := testStruct.Repr(NoPretty); gotRepr != wantRepr { t.Errorf(`testStruct.Repr() => %q, want %q`, gotRepr, wantRepr) } wantJSON := `{"foo":"lorem","bar":"ipsum"}` gotJSONBytes, err := testStruct.MarshalJSON() gotJSON := string(gotJSONBytes) if err != nil { t.Errorf(`testStruct.MarshalJSON() => error %v`, err) } if wantJSON != gotJSON { t.Errorf(`testStruct.MarshalJSON() => %q, want %q`, gotJSON, wantJSON) } } elvish-0.11+ds1/eval/types/throw.go000066400000000000000000000004161323000013700171630ustar00rootroot00000000000000package types import ( "fmt" "github.com/elves/elvish/util" ) func throw(e error) { util.Throw(e) } func throwf(format string, args ...interface{}) { util.Throw(fmt.Errorf(format, args...)) } func maybeThrow(err error) { if err != nil { util.Throw(err) } } elvish-0.11+ds1/eval/types/types.go000066400000000000000000000107011323000013700171620ustar00rootroot00000000000000// Package types contains basic types for the Elvish runtime. package types import ( "github.com/elves/elvish/util" ) // Definitions for Value interfaces, some simple Value types and some common // Value helpers. // Value is an Elvish value. type Value interface { Kinder Equaler Hasher Reprer } // Kinder wraps the Kind method. type Kinder interface { Kind() string } // Reprer wraps the Repr method. type Reprer interface { // Repr returns a string that represents a Value. The string either be a // literal of that Value that is preferably deep-equal to it (like `[a b c]` // for a list), or a string enclosed in "<>" containing the kind and // identity of the Value(like ``). // // If indent is at least 0, it should be pretty-printed with the current // indentation level of indent; the indent of the first line has already // been written and shall not be written in Repr. The returned string // should never contain a trailing newline. Repr(indent int) string } // NoPretty can be passed to Repr to suppress pretty-printing. const NoPretty = util.MinInt // Equaler wraps the Equal method. type Equaler interface { // Equal compares the receiver to another value. Two equal values must have // the same hash code. Equal(other interface{}) bool } // Hasher wraps the Hash method. type Hasher interface { // Hash computes the hash code of the receiver. Hash() uint32 } // Booler wraps the Bool method. type Booler interface { // Bool computes the truth value of the receiver. Bool() bool } // Stringer wraps the String method. type Stringer interface { // Stringer converts the receiver to a string. String() string } // ToString converts a Value to string. When the Value type implements // String(), it is used. Otherwise Repr(NoPretty) is used. func ToString(v Value) string { if s, ok := v.(Stringer); ok { return s.String() } return v.Repr(NoPretty) } // Lener wraps the Len method. type Lener interface { // Len computes the length of the receiver. Len() int } // Iterator wraps the Iterate method. type Iterator interface { // Iterate calls the passed function with each value within the receiver. // The iteration is aborted if the function returns false. Iterate(func(v Value) bool) } // IteratorValue is an iterable Value. type IteratorValue interface { Iterator Value } func CollectFromIterator(it Iterator) []Value { var vs []Value if lener, ok := it.(Lener); ok { vs = make([]Value, 0, lener.Len()) } it.Iterate(func(v Value) bool { vs = append(vs, v) return true }) return vs } // IterateKeyer wraps the IterateKey method. type IterateKeyer interface { // IterateKey calls the passed function with each value within the receiver. // The iteration is aborted if the function returns false. IterateKey(func(k Value) bool) } // IteratePairer wraps the IteratePair method. type IteratePairer interface { // IteratePair calls the passed function with each key and value within the // receiver. The iteration is aborted if the function returns false. IteratePair(func(k, v Value) bool) } // Indexer wraps the Index method. type Indexer interface { // Index retrieves the values within the receiver at the specified indicies. Index(idx []Value) []Value } // IndexOneer wraps the IndexOne method. type IndexOneer interface { // Index retrieves one value from the receiver at the specified index. IndexOne(idx Value) Value } // GetIndexer adapts a Value to an Indexer if there is an adapter. func GetIndexer(v Value) (Indexer, bool) { if indexer, ok := v.(Indexer); ok { return indexer, true } if indexOneer, ok := v.(IndexOneer); ok { return IndexOneerIndexer{indexOneer}, true } return nil, false } // IndexOneerIndexer adapts an IndexOneer to an Indexer by calling all the // indicies on the IndexOner and collect the results. type IndexOneerIndexer struct { IndexOneer } func (ioi IndexOneerIndexer) Index(vs []Value) []Value { results := make([]Value, len(vs)) for i, v := range vs { results[i] = ioi.IndexOneer.IndexOne(v) } return results } // Assocer wraps the Assoc method. type Assocer interface { // Assoc returns a slightly modified version of the receiver with key k // associated with value v. Assoc(k, v Value) Value } // Dissocer is anything tha can return a slightly modified version of itself with // the specified key removed, as a new value. type Dissocer interface { // Dissoc returns a slightly modified version of the receiver with key k // dissociated with any value. Dissoc(k Value) Value } elvish-0.11+ds1/eval/types/types_test.go000066400000000000000000000011331323000013700202200ustar00rootroot00000000000000package types import ( "os" "testing" "github.com/elves/elvish/tt" "github.com/xiaq/persistent/hashmap" "github.com/xiaq/persistent/vector" ) var Args = tt.Args func kind(k Kinder) string { return k.Kind() } func TestKind(t *testing.T) { tt.Test(t, tt.Fn("kind", kind), tt.Table{ Args(Bool(true)).Rets("bool"), Args(String("")).Rets("string"), Args(NewList(vector.Empty)).Rets("list"), Args(NewMap(hashmap.Empty)).Rets("map"), Args(NewStruct(NewStructDescriptor(), nil)).Rets("map"), Args(NewFile(os.Stdin)).Rets("file"), Args(NewPipe(os.Stdin, os.Stdout)).Rets("pipe"), }) } elvish-0.11+ds1/eval/unwrap.go000066400000000000000000000050331323000013700161700ustar00rootroot00000000000000package eval import ( "fmt" "strconv" "github.com/elves/elvish/eval/types" ) // Unwrappers are helper types for "unwrapping" values, the process for // asserting certain properties of values and throwing exceptions when such // properties are not satisfied. type unwrapperInner struct { // ctx is the evaluation context. ctx *Frame // description describes what is being unwrapped. It is used in error // messages. description string // begin and end contains positions in the source code to point to when // error occurs. begin, end int // values contain the Value's to unwrap. values []types.Value } func (u *unwrapperInner) error(want, gotfmt string, gotargs ...interface{}) { got := fmt.Sprintf(gotfmt, gotargs...) u.ctx.errorpf(u.begin, u.end, "%s must be %s; got %s", u.description, want, got) } // ValuesUnwrapper unwraps []Value. type ValuesUnwrapper struct{ *unwrapperInner } // Unwrap creates an Unwrapper. func (ctx *Frame) Unwrap(desc string, begin, end int, vs []types.Value) ValuesUnwrapper { return ValuesUnwrapper{&unwrapperInner{ctx, desc, begin, end, vs}} } // ExecAndUnwrap executes a ValuesOp and creates an Unwrapper for the obtained // values. func (ctx *Frame) ExecAndUnwrap(desc string, op ValuesOp) ValuesUnwrapper { return ctx.Unwrap(desc, op.Begin, op.End, op.Exec(ctx)) } // One unwraps the value to be exactly one value. func (u ValuesUnwrapper) One() ValueUnwrapper { if len(u.values) != 1 { u.error("a single value", "%d values", len(u.values)) } return ValueUnwrapper{u.unwrapperInner} } // ValueUnwrapper unwraps one Value. type ValueUnwrapper struct{ *unwrapperInner } func (u ValueUnwrapper) Any() types.Value { return u.values[0] } func (u ValueUnwrapper) String() types.String { s, ok := u.values[0].(types.String) if !ok { u.error("string", "%s", u.values[0].Kind()) } return s } func (u ValueUnwrapper) Int() int { s := u.String() i, err := strconv.Atoi(string(s)) if err != nil { u.error("integer", "%s", s) } return i } func (u ValueUnwrapper) NonNegativeInt() int { i := u.Int() if i < 0 { u.error("non-negative int", "%d", i) } return i } func (u ValueUnwrapper) FdOrClose() int { s := string(u.String()) if s == "-" { return -1 } return u.NonNegativeInt() } func (u ValueUnwrapper) Callable() Callable { c, ok := u.values[0].(Callable) if !ok { u.error("callable", "%s", u.values[0].Kind()) } return c } func (u ValueUnwrapper) Iterable() types.Iterator { it, ok := u.values[0].(types.Iterator) if !ok { u.error("iterable", "%s", u.values[0].Kind()) } return it } elvish-0.11+ds1/eval/util.go000066400000000000000000000035001323000013700156260ustar00rootroot00000000000000package eval import ( "errors" "fmt" "os" "strings" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" "github.com/elves/elvish/util" ) func throw(e error) { util.Throw(e) } func throwf(format string, args ...interface{}) { util.Throw(fmt.Errorf(format, args...)) } func maybeThrow(err error) { if err != nil { util.Throw(err) } } func mustGetHome(uname string) string { dir, err := util.GetHome(uname) if err != nil { throw(err) } return dir } func ParseVariable(text string) (explode bool, ns string, name string) { explodePart, qname := ParseVariableSplice(text) nsPart, name := ParseVariableQName(qname) ns = nsPart if len(ns) > 0 { ns = ns[:len(ns)-1] } return explodePart != "", ns, name } func ParseVariableSplice(text string) (explode, qname string) { if strings.HasPrefix(text, "@") { return "@", text[1:] } return "", text } func ParseVariableQName(qname string) (ns, name string) { i := strings.LastIndexByte(qname, ':') if i == -1 { return "", qname } return qname[:i+1], qname[i+1:] } func MakeVariableName(explode bool, ns string, name string) string { prefix := "" if explode { prefix = "@" } if ns != "" { prefix += ns + ":" } return prefix + name } func makeFlag(m parse.RedirMode) int { switch m { case parse.Read: return os.O_RDONLY case parse.Write: return os.O_WRONLY | os.O_CREATE | os.O_TRUNC case parse.ReadWrite: return os.O_RDWR | os.O_CREATE case parse.Append: return os.O_WRONLY | os.O_CREATE | os.O_APPEND default: return -1 } } var ( ErrNoArgAccepted = errors.New("no argument accepted") ErrNoOptAccepted = errors.New("no option accepted") ) func TakeNoArg(args []types.Value) { if len(args) > 0 { throw(ErrNoArgAccepted) } } func TakeNoOpt(opts map[string]types.Value) { if len(opts) > 0 { throw(ErrNoOptAccepted) } } elvish-0.11+ds1/eval/validator.go000066400000000000000000000006151323000013700166420ustar00rootroot00000000000000package eval import ( "errors" "github.com/elves/elvish/eval/types" ) var ( errShouldBeFn = errors.New("should be function") errShouldBeNs = errors.New("should be ns") ) func ShouldBeFn(v types.Value) error { if _, ok := v.(Callable); !ok { return errShouldBeFn } return nil } func ShouldBeNs(v types.Value) error { if _, ok := v.(Ns); !ok { return errShouldBeNs } return nil } elvish-0.11+ds1/eval/value.go000066400000000000000000000031421323000013700157670ustar00rootroot00000000000000package eval import ( "fmt" "github.com/elves/elvish/eval/types" "github.com/xiaq/persistent/hashmap" ) func mustIndexer(v types.Value, ec *Frame) types.Indexer { indexer, ok := types.GetIndexer(v) if !ok { throw(fmt.Errorf("a %s is not indexable", v.Kind())) } return indexer } // Callable wraps the Call method. type Callable interface { // Call calls the receiver in a Frame with arguments and options. Call(ec *Frame, args []types.Value, opts map[string]types.Value) } var ( // NoArgs is an empty argument list. It can be used as an argument to Call. NoArgs = []types.Value{} // NoOpts is an empty option map. It can be used as an argument to Call. NoOpts = map[string]types.Value{} ) // Fn is a callable value. type Fn interface { types.Value Callable } // FromJSONInterface converts a interface{} that results from json.Unmarshal to // a Value. func FromJSONInterface(v interface{}) types.Value { if v == nil { // TODO Use a more appropriate type return types.String("") } switch v.(type) { case bool: return types.Bool(v.(bool)) case float64, string: // TODO Use a numeric type for float64 return types.String(fmt.Sprint(v)) case []interface{}: a := v.([]interface{}) vs := make([]types.Value, len(a)) for i, v := range a { vs[i] = FromJSONInterface(v) } return types.MakeList(vs...) case map[string]interface{}: m := v.(map[string]interface{}) mv := hashmap.Empty for k, v := range m { mv = mv.Assoc(types.String(k), FromJSONInterface(v)) } return types.NewMap(mv) default: throw(fmt.Errorf("unexpected json type: %T", v)) return nil // not reached } } elvish-0.11+ds1/eval/value_test.go000066400000000000000000000034321323000013700170300ustar00rootroot00000000000000package eval import ( "errors" "reflect" "testing" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/glob" ) var reprTests = []struct { v types.Value want string }{ {types.String("233"), "233"}, {types.String("a\nb"), `"a\nb"`}, {types.String("foo bar"), "'foo bar'"}, {types.String("a\x00b"), `"a\x00b"`}, {types.Bool(true), "$true"}, {types.Bool(false), "$false"}, {&Exception{nil, nil}, "$ok"}, {&Exception{errors.New("foo bar"), nil}, "?(fail 'foo bar')"}, {&Exception{ PipelineError{[]*Exception{{nil, nil}, {errors.New("lorem"), nil}}}, nil}, "?(multi-error $ok ?(fail lorem))"}, {&Exception{Return, nil}, "?(return)"}, {types.EmptyList, "[]"}, {types.MakeList(types.String("bash"), types.Bool(false)), "[bash $false]"}, {types.MakeMap(map[types.Value]types.Value{}), "[&]"}, {types.MakeMap(map[types.Value]types.Value{&Exception{nil, nil}: types.String("elvish")}), "[&$ok=elvish]"}, // TODO: test maps of more elements } func TestRepr(t *testing.T) { for _, test := range reprTests { repr := test.v.Repr(types.NoPretty) if repr != test.want { t.Errorf("Repr = %s, want %s", repr, test.want) } } } var stringToSegmentsTests = []struct { s string want []glob.Segment }{ {"", []glob.Segment{}}, {"a", []glob.Segment{glob.Literal{"a"}}}, {"/a", []glob.Segment{glob.Slash{}, glob.Literal{"a"}}}, {"a/", []glob.Segment{glob.Literal{"a"}, glob.Slash{}}}, {"/a/", []glob.Segment{glob.Slash{}, glob.Literal{"a"}, glob.Slash{}}}, {"a//b", []glob.Segment{glob.Literal{"a"}, glob.Slash{}, glob.Literal{"b"}}}, } func TestStringToSegments(t *testing.T) { for _, tc := range stringToSegmentsTests { segs := stringToSegments(tc.s) if !reflect.DeepEqual(segs, tc.want) { t.Errorf("stringToSegments(%q) => %v, want %v", tc.s, segs, tc.want) } } } elvish-0.11+ds1/eval/vartypes/000077500000000000000000000000001323000013700162015ustar00rootroot00000000000000elvish-0.11+ds1/eval/vartypes/blackhole.go000066400000000000000000000011601323000013700204520ustar00rootroot00000000000000package vartypes import "github.com/elves/elvish/eval/types" type blackhole struct{} func (blackhole) Set(types.Value) error { return nil } func (blackhole) Get() types.Value { // TODO: Return a special placeholder value. return types.String("") } // NewBlackhole returns a blackhole variable. Assignments to a blackhole // variable will be discarded, and getting a blackhole variable always returns // an empty string. func NewBlackhole() Variable { return blackhole{} } // IsBlackhole returns whether the variable is a blackhole variable. func IsBlackhole(v Variable) bool { _, ok := v.(blackhole) return ok } elvish-0.11+ds1/eval/vartypes/bool.go000066400000000000000000000006561323000013700174720ustar00rootroot00000000000000package vartypes import ( "errors" "github.com/elves/elvish/eval/types" ) var errMustBeBool = errors.New("must be bool") type boolVar struct { ptr *bool } func NewBool(ptr *bool) Variable { return boolVar{ptr} } func (bv boolVar) Get() types.Value { return types.Bool(*bv.ptr) } func (bv boolVar) Set(v types.Value) error { if b, ok := v.(types.Bool); ok { *bv.ptr = bool(b) return nil } return errMustBeBool } elvish-0.11+ds1/eval/vartypes/callback.go000066400000000000000000000014021323000013700202610ustar00rootroot00000000000000package vartypes import "github.com/elves/elvish/eval/types" type callback struct { set func(types.Value) error get func() types.Value } // NewCallback makes a variable from a set callback and a get callback. func NewCallback(set func(types.Value) error, get func() types.Value) Variable { return &callback{set, get} } func (cv *callback) Set(val types.Value) error { return cv.set(val) } func (cv *callback) Get() types.Value { return cv.get() } type roCallback func() types.Value // NewRoCallback makes a read-only variable from a get callback. func NewRoCallback(get func() types.Value) Variable { return roCallback(get) } func (cv roCallback) Set(types.Value) error { return errRoCannotBeSet } func (cv roCallback) Get() types.Value { return cv() } elvish-0.11+ds1/eval/vartypes/del_element.go000066400000000000000000000021661323000013700210120ustar00rootroot00000000000000package vartypes import "github.com/elves/elvish/eval/types" // DelElement deletes an element. It uses a similar process to MakeElement, // except that the last level of container needs to be a Dissocer instead of an // Assocer. func DelElement(variable Variable, indicies []types.Value) error { // In "del a[0][1][2]", // // indicies: 0 1 2 // assocers: $a $a[0] // dissocer: $a[0][1] assocers := make([]types.Assocer, len(indicies)-1) container := variable.Get() for i, index := range indicies[:len(indicies)-1] { indexer, ok := container.(types.IndexOneer) if !ok { return elemErr{i, "value does not support indexing"} } assocer, ok := container.(types.Assocer) if !ok { return elemErr{i, "value does not support indexing for setting"} } assocers[i] = assocer container = indexer.IndexOne(index) } dissocer, ok := container.(types.Dissocer) if !ok { return elemErr{len(indicies), "value does not support element removal"} } v := dissocer.Dissoc(indicies[len(indicies)-1]) for i := len(assocers) - 1; i >= 0; i-- { v = assocers[i].Assoc(indicies[i], v) } return variable.Set(v) } elvish-0.11+ds1/eval/vartypes/element.go000066400000000000000000000066641323000013700201750ustar00rootroot00000000000000package vartypes import ( "github.com/elves/elvish/eval/types" ) type elem struct { variable Variable assocers []types.Assocer indices []types.Value setValue types.Value } func (ev *elem) Set(v0 types.Value) error { v := v0 // Evaluate the actual new value from inside out. See comments in // MakeElement for how element assignment works. for i := len(ev.assocers) - 1; i >= 0; i-- { v = ev.assocers[i].Assoc(ev.indices[i], v) } err := ev.variable.Set(v) // XXX(xiaq): Remember the set value for use in Get. ev.setValue = v0 return err } func (ev *elem) Get() types.Value { // XXX(xiaq): This is only called from fixNilVariables. We don't want to // waste time accessing the variable, so we simply return the value that was // set. return ev.setValue } // NewElement returns an ephemeral variable used for assigning variable element. func NewElement(v Variable, a []types.Assocer, i []types.Value) Variable { return &elem{v, a, i, types.String("")} } // MakeElement returns a variable, that when set, simulates the mutation of an // element. func MakeElement(v Variable, indicies []types.Value) (Variable, error) { // Assignment of indexed variables actually assignes the variable, with // the right hand being a nested series of Assocs. As the simplest // example, `a[0] = x` is equivalent to `a = (assoc $a 0 x)`. A more // complex example is that `a[0][1][2] = x` is equivalent to // `a = (assoc $a 0 (assoc $a[0] 1 (assoc $a[0][1] 2 x)))`. // Note that in each assoc form, the first two arguments can be // determined now, while the last argument is only known when the // right-hand-side is known. So here we evaluate the first two arguments // of each assoc form and put them in two slices, assocers and indicies. // In the previous example, the two slices will contain: // // assocers: $a $a[0] $a[0][1] // indicies: 0 1 2 // // When the right-hand side of the assignment becomes available, the new // value for $a is evaluated by doing Assoc from inside out. assocers := make([]types.Assocer, len(indicies)) varValue, ok := v.Get().(indexOneAssocer) if !ok { return nil, elemErr{0, "cannot be indexed for setting"} } assocers[0] = varValue for i, index := range indicies[:len(indicies)-1] { lastAssocer, ok := assocers[i].(types.IndexOneer) if !ok { // This cannot occur when i==0, since varValue as already // asserted to be an IndexOnner. return nil, elemErr{i, "cannot be indexed"} } assocer, ok := lastAssocer.IndexOne(index).(types.Assocer) if !ok { return nil, elemErr{i + 1, "cannot be indexed for setting"} } assocers[i+1] = assocer } return NewElement(v, assocers, indicies), nil } // indexOneAssocer combines IndexOneer and Assocer. type indexOneAssocer interface { types.IndexOneer types.Assocer } type elemErr struct { level int msg string } func (err elemErr) Error() string { return err.msg } // GetHeadOfElement gets the underlying head variable of an element variable, or // nil if the argument is not an element variable. func GetHeadOfElement(v Variable) Variable { if ev, ok := v.(*elem); ok { return ev.variable } return nil } // GetElementErrorLevel returns the level of an error returned by MakeElement or // DelElement. Level 0 represents that the error is about the variable itself. // If the argument was not returned from MakeVariable, -1 is returned. func GetElementErrorLevel(err error) int { if err, ok := err.(elemErr); ok { return err.level } return -1 } elvish-0.11+ds1/eval/vartypes/env.go000066400000000000000000000011651323000013700173230ustar00rootroot00000000000000package vartypes import ( "errors" "os" "github.com/elves/elvish/eval/types" ) var errEnvMustBeString = errors.New("environment variable can only be set string values") // envVariable represents an environment variable. type envVariable struct { name string } func (ev envVariable) Set(val types.Value) error { if s, ok := val.(types.String); ok { os.Setenv(ev.name, string(s)) return nil } return errEnvMustBeString } func (ev envVariable) Get() types.Value { return types.String(os.Getenv(ev.name)) } // NewEnv returns an environment variable. func NewEnv(name string) Variable { return envVariable{name} } elvish-0.11+ds1/eval/vartypes/number.go000066400000000000000000000010541323000013700200200ustar00rootroot00000000000000package vartypes import ( "errors" "strconv" "github.com/elves/elvish/eval/types" ) var errMustBeNumber = errors.New("must be number") type number struct { ptr *float64 } func NewNumber(ptr *float64) Variable { return number{ptr} } func (nv number) Get() types.Value { return types.String(strconv.FormatFloat(*nv.ptr, 'E', -1, 64)) } func (nv number) Set(v types.Value) error { if s, ok := v.(types.String); ok { if num, err := strconv.ParseFloat(string(s), 64); err == nil { *nv.ptr = num return nil } } return errMustBeNumber } elvish-0.11+ds1/eval/vartypes/ptr.go000066400000000000000000000015501323000013700173360ustar00rootroot00000000000000package vartypes import "github.com/elves/elvish/eval/types" type ptr struct { valuePtr *types.Value } func (pv ptr) Set(val types.Value) error { *pv.valuePtr = val return nil } func (pv ptr) Get() types.Value { return *pv.valuePtr } func NewPtr(v types.Value) Variable { return ptr{&v} } type validatedPtr struct { valuePtr *types.Value validator func(types.Value) error } type invalidValueError struct { inner error } func (err invalidValueError) Error() string { return "invalid value: " + err.inner.Error() } func NewValidatedPtr(v types.Value, vld func(types.Value) error) Variable { return validatedPtr{&v, vld} } func (iv validatedPtr) Set(val types.Value) error { if err := iv.validator(val); err != nil { return invalidValueError{err} } *iv.valuePtr = val return nil } func (iv validatedPtr) Get() types.Value { return *iv.valuePtr } elvish-0.11+ds1/eval/vartypes/ro.go000066400000000000000000000005561323000013700171560ustar00rootroot00000000000000package vartypes import ( "errors" "github.com/elves/elvish/eval/types" ) var errRoCannotBeSet = errors.New("read-only variable; cannot be set") type ro struct { value types.Value } func NewRo(v types.Value) Variable { return ro{v} } func (rv ro) Set(val types.Value) error { return errRoCannotBeSet } func (rv ro) Get() types.Value { return rv.value } elvish-0.11+ds1/eval/vartypes/string.go000066400000000000000000000011461323000013700200400ustar00rootroot00000000000000package vartypes import ( "errors" "github.com/elves/elvish/eval/types" ) var errMustBeString = errors.New("must be string") type stringVar struct { ptr *string } // NewString creates a variable from a string pointer. The Variable can only be // set to a String value, and modifications are reflected in the passed string. func NewString(ps *string) Variable { return stringVar{ps} } func (sv stringVar) Get() types.Value { return types.String(*sv.ptr) } func (sv stringVar) Set(v types.Value) error { if s, ok := v.(types.String); ok { *sv.ptr = string(s) return nil } return errMustBeString } elvish-0.11+ds1/eval/vartypes/validator.go000066400000000000000000000015061323000013700205170ustar00rootroot00000000000000package vartypes import ( "errors" "strconv" "github.com/elves/elvish/eval/types" ) var ( errShouldBeList = errors.New("should be list") errShouldBeMap = errors.New("should be map") errShouldBeBool = errors.New("should be bool") errShouldBeNumber = errors.New("should be number") ) func ShouldBeList(v types.Value) error { if _, ok := v.(types.List); !ok { return errShouldBeList } return nil } func ShouldBeMap(v types.Value) error { if _, ok := v.(types.Map); !ok { return errShouldBeMap } return nil } func ShouldBeBool(v types.Value) error { if _, ok := v.(types.Bool); !ok { return errShouldBeBool } return nil } func ShouldBeNumber(v types.Value) error { if _, ok := v.(types.String); !ok { return errShouldBeNumber } _, err := strconv.ParseFloat(string(v.(types.String)), 64) return err } elvish-0.11+ds1/eval/vartypes/variable_test.go000066400000000000000000000043051323000013700213560ustar00rootroot00000000000000package vartypes import ( "os" "testing" "github.com/elves/elvish/eval/types" ) func TestPtrVariable(t *testing.T) { v := NewPtr(types.Bool(true)) if v.Get() != types.Bool(true) { t.Errorf("PtrVariable.Get doesn't return initial value") } if v.Set(types.String("233")) != nil { t.Errorf("PtrVariable.Set errors") } if v.Get() != types.String("233") { t.Errorf("PtrVariable.Get doesn't return altered value") } } func TestValidatedPtrVariable(t *testing.T) { v := NewValidatedPtr(types.Bool(true), ShouldBeBool) if v.Set(types.String("233")) == nil { t.Errorf("ValidatedPtrVariable.Set doesn't error when setting incompatible value") } } func TestRoVariable(t *testing.T) { v := NewRo(types.String("haha")) if v.Get() != types.String("haha") { t.Errorf("RoVariable.Get doesn't return initial value") } if v.Set(types.String("lala")) == nil { t.Errorf("RoVariable.Set doesn't error") } } func TestCbVariable(t *testing.T) { getCalled := false get := func() types.Value { getCalled = true return types.String("cb") } var setCalledWith types.Value set := func(v types.Value) error { setCalledWith = v return nil } v := NewCallback(set, get) if v.Get() != types.String("cb") { t.Errorf("cbVariable doesn't return value from callback") } if !getCalled { t.Errorf("cbVariable doesn't call callback") } v.Set(types.String("setting")) if setCalledWith != types.String("setting") { t.Errorf("cbVariable.Set doesn't call setter with value") } } func TestRoCbVariable(t *testing.T) { getCalled := false get := func() types.Value { getCalled = true return types.String("cb") } v := NewRoCallback(get) if v.Get() != types.String("cb") { t.Errorf("roCbVariable doesn't return value from callback") } if !getCalled { t.Errorf("roCbVariable doesn't call callback") } if v.Set(types.String("lala")) == nil { t.Errorf("roCbVariable.Set doesn't error") } } func TestEnvVariable(t *testing.T) { name := "elvish_test" v := envVariable{name} os.Setenv(name, "foo") if v.Get() != types.String("foo") { t.Errorf("envVariable.Get doesn't return env value") } v.Set(types.String("bar")) if os.Getenv(name) != "bar" { t.Errorf("envVariable.Set doesn't alter env value") } } elvish-0.11+ds1/eval/vartypes/vartypes.go000066400000000000000000000004041323000013700204030ustar00rootroot00000000000000// Package vartypes contains basic types for manipulating Elvish variables. package vartypes import ( "github.com/elves/elvish/eval/types" ) // Variable represents an Elvish variable. type Variable interface { Set(v types.Value) error Get() types.Value } elvish-0.11+ds1/getopt/000077500000000000000000000000001323000013700146775ustar00rootroot00000000000000elvish-0.11+ds1/getopt/getopt.go000066400000000000000000000210221323000013700165250ustar00rootroot00000000000000// Package getopt implements a command-line argument parser. // // It tries to cover all common styles of option syntaxes, and provides context // information when given a partial input. It is mainly useful for writing // completion engines and wrapper programs. // // If you are looking for an option parser for your go programm, consider using // the flag package in the standard library instead. package getopt //go:generate stringer -type=Config,HasArg,ContextType -output=string.go import "strings" // Getopt specifies the syntax of command-line arguments. type Getopt struct { Options []*Option Config Config } // Config configurates the parsing behavior. type Config uint const ( // DoubleDashTerminatesOptions indicates that all elements after an argument // "--" are treated as arguments. DoubleDashTerminatesOptions Config = 1 << iota // FirstArgTerminatesOptions indicates that all elements after the first // argument are treated as arguments. FirstArgTerminatesOptions // LongOnly indicates that long options may be started by either one or two // dashes, and short options are not allowed. Should replicate the behavior // of getopt_long_only and the // flag package of the Go standard library. LongOnly // GNUGetoptLong is a configuration that should replicate the behavior of // GNU getopt_long. GNUGetoptLong = DoubleDashTerminatesOptions // POSIXGetopt is a configuration that should replicate the behavior of // POSIX getopt. POSIXGetopt = DoubleDashTerminatesOptions | FirstArgTerminatesOptions ) // HasAll tests whether a configuration has all specified flags set. func (conf Config) HasAll(flags Config) bool { return (conf & flags) == flags } // Option is a command-line option. type Option struct { // Short option. Set to 0 for long-only. Short rune // Long option. Set to "" for short-only. Long string // Whether the option takes an argument, and whether it is required. HasArg HasArg } // HasArg indicates whether an option takes an argument, and whether it is // required. type HasArg uint const ( // NoArgument indicates that an option takes no argument. NoArgument HasArg = iota // RequiredArgument indicates that an option must take an argument. The // argument can come either directly after a short option (-oarg), after a // long option followed by an equal sign (--long=arg), or as a subsequent // argument after the option (-o arg, --long arg). RequiredArgument // OptionalArgument indicates that an option takes an optional argument. // The argument can come either directly after a short option (-oarg) or // after a long option followed by an equal sign (--long=arg). OptionalArgument ) // ParsedOption represents a parsed option. type ParsedOption struct { Option *Option Long bool Argument string } // Context indicates what may come after the supplied argument list. type Context struct { // The nature of the context. Type ContextType // Current option, with a likely incomplete Argument. Non-nil when Type is // OptionArgument. Option *ParsedOption // Current partial long option name or argument. Non-empty when Type is // LongOption or Argument. Text string } // ContextType encodes what may be appended to the last element of the argument // list. type ContextType uint const ( // NewOptionOrArgument indicates that the last element may be either a new // option or a new argument. Returned when it is an empty string. NewOptionOrArgument ContextType = iota // NewOption indicates that the last element must be new option, short or // long. Returned when it is "-". NewOption // NewLongOption indicates that the last element must be a new long option. // Returned when it is "--". NewLongOption // LongOption indicates that the last element is a long option, but not its // argument. The partial name of the long option is stored in Context.Text. LongOption // ChainShortOption indicates that a new short option may be chained. // Returned when the last element consists of a chain of options that take // no arguments. ChainShortOption // OptionArgument indicates that the last element list must be an argument // to an option. The option in question is stored in Context.Option. OptionArgument // Argument indicates that the last element is an argument. The partial // argument is stored in Context.Text. Argument ) func (g *Getopt) findShort(r rune) *Option { for _, opt := range g.Options { if r == opt.Short { return opt } } return nil } // parseShort parse short options, without the leading dash. It returns the // parsed options and whether an argument is still to be seen. func (g *Getopt) parseShort(s string) ([]*ParsedOption, bool) { var opts []*ParsedOption var needArg bool for i, r := range s { opt := g.findShort(r) if opt != nil { if opt.HasArg == NoArgument { opts = append(opts, &ParsedOption{opt, false, ""}) continue } else { parsed := &ParsedOption{opt, false, s[i+len(string(r)):]} opts = append(opts, parsed) needArg = parsed.Argument == "" && opt.HasArg == RequiredArgument break } } // Unknown option, treat as taking an optional argument parsed := &ParsedOption{ &Option{r, "", OptionalArgument}, false, s[i+len(string(r)):]} opts = append(opts, parsed) break } return opts, needArg } // parseLong parse a long option, without the leading dashes. It returns the // parsed option and whether an argument is still to be seen. func (g *Getopt) parseLong(s string) (*ParsedOption, bool) { eq := strings.IndexRune(s, '=') for _, opt := range g.Options { if s == opt.Long { return &ParsedOption{opt, true, ""}, opt.HasArg == RequiredArgument } else if eq != -1 && s[:eq] == opt.Long { return &ParsedOption{opt, true, s[eq+1:]}, false } } // Unknown option, treat as taking an optional argument if eq == -1 { return &ParsedOption{&Option{0, s, OptionalArgument}, true, ""}, false } return &ParsedOption{&Option{0, s[:eq], OptionalArgument}, true, s[eq+1:]}, false } // Parse parses an argument list. func (g *Getopt) Parse(elems []string) ([]*ParsedOption, []string, *Context) { var ( opts []*ParsedOption args []string // Non-nil only when the last element was an option with required // argument, but the argument has not been seen. opt *ParsedOption // True if an option terminator has been seen. The criteria of option // terminators is determined by the configuration. noopt bool ) var elem string hasPrefix := func(p string) bool { return strings.HasPrefix(elem, p) } for _, elem = range elems[:len(elems)-1] { if opt != nil { opt.Argument = elem opts = append(opts, opt) opt = nil } else if noopt { args = append(args, elem) } else if g.Config.HasAll(DoubleDashTerminatesOptions) && elem == "--" { noopt = true } else if hasPrefix("--") { newopt, needArg := g.parseLong(elem[2:]) if needArg { opt = newopt } else { opts = append(opts, newopt) } } else if hasPrefix("-") { if g.Config.HasAll(LongOnly) { newopt, needArg := g.parseLong(elem[1:]) if needArg { opt = newopt } else { opts = append(opts, newopt) } } else { newopts, needArg := g.parseShort(elem[1:]) if needArg { opts = append(opts, newopts[:len(newopts)-1]...) opt = newopts[len(newopts)-1] } else { opts = append(opts, newopts...) } } } else { args = append(args, elem) if g.Config.HasAll(FirstArgTerminatesOptions) { noopt = true } } } elem = elems[len(elems)-1] ctx := &Context{} if opt != nil { opt.Argument = elem ctx.Type, ctx.Option = OptionArgument, opt } else if noopt { ctx.Type, ctx.Text = Argument, elem } else if elem == "" { ctx.Type = NewOptionOrArgument } else if elem == "-" { ctx.Type = NewOption } else if elem == "--" { ctx.Type = NewLongOption } else if hasPrefix("--") { if strings.IndexRune(elem, '=') == -1 { ctx.Type, ctx.Text = LongOption, elem[2:] } else { newopt, _ := g.parseLong(elem[2:]) ctx.Type, ctx.Option = OptionArgument, newopt } } else if hasPrefix("-") { if g.Config.HasAll(LongOnly) { if strings.IndexRune(elem, '=') == -1 { ctx.Type, ctx.Text = LongOption, elem[1:] } else { newopt, _ := g.parseLong(elem[1:]) ctx.Type, ctx.Option = OptionArgument, newopt } } else { newopts, _ := g.parseShort(elem[1:]) if newopts[len(newopts)-1].Option.HasArg == NoArgument { opts = append(opts, newopts...) ctx.Type = ChainShortOption } else { opts = append(opts, newopts[:len(newopts)-1]...) ctx.Type, ctx.Option = OptionArgument, newopts[len(newopts)-1] } } } else { ctx.Type, ctx.Text = Argument, elem } return opts, args, ctx } elvish-0.11+ds1/getopt/getopt_test.go000066400000000000000000000124771323000013700176020ustar00rootroot00000000000000package getopt import ( "reflect" "testing" ) var options = []*Option{ {'a', "all", NoArgument}, {'o', "option", RequiredArgument}, {'n', "number", OptionalArgument}, } var cases = []struct { config Config elems []string wantOpts []*ParsedOption wantArgs []string wantCtx *Context }{ // NoArgument, short option. {0, []string{"-a", ""}, []*ParsedOption{{options[0], false, ""}}, nil, &Context{Type: NewOptionOrArgument}}, // NoArgument, long option. {0, []string{"--all", ""}, []*ParsedOption{{options[0], true, ""}}, nil, &Context{Type: NewOptionOrArgument}}, // RequiredArgument, argument following the option directly {0, []string{"-oname=elvish", ""}, []*ParsedOption{{options[1], false, "name=elvish"}}, nil, &Context{Type: NewOptionOrArgument}}, // RequiredArgument, argument in next element {0, []string{"-o", "name=elvish", ""}, []*ParsedOption{{options[1], false, "name=elvish"}}, nil, &Context{Type: NewOptionOrArgument}}, // RequiredArgument, long option, argument following the option directly {0, []string{"--option=name=elvish", ""}, []*ParsedOption{{options[1], true, "name=elvish"}}, nil, &Context{Type: NewOptionOrArgument}}, // RequiredArgument, long option, argument in next element {0, []string{"--option", "name=elvish", ""}, []*ParsedOption{{options[1], true, "name=elvish"}}, nil, &Context{Type: NewOptionOrArgument}}, // OptionalArgument, with argument {0, []string{"-n1", ""}, []*ParsedOption{{options[2], false, "1"}}, nil, &Context{Type: NewOptionOrArgument}}, // OptionalArgument, without argument {0, []string{"-n", ""}, []*ParsedOption{{options[2], false, ""}}, nil, &Context{Type: NewOptionOrArgument}}, // DoubleDashTerminatesOptions {DoubleDashTerminatesOptions, []string{"-a", "--", "-o", ""}, []*ParsedOption{{options[0], false, ""}}, []string{"-o"}, &Context{Type: Argument}}, // FirstArgTerminatesOptions {FirstArgTerminatesOptions, []string{"-a", "x", "-o", ""}, []*ParsedOption{{options[0], false, ""}}, []string{"x", "-o"}, &Context{Type: Argument}}, // LongOnly {LongOnly, []string{"-all", ""}, []*ParsedOption{{options[0], true, ""}}, nil, &Context{Type: NewOptionOrArgument}}, // NewOption {0, []string{"-"}, nil, nil, &Context{Type: NewOption}}, // NewLongOption {0, []string{"--"}, nil, nil, &Context{Type: NewLongOption}}, // LongOption {0, []string{"--all"}, nil, nil, &Context{Type: LongOption, Text: "all"}}, // LongOption, LongOnly {LongOnly, []string{"-all"}, nil, nil, &Context{Type: LongOption, Text: "all"}}, // ChainShortOption {0, []string{"-a"}, []*ParsedOption{{options[0], false, ""}}, nil, &Context{Type: ChainShortOption}}, // OptionArgument, short option, same element {0, []string{"-o"}, nil, nil, &Context{Type: OptionArgument, Option: &ParsedOption{options[1], false, ""}}}, // OptionArgument, short option, separate element {0, []string{"-o", ""}, nil, nil, &Context{Type: OptionArgument, Option: &ParsedOption{options[1], false, ""}}}, // OptionArgument, long option, same element {0, []string{"--option="}, nil, nil, &Context{Type: OptionArgument, Option: &ParsedOption{options[1], true, ""}}}, // OptionArgument, long option, separate element {0, []string{"--option", ""}, nil, nil, &Context{Type: OptionArgument, Option: &ParsedOption{options[1], true, ""}}}, // OptionArgument, long only, same element {LongOnly, []string{"-option="}, nil, nil, &Context{Type: OptionArgument, Option: &ParsedOption{options[1], true, ""}}}, // OptionArgument, long only, separate element {LongOnly, []string{"-option", ""}, nil, nil, &Context{Type: OptionArgument, Option: &ParsedOption{options[1], true, ""}}}, // Argument {0, []string{"x"}, nil, nil, &Context{Type: Argument, Text: "x"}}, // Unknown short option, same element {0, []string{"-x"}, nil, nil, &Context{ Type: OptionArgument, Option: &ParsedOption{ &Option{'x', "", OptionalArgument}, false, ""}}}, // Unknown short option, separate element {0, []string{"-x", ""}, []*ParsedOption{{ &Option{'x', "", OptionalArgument}, false, ""}}, nil, &Context{Type: NewOptionOrArgument}}, // Unknown long option {0, []string{"--unknown", ""}, []*ParsedOption{{ &Option{0, "unknown", OptionalArgument}, true, ""}}, nil, &Context{Type: NewOptionOrArgument}}, // Unknown long option, with argument {0, []string{"--unknown=value", ""}, []*ParsedOption{{ &Option{0, "unknown", OptionalArgument}, true, "value"}}, nil, &Context{Type: NewOptionOrArgument}}, // Unknown long option, LongOnly {LongOnly, []string{"-unknown", ""}, []*ParsedOption{{ &Option{0, "unknown", OptionalArgument}, true, ""}}, nil, &Context{Type: NewOptionOrArgument}}, // Unknown long option, with argument {LongOnly, []string{"-unknown=value", ""}, []*ParsedOption{{ &Option{0, "unknown", OptionalArgument}, true, "value"}}, nil, &Context{Type: NewOptionOrArgument}}, } func TestGetopt(t *testing.T) { for _, tc := range cases { g := &Getopt{options, tc.config} opts, args, ctx := g.Parse(tc.elems) shouldEqual := func(name string, got, want interface{}) { if !reflect.DeepEqual(got, want) { t.Errorf("Parse(%#v) (config = %v)\ngot %s = %v, want %v", tc.elems, tc.config, name, got, want) } } shouldEqual("opts", opts, tc.wantOpts) shouldEqual("args", args, tc.wantArgs) shouldEqual("ctx", ctx, tc.wantCtx) } } elvish-0.11+ds1/getopt/string.go000066400000000000000000000023761323000013700165440ustar00rootroot00000000000000// Code generated by "stringer -type=Config,HasArg,ContextType -output=string.go"; DO NOT EDIT. package getopt import "strconv" const ( _Config_name_0 = "DoubleDashTerminatesOptionsFirstArgTerminatesOptions" _Config_name_1 = "LongOnly" ) var ( _Config_index_0 = [...]uint8{0, 27, 52} ) func (i Config) String() string { switch { case 1 <= i && i <= 2: i -= 1 return _Config_name_0[_Config_index_0[i]:_Config_index_0[i+1]] case i == 4: return _Config_name_1 default: return "Config(" + strconv.FormatInt(int64(i), 10) + ")" } } const _HasArg_name = "NoArgumentRequiredArgumentOptionalArgument" var _HasArg_index = [...]uint8{0, 10, 26, 42} func (i HasArg) String() string { if i >= HasArg(len(_HasArg_index)-1) { return "HasArg(" + strconv.FormatInt(int64(i), 10) + ")" } return _HasArg_name[_HasArg_index[i]:_HasArg_index[i+1]] } const _ContextType_name = "NewOptionOrArgumentNewOptionNewLongOptionLongOptionChainShortOptionOptionArgumentArgument" var _ContextType_index = [...]uint8{0, 19, 28, 41, 51, 67, 81, 89} func (i ContextType) String() string { if i >= ContextType(len(_ContextType_index)-1) { return "ContextType(" + strconv.FormatInt(int64(i), 10) + ")" } return _ContextType_name[_ContextType_index[i]:_ContextType_index[i+1]] } elvish-0.11+ds1/glob/000077500000000000000000000000001323000013700143205ustar00rootroot00000000000000elvish-0.11+ds1/glob/glob.go000066400000000000000000000156141323000013700156010ustar00rootroot00000000000000// Package glob implements globbing for elvish. package glob import ( "io/ioutil" "os" "runtime" "unicode/utf8" ) // TODO: Use native path separators instead of always using /. // Glob returns a list of file names satisfying the given pattern. func Glob(p string, cb func(string) bool) bool { return Parse(p).Glob(cb) } // Glob returns a list of file names satisfying the Pattern. func (p Pattern) Glob(cb func(string) bool) bool { segs := p.Segments dir := "" // XXX: This is a hack solely for supporting globs that start with ~ in the // eval package. if p.DirOverride != "" { dir = p.DirOverride } if len(segs) > 0 && IsSlash(segs[0]) { segs = segs[1:] dir += "/" } else if runtime.GOOS == "windows" && len(segs) > 1 && IsLiteral(segs[0]) && IsSlash(segs[1]) { // TODO: Handle UNC. elem := segs[0].(Literal).Data if isDrive(elem) { segs = segs[2:] dir = elem + "/" } } return glob(segs, dir, cb) } func isDrive(s string) bool { return len(s) == 2 && s[1] == ':' && (('a' <= s[0] && s[1] <= 'z') || ('A' <= s[0] && s[0] <= 'Z')) } // glob finds all filenames matching the given Segments in the given dir, and // calls the callback on all of them. If the callback returns false, globbing is // interrupted, and glob returns false. Otherwise it returns true. func glob(segs []Segment, dir string, cb func(string) bool) bool { // Consume non-wildcard path elements simply by following the path. This may // seem like an optimization, but is actually required for "." and ".." to // be used as path elements, as they do not appear in the result of ReadDir. for len(segs) > 1 && IsLiteral(segs[0]) && IsSlash(segs[1]) { elem := segs[0].(Literal).Data segs = segs[2:] dir += elem + "/" if info, err := os.Stat(dir); err != nil || !info.IsDir() { return true } } if len(segs) == 0 { return cb(dir) } else if len(segs) == 1 && IsLiteral(segs[0]) { path := dir + segs[0].(Literal).Data if _, err := os.Stat(path); err == nil { return cb(path) } return true } infos, err := readDir(dir) if err != nil { // XXX Silently drop the error return true } i := -1 // nexti moves i to the next index in segs that is either / or ** (in other // words, something that matches /). nexti := func() { for i++; i < len(segs); i++ { if IsSlash(segs[i]) || IsWild1(segs[i], StarStar) { break } } } nexti() // Enumerate the position of the first slash. In the presence of multiple // **'s in the pattern, the first slash may be in any of those. // // For instance, in x**y**z, the first slash may be in the first ** or the // second: // 1) If it is in the first, then pattern is equivalent to x*/**y**z. We // match directories with x* and recurse in each subdirectory with the // pattern **y**z. // 2) If it is the in the second, we know that since the first ** can no // longer contain any slashes, we treat it as * (this is done in // matchElement). The pattern is now equivalent to x*y*/**z. We match // directories with x*y* and recurse in each subdirectory with the // pattern **z. // // The rules are: // 1) For each **, we treat it as */** and all previous ones as *. We match // subdirectories with the part before /, and recurse in subdirectories // with the pattern after /. // 2) If a literal / is encountered, we return after recursing in the // subdirectories. for i < len(segs) { slash := IsSlash(segs[i]) var first, rest []Segment if slash { // segs = x/y. Match dir with x, recurse on y. first, rest = segs[:i], segs[i+1:] } else { // segs = x**y. Match dir with x*, recurse on **y. first, rest = segs[:i+1], segs[i:] } for _, info := range infos { name := info.Name() if matchElement(first, name) && info.IsDir() { if !glob(rest, dir+name+"/", cb) { return false } } } if slash { // First slash cannot appear later than a slash in the pattern. return true } nexti() } // If we reach here, it is possible to have no slashes at all. Simply match // the entire pattern with all files. for _, info := range infos { name := info.Name() if matchElement(segs, name) { if !cb(dir + name) { return false } } } return true } // readDir is just like ioutil.ReadDir except that it treats an argument of "" // as ".". func readDir(dir string) ([]os.FileInfo, error) { if dir == "" { dir = "." } return ioutil.ReadDir(dir) } // matchElement matches a path element against segments, which may not contain // any Slash segments. It treats StarStar segments as they are Star segments. func matchElement(segs []Segment, name string) bool { if len(segs) == 0 { return name == "" } // If the name start with "." and the first segment is a Wild, only match // when MatchHidden is true. if len(name) > 0 && name[0] == '.' && IsWild(segs[0]) && !segs[0].(Wild).MatchHidden { return false } segs: for len(segs) > 0 { // Find a chunk. A chunk is an optional Star followed by a run of // fixed-length segments (Literal and Question). var i int for i = 1; i < len(segs); i++ { if IsWild2(segs[i], Star, StarStar) { break } } chunk := segs[:i] startsWithStar := IsWild2(chunk[0], Star, StarStar) var startingStar Wild if startsWithStar { startingStar = chunk[0].(Wild) chunk = chunk[1:] } segs = segs[i:] // NOTE A quick path when len(segs) == 0 can be implemented: match // backwards. // Match at the current position. If this is the last chunk, we need to // make sure name is exhausted by the matching. ok, rest := matchFixedLength(chunk, name) if ok && (rest == "" || len(segs) > 0) { name = rest continue } if startsWithStar { // NOTE An optimization is to make the upper bound not len(names), // but rather len(names) - LB(# bytes segs can match) for i, r := range name { j := i + len(string(r)) // Match name[:j] with the starting *, and the rest with chunk. if !startingStar.Match(r) { break } ok, rest := matchFixedLength(chunk, name[j:]) if ok && (rest == "" || len(segs) > 0) { name = rest continue segs } } } return false } return name == "" } // matchFixedLength returns whether a run of fixed-length segments (Literal and // Question) matches a prefix of name. It returns whether the match is // successful and if if it is, the remaining part of name. func matchFixedLength(segs []Segment, name string) (bool, string) { for _, seg := range segs { if name == "" { return false, "" } switch seg := seg.(type) { case Literal: n := len(seg.Data) if len(name) < n || name[:n] != seg.Data { return false, "" } name = name[n:] case Wild: if seg.Type == Question { r, n := utf8.DecodeRuneInString(name) if !seg.Match(r) { return false, "" } name = name[n:] } else { panic("matchFixedLength given non-question wild segment") } default: panic("matchFixedLength given non-literal non-wild segment") } } return true, name } elvish-0.11+ds1/glob/glob_test.go000066400000000000000000000050421323000013700166320ustar00rootroot00000000000000package glob import ( "os" "reflect" "runtime" "sort" "testing" "github.com/elves/elvish/util" ) var ( mkdirs = []string{"a", "b", "c", "d1", "d1/e", "d1/e/f", "d1/e/f/g", "d2", "d2/e", "d2/e/f", "d2/e/f/g"} mkdirDots = []string{".el"} creates = []string{"a/X", "a/Y", "b/X", "c/Y", "dX", "dXY", "lorem", "ipsum", "d1/e/f/g/X", "d2/e/f/g/X"} createDots = []string{".x", ".el/x"} ) type globCase struct { pattern string want []string } var globCases = []globCase{ {"*", []string{"a", "b", "c", "d1", "d2", "dX", "dXY", "lorem", "ipsum"}}, {".", []string{"."}}, {"./*", []string{"./a", "./b", "./c", "./d1", "./d2", "./dX", "./dXY", "./lorem", "./ipsum"}}, {"..", []string{".."}}, {"a/..", []string{"a/.."}}, {"a/../*", []string{"a/../a", "a/../b", "a/../c", "a/../d1", "a/../d2", "a/../dX", "a/../dXY", "a/../lorem", "a/../ipsum"}}, {"*/", []string{"a/", "b/", "c/", "d1/", "d2/"}}, {"**", append(mkdirs, creates...)}, {"*/X", []string{"a/X", "b/X"}}, {"**X", []string{"a/X", "b/X", "dX", "d1/e/f/g/X", "d2/e/f/g/X"}}, {"*/*/*", []string{"d1/e/f", "d2/e/f"}}, {"l*m", []string{"lorem"}}, {"d*", []string{"d1", "d2", "dX", "dXY"}}, {"d*/", []string{"d1/", "d2/"}}, {"d**", []string{"d1", "d1/e", "d1/e/f", "d1/e/f/g", "d1/e/f/g/X", "d2", "d2/e", "d2/e/f", "d2/e/f/g", "d2/e/f/g/X", "dX", "dXY"}}, {"?", []string{"a", "b", "c"}}, {"??", []string{"d1", "d2", "dX"}}, // Nonexistent paths. {"xxxx", []string{}}, {"xxxx/*", []string{}}, {"a/*/", []string{}}, // TODO Test cases against dotfiles. } func init() { // Add tests for absolute paths. This is platform-dependent and may break if // the directories used change during testing. var dirs []string if runtime.GOOS == "windows" { dirs = []string{`C:/`, `C:/Windows/`} } else { dirs = []string{"/", "/usr/"} } for _, dir := range dirs { globCases = append(globCases, globCase{dir + "*", util.FullNames(dir)}) } } func TestGlob(t *testing.T) { util.InTempDir(func(string) { for _, dir := range append(mkdirs, mkdirDots...) { err := os.Mkdir(dir, 0755) if err != nil { panic(err) } } for _, file := range append(creates, createDots...) { f, err := os.Create(file) if err != nil { panic(err) } f.Close() } for _, tc := range globCases { names := []string{} Glob(tc.pattern, func(name string) bool { names = append(names, name) return true }) sort.Strings(names) sort.Strings(tc.want) if !reflect.DeepEqual(names, tc.want) { t.Errorf(`Glob(%q, "") => %v, want %v`, tc.pattern, names, tc.want) } } }) } elvish-0.11+ds1/glob/parse.go000066400000000000000000000026151323000013700157650ustar00rootroot00000000000000package glob import ( "bytes" "unicode/utf8" ) // Parse parses a pattern. func Parse(s string) Pattern { segments := []Segment{} add := func(seg Segment) { segments = append(segments, seg) } p := &parser{s, 0, 0} rune: for { r := p.next() switch r { case eof: break rune case '?': add(Wild{Question, false, nil}) case '*': n := 1 for p.next() == '*' { n++ } p.backup() if n == 1 { add(Wild{Star, false, nil}) } else { add(Wild{StarStar, false, nil}) } case '/': for p.next() == '/' { } p.backup() add(Slash{}) default: var literal bytes.Buffer literal: for { switch r { case '?', '*', '/', eof: break literal case '\\': r = p.next() if r == eof { break literal } literal.WriteRune(r) default: literal.WriteRune(r) } r = p.next() } p.backup() add(Literal{literal.String()}) } } return Pattern{segments, ""} } // XXX Contains duplicate code with parse/parser.go. type parser struct { src string pos int overEOF int } const eof rune = -1 func (ps *parser) next() rune { if ps.pos == len(ps.src) { ps.overEOF++ return eof } r, s := utf8.DecodeRuneInString(ps.src[ps.pos:]) ps.pos += s return r } func (ps *parser) backup() { if ps.overEOF > 0 { ps.overEOF-- return } _, s := utf8.DecodeLastRuneInString(ps.src[:ps.pos]) ps.pos -= s } elvish-0.11+ds1/glob/parse_test.go000066400000000000000000000020101323000013700170110ustar00rootroot00000000000000package glob import ( "reflect" "testing" ) var parseCases = []struct { src string want []Segment }{ {``, []Segment{}}, {`foo`, []Segment{Literal{"foo"}}}, {`*foo*bar`, []Segment{ Wild{Star, false, nil}, Literal{"foo"}, Wild{Star, false, nil}, Literal{"bar"}}}, {`foo**bar`, []Segment{ Literal{"foo"}, Wild{StarStar, false, nil}, Literal{"bar"}}}, {`/usr/a**b/c`, []Segment{ Slash{}, Literal{"usr"}, Slash{}, Literal{"a"}, Wild{StarStar, false, nil}, Literal{"b"}, Slash{}, Literal{"c"}}}, {`??b`, []Segment{ Wild{Question, false, nil}, Wild{Question, false, nil}, Literal{"b"}}}, // Multiple slashes should be parsed as one. {`//a//b`, []Segment{ Slash{}, Literal{"a"}, Slash{}, Literal{"b"}}}, // Escaping. {`\*\?b`, []Segment{ Literal{"*?b"}, }}, {`abc\`, []Segment{ Literal{"abc"}, }}, } func TestParse(t *testing.T) { for _, tc := range parseCases { p := Parse(tc.src) if !reflect.DeepEqual(p.Segments, tc.want) { t.Errorf("Parse(%q) => %v, want %v", tc.src, p, tc.want) } } } elvish-0.11+ds1/glob/pattern.go000066400000000000000000000032131323000013700163230ustar00rootroot00000000000000package glob // Pattern is a glob pattern. type Pattern struct { Segments []Segment DirOverride string } // Segment is the building block of Pattern. type Segment interface { isSegment() } // Slash represents a slash "/". type Slash struct{} // Literal is a series of non-slash, non-wildcard characters, that is to be // matched literally. type Literal struct { Data string } // Wild is a wildcard. type Wild struct { Type WildType MatchHidden bool Matchers []func(rune) bool } // WildType is the type of a Wild. type WildType int // Values for WildType. const ( Question = iota Star StarStar ) // Match returns whether a rune is within the match set. func (w Wild) Match(r rune) bool { if len(w.Matchers) == 0 { return true } for _, m := range w.Matchers { if m(r) { return true } } return false } func (Literal) isSegment() {} func (Slash) isSegment() {} func (Wild) isSegment() {} // IsSlash returns whether a Segment is a Slash. func IsSlash(seg Segment) bool { _, ok := seg.(Slash) return ok } // IsLiteral returns whether a Segment is a Literal. func IsLiteral(seg Segment) bool { _, ok := seg.(Literal) return ok } // IsWild returns whether a Segment is a Wild. func IsWild(seg Segment) bool { _, ok := seg.(Wild) return ok } // IsWild1 returns whether a Segment is a Wild and has the specified type. func IsWild1(seg Segment, t WildType) bool { return IsWild(seg) && seg.(Wild).Type == t } // IsWild2 returns whether a Segment is a Wild and has one of the two specified // types. func IsWild2(seg Segment, t1, t2 WildType) bool { return IsWild(seg) && (seg.(Wild).Type == t1 || seg.(Wild).Type == t2) } elvish-0.11+ds1/main.go000066400000000000000000000006451323000013700146550ustar00rootroot00000000000000// Elvish is a cross-platform shell, supporting Linux, BSDs and Windows. It // features an expressive programming language, with features like namespacing // and anonymous functions, and a fully programmable user interface with // friendly defaults. It is suitable for both interactive use and scripting. package main import ( "os" "github.com/elves/elvish/program" ) func main() { os.Exit(program.Main(os.Args)) } elvish-0.11+ds1/parse/000077500000000000000000000000001323000013700145075ustar00rootroot00000000000000elvish-0.11+ds1/parse/boilerplate.go000066400000000000000000000136211323000013700173430ustar00rootroot00000000000000package parse func IsChunk(n Node) bool { _, ok := n.(*Chunk) return ok } func GetChunk(n Node) *Chunk { if nn, ok := n.(*Chunk); ok { return nn } return nil } func (n *Chunk) addToPipelines(ch *Pipeline) { n.Pipelines = append(n.Pipelines, ch) addChild(n, ch) } func ParseChunk(ps *Parser) *Chunk { n := &Chunk{node: node{begin: ps.pos}} n.parse(ps) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } func IsPipeline(n Node) bool { _, ok := n.(*Pipeline) return ok } func GetPipeline(n Node) *Pipeline { if nn, ok := n.(*Pipeline); ok { return nn } return nil } func (n *Pipeline) addToForms(ch *Form) { n.Forms = append(n.Forms, ch) addChild(n, ch) } func ParsePipeline(ps *Parser) *Pipeline { n := &Pipeline{node: node{begin: ps.pos}} n.parse(ps) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } func IsForm(n Node) bool { _, ok := n.(*Form) return ok } func GetForm(n Node) *Form { if nn, ok := n.(*Form); ok { return nn } return nil } func (n *Form) addToAssignments(ch *Assignment) { n.Assignments = append(n.Assignments, ch) addChild(n, ch) } func (n *Form) setHead(ch *Compound) { n.Head = ch addChild(n, ch) } func (n *Form) addToVars(ch *Compound) { n.Vars = append(n.Vars, ch) addChild(n, ch) } func (n *Form) addToArgs(ch *Compound) { n.Args = append(n.Args, ch) addChild(n, ch) } func (n *Form) addToOpts(ch *MapPair) { n.Opts = append(n.Opts, ch) addChild(n, ch) } func (n *Form) addToRedirs(ch *Redir) { n.Redirs = append(n.Redirs, ch) addChild(n, ch) } func (n *Form) setExitusRedir(ch *ExitusRedir) { n.ExitusRedir = ch addChild(n, ch) } func ParseForm(ps *Parser) *Form { n := &Form{node: node{begin: ps.pos}} n.parse(ps) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } func IsAssignment(n Node) bool { _, ok := n.(*Assignment) return ok } func GetAssignment(n Node) *Assignment { if nn, ok := n.(*Assignment); ok { return nn } return nil } func (n *Assignment) setLeft(ch *Indexing) { n.Left = ch addChild(n, ch) } func (n *Assignment) setRight(ch *Compound) { n.Right = ch addChild(n, ch) } func ParseAssignment(ps *Parser) *Assignment { n := &Assignment{node: node{begin: ps.pos}} n.parse(ps) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } func IsExitusRedir(n Node) bool { _, ok := n.(*ExitusRedir) return ok } func GetExitusRedir(n Node) *ExitusRedir { if nn, ok := n.(*ExitusRedir); ok { return nn } return nil } func (n *ExitusRedir) setDest(ch *Compound) { n.Dest = ch addChild(n, ch) } func ParseExitusRedir(ps *Parser) *ExitusRedir { n := &ExitusRedir{node: node{begin: ps.pos}} n.parse(ps) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } func IsRedir(n Node) bool { _, ok := n.(*Redir) return ok } func GetRedir(n Node) *Redir { if nn, ok := n.(*Redir); ok { return nn } return nil } func (n *Redir) setLeft(ch *Compound) { n.Left = ch addChild(n, ch) } func (n *Redir) setRight(ch *Compound) { n.Right = ch addChild(n, ch) } func ParseRedir(ps *Parser, dest *Compound) *Redir { n := &Redir{node: node{begin: ps.pos}} n.parse(ps, dest) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } func IsCompound(n Node) bool { _, ok := n.(*Compound) return ok } func GetCompound(n Node) *Compound { if nn, ok := n.(*Compound); ok { return nn } return nil } func (n *Compound) addToIndexings(ch *Indexing) { n.Indexings = append(n.Indexings, ch) addChild(n, ch) } func ParseCompound(ps *Parser, ctx ExprCtx) *Compound { n := &Compound{node: node{begin: ps.pos}} n.parse(ps, ctx) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } func IsIndexing(n Node) bool { _, ok := n.(*Indexing) return ok } func GetIndexing(n Node) *Indexing { if nn, ok := n.(*Indexing); ok { return nn } return nil } func (n *Indexing) setHead(ch *Primary) { n.Head = ch addChild(n, ch) } func (n *Indexing) addToIndicies(ch *Array) { n.Indicies = append(n.Indicies, ch) addChild(n, ch) } func ParseIndexing(ps *Parser, ctx ExprCtx) *Indexing { n := &Indexing{node: node{begin: ps.pos}} n.parse(ps, ctx) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } func IsArray(n Node) bool { _, ok := n.(*Array) return ok } func GetArray(n Node) *Array { if nn, ok := n.(*Array); ok { return nn } return nil } func (n *Array) addToCompounds(ch *Compound) { n.Compounds = append(n.Compounds, ch) addChild(n, ch) } func ParseArray(ps *Parser, allowSemicolon bool) *Array { n := &Array{node: node{begin: ps.pos}} n.parse(ps, allowSemicolon) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } func IsPrimary(n Node) bool { _, ok := n.(*Primary) return ok } func GetPrimary(n Node) *Primary { if nn, ok := n.(*Primary); ok { return nn } return nil } func (n *Primary) addToElements(ch *Compound) { n.Elements = append(n.Elements, ch) addChild(n, ch) } func (n *Primary) setChunk(ch *Chunk) { n.Chunk = ch addChild(n, ch) } func (n *Primary) addToMapPairs(ch *MapPair) { n.MapPairs = append(n.MapPairs, ch) addChild(n, ch) } func (n *Primary) addToBraced(ch *Compound) { n.Braced = append(n.Braced, ch) addChild(n, ch) } func ParsePrimary(ps *Parser, ctx ExprCtx) *Primary { n := &Primary{node: node{begin: ps.pos}} n.parse(ps, ctx) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } func IsMapPair(n Node) bool { _, ok := n.(*MapPair) return ok } func GetMapPair(n Node) *MapPair { if nn, ok := n.(*MapPair); ok { return nn } return nil } func (n *MapPair) setKey(ch *Compound) { n.Key = ch addChild(n, ch) } func (n *MapPair) setValue(ch *Compound) { n.Value = ch addChild(n, ch) } func ParseMapPair(ps *Parser) *MapPair { n := &MapPair{node: node{begin: ps.pos}} n.parse(ps) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } func IsSep(n Node) bool { _, ok := n.(*Sep) return ok } func GetSep(n Node) *Sep { if nn, ok := n.(*Sep); ok { return nn } return nil } elvish-0.11+ds1/parse/boilerplate.py000077500000000000000000000075601323000013700173760ustar00rootroot00000000000000#!/usr/bin/python2.7 """ Generate helper functions for node types. For every node type T, it generates the following: * A IsT func that determines whether a Node is actually of type *T. * A GetT func that takes Node and returns *T. It examines whether the Node is actually of type *T, and if it is, returns it; otherwise it returns nil. * For each field F of type *[]U, it generates a addToF method that appends a node to this field and adds it to the children list. * For each field F of type *U where U is not a slice, it generates a setF method that sets this field and adds it to the children list. * If the type has a parse method that takes a *paser, it genertes a parseT func that takes a *Parser and returns *T. The func creates a new instance of *T, sets its begin field, calls its parse method, and set its end and sourceText fields. For example, for the following type: type X struct { node F *Y G *[]Z } The following boilerplate is generated: func IsX(n Node) bool { _, ok := n.(*X) return ok } func GetX(n Node) *X { if nn, ok := n.(*X); ok { return nn } return nil } func (n *X) setF(ch *Y) { n.F = ch addChild(n, ch) } func (n *X) addToG(ch *Z) { n.G = append(n.G, ch) addChild(n, ch) } func ParseX(ps *Parser) *X { n := &X{node: node{begin: ps.pos}} n.parse(ps) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n } """ import re import os def put_is(out, typename): print >>out, ''' func Is{typename}(n Node) bool {{ _, ok := n.(*{typename}) return ok }} '''.format(typename=typename) def put_get(out, typename): print >>out, ''' func Get{typename}(n Node) *{typename} {{ if nn, ok := n.(*{typename}); ok {{ return nn }} return nil }} '''.format(typename=typename) def put_set(out, parent, field, child): print >>out, ''' func (n *{parent}) set{field}(ch *{child}) {{ n.{field} = ch addChild(n, ch) }}'''.format(parent=parent, field=field, child=child) def put_addto(out, parent, field, child): print >>out, ''' func (n *{parent}) addTo{field}(ch *{child}) {{ n.{field} = append(n.{field}, ch) addChild(n, ch) }}'''.format(parent=parent, field=field, child=child) def put_parse(out, typename, extraargs): extranames = ', '.join(a.split(' ')[0] for a in extraargs.split(', ')) if extraargs else '' print >>out, ''' func Parse{typename}(ps *Parser{extraargs}) *{typename} {{ n := &{typename}{{node: node{{begin: ps.pos}}}} n.parse(ps{extranames}) n.end = ps.pos n.sourceText = ps.src[n.begin:n.end] return n }}'''.format(typename=typename, extraargs=extraargs, extranames=extranames) def main(): types = [] in_type = '' out = open('boilerplate.go', 'w') print >>out, 'package parse' for line in file('parse.go'): if in_type: if line == '}\n': in_type = '' continue m = re.match(r'^\t(\w+(?:, \w+)*) +(\S+)', line) if m: fields = m.group(1).split(', ') typename = m.group(2) if typename.startswith('*'): # Single child [put_set(out, in_type, f, typename[1:]) for f in fields] elif typename.startswith('[]*'): # Children list [put_addto(out, in_type, f, typename[3:]) for f in fields] continue m = re.match(r'^type (.*) struct', line) if m: in_type = m.group(1) put_is(out, in_type) put_get(out, in_type) continue m = re.match( r'^func \(.* \*(.*)\) parse\(ps \*Parser(.*?)\) {$', line) if m: typename, extraargs = m.groups() put_parse(out, typename, extraargs) out.close() os.system('gofmt -w boilerplate.go') if __name__ == '__main__': main() elvish-0.11+ds1/parse/check_ast.go000066400000000000000000000106111323000013700167610ustar00rootroot00000000000000package parse import ( "fmt" "reflect" "strings" "unicode" "unicode/utf8" ) // AST checking utilities. Used in test cases. // ast is an AST specification. The name part identifies the type of the Node; // for instance, "Chunk" specifies a Chunk. The fields part is specifies children // to check; see document of fs. // // When a Node contains exactly one child, It can be coalesced with its child // by adding "/ChildName" in the name part. For instance, "Chunk/Pipeline" // specifies a Chunk that contains exactly one Pipeline. In this case, the // fields part specified the children of the Pipeline instead of the Chunk // (which has no additional interesting fields anyway). Multi-level coalescence // like "Chunk/Pipeline/Form" is also allowed. // // The dynamic type of the Node being checked is assumed to be a pointer to a // struct that embeds the "node" struct. type ast struct { name string fields fs } // fs specifies fields of a Node to check. For the value of field $f in the // Node ("found value"), fs[$f] ("wanted value") is used to check against it. // // If the key is "text", the SourceText of the Node is checked. It doesn't // involve a found value. // // If the wanted value is nil, the found value is checked against nil. // // If the found value implements Node, then the wanted value must be either an // ast, where the checking algorithm of ast applies, or a string, where the // source text of the found value is checked. // // If the found value is a slice whose elements implement Node, then the wanted // value must be a slice where checking is then done recursively. // // If the found value satisfied none of the above conditions, it is checked // against the wanted value using reflect.DeepEqual. type fs map[string]interface{} // checkAST checks an AST against a specification. func checkAST(n Node, want ast) error { wantnames := strings.Split(want.name, "/") // Check coalesced levels for i, wantname := range wantnames { name := reflect.TypeOf(n).Elem().Name() if wantname != name { return fmt.Errorf("want %s, got %s (%s)", wantname, name, summary(n)) } if i == len(wantnames)-1 { break } fields := n.Children() if len(fields) != 1 { return fmt.Errorf("want exactly 1 child, got %d (%s)", len(fields), summary(n)) } n = fields[0] } ntype := reflect.TypeOf(n).Elem() nvalue := reflect.ValueOf(n).Elem() for i := 0; i < ntype.NumField(); i++ { fieldname := ntype.Field(i).Name if !exported(fieldname) { // Unexported field continue } got := nvalue.Field(i).Interface() want, ok := want.fields[fieldname] if ok { err := checkField(got, want, "field "+fieldname+" of: "+summary(n)) if err != nil { return err } } else { // Not specified. Check if got is a zero value of its type. if !reflect.DeepEqual(got, reflect.Zero(reflect.TypeOf(got)).Interface()) { return fmt.Errorf("want zero, got %v (field %s of: %s)", got, fieldname, summary(n)) } } } return nil } var nodeType = reflect.TypeOf((*Node)(nil)).Elem() // checkField checks a field against a field specification. func checkField(got interface{}, want interface{}, ctx string) error { // Want nil. if want == nil { if !reflect.ValueOf(got).IsNil() { return fmt.Errorf("want nil, got %v (%s)", got, ctx) } return nil } if got, ok := got.(Node); ok { // Got a Node. return checkNodeInField(got.(Node), want) } tgot := reflect.TypeOf(got) if tgot.Kind() == reflect.Slice && tgot.Elem().Implements(nodeType) { // Got a slice of Nodes. vgot := reflect.ValueOf(got) vwant := reflect.ValueOf(want) if vgot.Len() != vwant.Len() { return fmt.Errorf("want %d, got %d (%s)", vwant.Len(), vgot.Len(), ctx) } for i := 0; i < vgot.Len(); i++ { err := checkNodeInField(vgot.Index(i).Interface().(Node), vwant.Index(i).Interface()) if err != nil { return err } } return nil } if !reflect.DeepEqual(want, got) { return fmt.Errorf("want %v, got %v (%s)", want, got, ctx) } return nil } func checkNodeInField(got Node, want interface{}) error { switch want := want.(type) { case string: text := got.SourceText() if want != text { return fmt.Errorf("want %q, got %q (%s)", want, text, summary(got)) } return nil case ast: return checkAST(got, want) default: panic(fmt.Sprintf("bad want type %T (%s)", want, summary(got))) } } func exported(name string) bool { r, _ := utf8.DecodeRuneInString(name) return unicode.IsUpper(r) } elvish-0.11+ds1/parse/error.go000066400000000000000000000031211323000013700161640ustar00rootroot00000000000000package parse import ( "bytes" "fmt" "github.com/elves/elvish/util" ) // ErrorEntry represents one parse error. type ErrorEntry struct { Message string Context util.SourceRange } // Error stores multiple ErrorEntry's and can pretty print them. type Error struct { Entries []*ErrorEntry } func (pe *Error) Add(msg string, ctx *util.SourceRange) { pe.Entries = append(pe.Entries, &ErrorEntry{msg, *ctx}) } func (pe *Error) Error() string { switch len(pe.Entries) { case 0: return "no parse error" case 1: e := pe.Entries[0] return fmt.Sprintf("parse error: %d-%d in %s: %s", e.Context.Begin, e.Context.End, e.Context.Name, e.Message) default: buf := new(bytes.Buffer) // Contexts of parse error entries all have the same name fmt.Fprintf(buf, "multiple parse errors in %s: ", pe.Entries[0].Context.Name) for i, e := range pe.Entries { if i > 0 { fmt.Fprint(buf, "; ") } fmt.Fprintf(buf, "%d-%d: %s", e.Context.Begin, e.Context.End, e.Message) } return buf.String() } } func (pe *Error) Pprint(indent string) string { buf := new(bytes.Buffer) switch len(pe.Entries) { case 0: return "no parse error" case 1: e := pe.Entries[0] fmt.Fprintf(buf, "Parse error: \033[31;1m%s\033[m\n", e.Message) buf.WriteString(e.Context.PprintCompact(indent + " ")) default: fmt.Fprint(buf, "Multiple parse errors:") for _, e := range pe.Entries { buf.WriteString("\n" + indent + " ") fmt.Fprintf(buf, "\033[31;1m%s\033[m\n", e.Message) buf.WriteString(indent + " ") buf.WriteString(e.Context.Pprint(indent + " ")) } } return buf.String() } elvish-0.11+ds1/parse/node.go000066400000000000000000000010761323000013700157670ustar00rootroot00000000000000package parse // Node represents a parse tree as well as an AST. type Node interface { n() *node Parent() Node Begin() int End() int SourceText() string Children() []Node } type node struct { parent Node begin, end int sourceText string children []Node } func (n *node) n() *node { return n } func (n *node) Parent() Node { return n.parent } func (n *node) Begin() int { return n.begin } func (n *node) End() int { return n.end } func (n *node) SourceText() string { return n.sourceText } func (n *node) Children() []Node { return n.children } elvish-0.11+ds1/parse/parse.go000066400000000000000000000532051323000013700161550ustar00rootroot00000000000000// Package parse implements the elvish parser. package parse //go:generate ./boilerplate.py //go:generate stringer -type=PrimaryType,RedirMode -output=string.go import ( "bytes" "errors" "fmt" "unicode" ) // Parse parses Elvish source. If the error is not nil, it always has type // ParseError. func Parse(srcname, src string) (*Chunk, error) { ps := NewParser(srcname, src) n := ParseChunk(ps) ps.Done() return n, ps.Errors() } // Errors. var ( errShouldBeForm = newError("", "form") errBadLHS = errors.New("bad assignment LHS") errDuplicateExitusRedir = newError("duplicate exitus redir") errBadRedirSign = newError("bad redir sign", "'<'", "'>'", "'>>'", "'<>'") errShouldBeFD = newError("", "a composite term representing fd") errShouldBeFilename = newError("", "a composite term representing filename") errShouldBeArray = newError("", "spaced") errStringUnterminated = newError("string not terminated") errChainedAssignment = newError("chained assignment not yet supported") errInvalidEscape = newError("invalid escape sequence") errInvalidEscapeOct = newError("invalid escape sequence", "octal digit") errInvalidEscapeHex = newError("invalid escape sequence", "hex digit") errInvalidEscapeControl = newError("invalid control sequence", "a rune between @ (0x40) and _(0x5F)") errShouldBePrimary = newError("", "single-quoted string", "double-quoted string", "bareword") errShouldBeVariableName = newError("", "variable name") errShouldBeRBracket = newError("", "']'") errShouldBeRBrace = newError("", "'}'") errShouldBeBraceSepOrRBracket = newError("", "','", "'}'") errShouldBeRParen = newError("", "')'") errShouldBeCompound = newError("", "compound") errShouldBeEqual = newError("", "'='") errBothElementsAndPairs = newError("cannot contain both list elements and map pairs") errShouldBeNewline = newError("", "newline") ) // Chunk = { PipelineSep | Space } { Pipeline { PipelineSep | Space } } type Chunk struct { node Pipelines []*Pipeline } func (bn *Chunk) parse(ps *Parser) { bn.parseSeps(ps) for startsPipeline(ps.peek()) { bn.addToPipelines(ParsePipeline(ps)) if bn.parseSeps(ps) == 0 { break } } } func isPipelineSep(r rune) bool { return r == '\n' || r == ';' } // parseSeps parses pipeline separators along with whitespaces. It returns the // number of pipeline separators parsed. func (bn *Chunk) parseSeps(ps *Parser) int { nseps := 0 for { r := ps.peek() if isPipelineSep(r) { // parse as a Sep parseSep(bn, ps, r) nseps++ } else if IsSpace(r) { // parse a run of spaces as a Sep parseSpaces(bn, ps) } else if r == '#' { // parse a comment as a Sep for { r := ps.peek() if r == eof || r == '\n' { break } ps.next() } addSep(bn, ps) nseps++ } else { break } } return nseps } // Pipeline = Form { '|' Form } type Pipeline struct { node Forms []*Form Background bool } func (pn *Pipeline) parse(ps *Parser) { pn.addToForms(ParseForm(ps)) for parseSep(pn, ps, '|') { parseSpacesAndNewlines(pn, ps) if !startsForm(ps.peek()) { ps.error(errShouldBeForm) return } pn.addToForms(ParseForm(ps)) } parseSpaces(pn, ps) if ps.peek() == '&' { ps.next() addSep(pn, ps) pn.Background = true parseSpaces(pn, ps) } } func startsPipeline(r rune) bool { return startsForm(r) } // Form = { Space } { { Assignment } { Space } } // { Compound } { Space } { ( Compound | MapPair | Redir | ExitusRedir ) { Space } } type Form struct { node Assignments []*Assignment Head *Compound // Left-hand-sides for the spacey assignment. Right-hand-sides are in Args. Vars []*Compound Args []*Compound Opts []*MapPair Redirs []*Redir ExitusRedir *ExitusRedir } func (fn *Form) parse(ps *Parser) { parseSpaces(fn, ps) for fn.tryAssignment(ps) { parseSpaces(fn, ps) } // Parse head. if !startsCompound(ps.peek(), CmdExpr) { if len(fn.Assignments) > 0 { // Assignment-only form. return } // Bad form. ps.error(fmt.Errorf("bad rune at form head: %q", ps.peek())) } fn.setHead(ParseCompound(ps, CmdExpr)) parseSpaces(fn, ps) for { r := ps.peek() switch { case r == '&': ps.next() hasMapPair := startsCompound(ps.peek(), LHSExpr) ps.backup() if !hasMapPair { // background indicator return } fn.addToOpts(ParseMapPair(ps)) case startsCompound(r, NormalExpr): if ps.hasPrefix("?>") { if fn.ExitusRedir != nil { ps.error(errDuplicateExitusRedir) // Parse the duplicate redir anyway. addChild(fn, ParseExitusRedir(ps)) } else { fn.setExitusRedir(ParseExitusRedir(ps)) } continue } cn := ParseCompound(ps, NormalExpr) if isRedirSign(ps.peek()) { // Redir fn.addToRedirs(ParseRedir(ps, cn)) } else if cn.sourceText == "=" { // Spacey assignment. // Turn the equal sign into a Sep. addChild(fn, NewSep(ps.src, cn.begin, cn.end)) // Turn the head and preceding arguments into LHSs. addLHS := func(cn *Compound) { if len(cn.Indexings) == 1 && checkVariableInAssignment(cn.Indexings[0].Head, ps) { fn.Vars = append(fn.Vars, cn) } else { ps.errorp(cn.begin, cn.end, errBadLHS) } } if fn.Head != nil { addLHS(fn.Head) } else { ps.error(errChainedAssignment) } fn.Head = nil for _, cn := range fn.Args { addLHS(cn) } fn.Args = nil } else { fn.addToArgs(cn) } case isRedirSign(r): fn.addToRedirs(ParseRedir(ps, nil)) default: return } parseSpaces(fn, ps) } } // tryAssignment tries to parse an assignment. If succeeded, it adds the parsed // assignment to fn.Assignments and returns true. Otherwise it rewinds the // parser and returns false. func (fn *Form) tryAssignment(ps *Parser) bool { if !startsIndexing(ps.peek(), LHSExpr) { return false } pos := ps.pos errorEntries := ps.errors.Entries an := ParseAssignment(ps) // If errors were added, revert if len(ps.errors.Entries) > len(errorEntries) { ps.errors.Entries = errorEntries ps.pos = pos return false } fn.addToAssignments(an) return true } func startsForm(r rune) bool { return IsSpace(r) || startsCompound(r, CmdExpr) } // Assignment = Indexing '=' Compound type Assignment struct { node Left *Indexing Right *Compound } func (an *Assignment) parse(ps *Parser) { an.setLeft(ParseIndexing(ps, LHSExpr)) head := an.Left.Head if !checkVariableInAssignment(head, ps) { ps.errorp(head.Begin(), head.End(), errShouldBeVariableName) } if !parseSep(an, ps, '=') { ps.error(errShouldBeEqual) } an.setRight(ParseCompound(ps, NormalExpr)) } func checkVariableInAssignment(p *Primary, ps *Parser) bool { if p.Type == Braced { // XXX don't check further inside braced expression return true } if p.Type != Bareword && p.Type != SingleQuoted && p.Type != DoubleQuoted { return false } if p.Value == "" { return false } for _, r := range p.Value { // XXX special case '&' and '@'. if !allowedInVariableName(r) && r != '&' && r != '@' { return false } } return true } // ExitusRedir = '?' '>' { Space } Compound type ExitusRedir struct { node Dest *Compound } func (ern *ExitusRedir) parse(ps *Parser) { ps.next() ps.next() addSep(ern, ps) parseSpaces(ern, ps) ern.setDest(ParseCompound(ps, NormalExpr)) } // Redir = { Compound } { '<'|'>'|'<>'|'>>' } { Space } ( '&'? Compound ) type Redir struct { node Left *Compound Mode RedirMode RightIsFd bool Right *Compound } func (rn *Redir) parse(ps *Parser, dest *Compound) { // The parsing of the Left part is done in Form.parse. if dest != nil { rn.setLeft(dest) rn.begin = dest.begin } begin := ps.pos for isRedirSign(ps.peek()) { ps.next() } sign := ps.src[begin:ps.pos] switch sign { case "<": rn.Mode = Read case ">": rn.Mode = Write case ">>": rn.Mode = Append case "<>": rn.Mode = ReadWrite default: ps.error(errBadRedirSign) } addSep(rn, ps) parseSpaces(rn, ps) if parseSep(rn, ps, '&') { rn.RightIsFd = true } rn.setRight(ParseCompound(ps, NormalExpr)) if len(rn.Right.Indexings) == 0 { if rn.RightIsFd { ps.error(errShouldBeFD) } else { ps.error(errShouldBeFilename) } return } } func isRedirSign(r rune) bool { return r == '<' || r == '>' } // RedirMode records the mode of an IO redirection. type RedirMode int // Possible values for RedirMode. const ( BadRedirMode RedirMode = iota Read Write ReadWrite Append ) // Compound = { Indexing } type Compound struct { node Indexings []*Indexing } // ExprCtx represents special contexts of expression parsing. type ExprCtx int const ( // NormalExpr represents a normal expression, namely none of the special // ones below. NormalExpr ExprCtx = iota // CmdExpr represents an expression used as the command in a form. In this // context, unquoted <>*^ are treated as bareword characters. CmdExpr // LHSExpr represents an expression used as the left-hand-side in either // assignments or map pairs. In this context, an unquoted = serves as an // expression terminator and is thus not treated as a bareword character. LHSExpr // BracedElemExpr represents an expression used as an element in a braced // expression. In this context, an unquoted , serves as an expression // terminator and is thus not treated as a bareword character. BracedElemExpr // strictExpr is only meaningful to allowedInBareword. strictExpr ) func (cn *Compound) parse(ps *Parser, ctx ExprCtx) { cn.tilde(ps) for startsIndexing(ps.peek(), ctx) { cn.addToIndexings(ParseIndexing(ps, ctx)) } } // tilde parses a tilde if there is one. It is implemented here instead of // within Primary since a tilde can only appear as the first part of a // Compound. Elsewhere tildes are barewords. func (cn *Compound) tilde(ps *Parser) { if ps.peek() == '~' { ps.next() base := node{nil, ps.pos - 1, ps.pos, "~", nil} pn := &Primary{node: base, Type: Tilde, Value: "~"} in := &Indexing{node: base} in.setHead(pn) cn.addToIndexings(in) } } func startsCompound(r rune, ctx ExprCtx) bool { return startsIndexing(r, ctx) } // Indexing = Primary { '[' Array ']' } type Indexing struct { node Head *Primary Indicies []*Array } func (in *Indexing) parse(ps *Parser, ctx ExprCtx) { in.setHead(ParsePrimary(ps, ctx)) for parseSep(in, ps, '[') { if !startsArray(ps.peek()) { ps.error(errShouldBeArray) } in.addToIndicies(ParseArray(ps, false)) if !parseSep(in, ps, ']') { ps.error(errShouldBeRBracket) return } } } func startsIndexing(r rune, ctx ExprCtx) bool { return startsPrimary(r, ctx) } // Array = { Space | '\n' } { Compound { Space | '\n' } } type Array struct { node Compounds []*Compound // When non-empty, records the occurrences of semicolons by the indices of // the compounds they appear before. For instance, [; ; a b; c d;] results // in Semicolons={0 0 2 4}. Semicolons []int } func (sn *Array) parse(ps *Parser, allowSemicolon bool) { parseSep := func() { parseSpacesAndNewlines(sn, ps) if allowSemicolon { for parseSep(sn, ps, ';') { sn.Semicolons = append(sn.Semicolons, len(sn.Compounds)) } parseSpacesAndNewlines(sn, ps) } } parseSep() for startsCompound(ps.peek(), NormalExpr) { sn.addToCompounds(ParseCompound(ps, NormalExpr)) parseSep() } } func IsSpace(r rune) bool { return r == ' ' || r == '\t' } func startsArray(r rune) bool { return IsSpaceOrNewline(r) || startsIndexing(r, NormalExpr) } // Primary is the smallest expression unit. type Primary struct { node Type PrimaryType // The unquoted string value. Valid for Bareword, SingleQuoted, // DoubleQuoted, Variable, Wildcard and Tilde. Value string Elements []*Compound // Valid for List and Labda Chunk *Chunk // Valid for OutputCapture, ExitusCapture and Lambda MapPairs []*MapPair // Valid for Map and Lambda Braced []*Compound // Valid for Braced } // PrimaryType is the type of a Primary. type PrimaryType int // Possible values for PrimaryType. const ( BadPrimary PrimaryType = iota Bareword SingleQuoted DoubleQuoted Variable Wildcard Tilde ExceptionCapture OutputCapture List Lambda Map Braced ) func (pn *Primary) parse(ps *Parser, ctx ExprCtx) { r := ps.peek() if !startsPrimary(r, ctx) { ps.error(errShouldBePrimary) return } // Try bareword early, since it has precedence over wildcard on * // when ctx = commandExpr. if allowedInBareword(r, ctx) { pn.bareword(ps, ctx) return } switch r { case '\'': pn.singleQuoted(ps) case '"': pn.doubleQuoted(ps) case '$': pn.variable(ps) case '*': pn.wildcard(ps) case '?': if ps.hasPrefix("?(") { pn.exitusCapture(ps) } else { pn.wildcard(ps) } case '(': pn.outputCapture(ps) case '[': pn.lbracket(ps) case '{': pn.lbrace(ps) default: // Parse an empty bareword. pn.Type = Bareword } } func (pn *Primary) singleQuoted(ps *Parser) { pn.Type = SingleQuoted ps.next() var buf bytes.Buffer defer func() { pn.Value = buf.String() }() for { switch r := ps.next(); r { case eof: ps.error(errStringUnterminated) return case '\'': if ps.peek() == '\'' { // Two consecutive single quotes ps.next() buf.WriteByte('\'') } else { // End of string return } default: buf.WriteRune(r) } } } func (pn *Primary) doubleQuoted(ps *Parser) { pn.Type = DoubleQuoted ps.next() var buf bytes.Buffer defer func() { pn.Value = buf.String() }() for { switch r := ps.next(); r { case eof: ps.error(errStringUnterminated) return case '"': return case '\\': switch r := ps.next(); r { case 'c', '^': // Control sequence r := ps.next() if r < 0x40 || r >= 0x60 { ps.backup() ps.error(errInvalidEscapeControl) ps.next() } buf.WriteByte(byte(r - 0x40)) case 'x', 'u', 'U': var n int switch r { case 'x': n = 2 case 'u': n = 4 case 'U': n = 8 } var rr rune for i := 0; i < n; i++ { d, ok := hexToDigit(ps.next()) if !ok { ps.backup() ps.error(errInvalidEscapeHex) break } rr = rr*16 + d } buf.WriteRune(rr) case '0', '1', '2', '3', '4', '5', '6', '7': // 2 more octal digits rr := r - '0' for i := 0; i < 2; i++ { r := ps.next() if r < '0' || r > '7' { ps.backup() ps.error(errInvalidEscapeOct) break } rr = rr*8 + (r - '0') } buf.WriteRune(rr) default: if rr, ok := doubleEscape[r]; ok { buf.WriteRune(rr) } else { ps.backup() ps.error(errInvalidEscape) ps.next() } } default: buf.WriteRune(r) } } } // a table for the simple double-quote escape sequences. var doubleEscape = map[rune]rune{ // same as golang 'a': '\a', 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', 'v': '\v', '\\': '\\', '"': '"', // additional 'e': '\033', } var doubleUnescape = map[rune]rune{} func init() { for k, v := range doubleEscape { doubleUnescape[v] = k } } func hexToDigit(r rune) (rune, bool) { switch { case '0' <= r && r <= '9': return r - '0', true case 'a' <= r && r <= 'f': return r - 'a' + 10, true case 'A' <= r && r <= 'F': return r - 'A' + 10, true default: return -1, false } } func (pn *Primary) variable(ps *Parser) { pn.Type = Variable defer func() { pn.Value = ps.src[pn.begin+1 : ps.pos] }() ps.next() // The character of the variable name can be anything. if ps.next() == eof { ps.backup() ps.error(errShouldBeVariableName) ps.next() } for allowedInVariableName(ps.peek()) { ps.next() } } // The following are allowed in variable names: // * Anything beyond ASCII that is printable // * Letters and numbers // * The symbols "-_:~" func allowedInVariableName(r rune) bool { return (r >= 0x80 && unicode.IsPrint(r)) || ('0' <= r && r <= '9') || ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z') || r == '-' || r == '_' || r == ':' || r == '~' } func (pn *Primary) wildcard(ps *Parser) { pn.Type = Wildcard for isWildcard(ps.peek()) { ps.next() } pn.Value = ps.src[pn.begin:ps.pos] } func isWildcard(r rune) bool { return r == '*' || r == '?' } func (pn *Primary) exitusCapture(ps *Parser) { ps.next() ps.next() addSep(pn, ps) pn.Type = ExceptionCapture pn.setChunk(ParseChunk(ps)) if !parseSep(pn, ps, ')') { ps.error(errShouldBeRParen) } } func (pn *Primary) outputCapture(ps *Parser) { pn.Type = OutputCapture parseSep(pn, ps, '(') pn.setChunk(ParseChunk(ps)) if !parseSep(pn, ps, ')') { ps.error(errShouldBeRParen) } } // List = '[' { Space } { Compound } ']' // = '[' { Space } { MapPair { Space } } ']' // Map = '[' { Space } '&' { Space } ']' // Lambda = '[' { Space } { (Compound | MapPair) { Space } } ']' '{' Chunk '}' func (pn *Primary) lbracket(ps *Parser) { parseSep(pn, ps, '[') parseSpacesAndNewlines(pn, ps) loneAmpersand := false items: for { r := ps.peek() switch { case r == '&': ps.next() hasMapPair := startsCompound(ps.peek(), LHSExpr) if !hasMapPair { loneAmpersand = true addSep(pn, ps) parseSpacesAndNewlines(pn, ps) break items } ps.backup() pn.addToMapPairs(ParseMapPair(ps)) case startsCompound(r, NormalExpr): pn.addToElements(ParseCompound(ps, NormalExpr)) default: break items } parseSpacesAndNewlines(pn, ps) } if !parseSep(pn, ps, ']') { ps.error(errShouldBeRBracket) } if parseSep(pn, ps, '{') { pn.lambda(ps) } else { if loneAmpersand || len(pn.MapPairs) > 0 { if len(pn.Elements) > 0 { ps.error(errBothElementsAndPairs) } pn.Type = Map } else { pn.Type = List } } } // lambda parses a lambda expression. The opening brace has been seen. func (pn *Primary) lambda(ps *Parser) { pn.Type = Lambda pn.setChunk(ParseChunk(ps)) if !parseSep(pn, ps, '}') { ps.error(errShouldBeRBrace) } } // Braced = '{' Compound { BracedSep Compounds } '}' // BracedSep = { Space | '\n' } [ ',' ] { Space | '\n' } func (pn *Primary) lbrace(ps *Parser) { parseSep(pn, ps, '{') if r := ps.peek(); r == ';' || r == '\n' || IsSpace(r) { pn.lambda(ps) return } pn.Type = Braced // XXX: The compound can be empty, which allows us to parse {,foo}. // Allowing compounds to be empty can be fragile in other cases. pn.addToBraced(ParseCompound(ps, BracedElemExpr)) for isBracedSep(ps.peek()) { parseSpacesAndNewlines(pn, ps) // optional, so ignore the return value parseSep(pn, ps, ',') parseSpacesAndNewlines(pn, ps) pn.addToBraced(ParseCompound(ps, BracedElemExpr)) } if !parseSep(pn, ps, '}') { ps.error(errShouldBeBraceSepOrRBracket) } } func isBracedSep(r rune) bool { return r == ',' || IsSpaceOrNewline(r) } func (pn *Primary) bareword(ps *Parser, ctx ExprCtx) { pn.Type = Bareword defer func() { pn.Value = ps.src[pn.begin:ps.pos] }() for allowedInBareword(ps.peek(), ctx) { ps.next() } } // allowedInBareword returns where a rune is allowed in barewords in the given // expression context. The special strictExpr context queries whether the rune // is allowed in all contexts. // // The following are allowed in barewords: // // * Anything allowed in variable names // * The symbols "./\@%+!" // * The symbol "=", if ctx != lhsExpr && ctx != strictExpr // * The symbol ",", if ctx != bracedExpr && ctx != strictExpr // * The symbols "<>*^", if ctx = commandExpr // // The seemingly weird inclusion of \ is for easier path manipulation in // Windows. func allowedInBareword(r rune, ctx ExprCtx) bool { return allowedInVariableName(r) || r == '.' || r == '/' || r == '\\' || r == '@' || r == '%' || r == '+' || r == '!' || (ctx != LHSExpr && ctx != strictExpr && r == '=') || (ctx != BracedElemExpr && ctx != strictExpr && r == ',') || (ctx == CmdExpr && (r == '<' || r == '>' || r == '*' || r == '^')) } func startsPrimary(r rune, ctx ExprCtx) bool { return r == '\'' || r == '"' || r == '$' || allowedInBareword(r, ctx) || r == '?' || r == '*' || r == '(' || r == '[' || r == '{' } // MapPair = '&' { Space } Compound { Space } Compound type MapPair struct { node Key, Value *Compound } func (mpn *MapPair) parse(ps *Parser) { parseSep(mpn, ps, '&') mpn.setKey(ParseCompound(ps, LHSExpr)) if len(mpn.Key.Indexings) == 0 { ps.error(errShouldBeCompound) } if parseSep(mpn, ps, '=') { parseSpacesAndNewlines(mpn, ps) // Parse value part. mpn.setValue(ParseCompound(ps, NormalExpr)) // The value part can be empty. } } // Sep is the catch-all node type for leaf nodes that lack internal structures // and semantics, and serve solely for syntactic purposes. The parsing of // separators depend on the Parent node; as such it lacks a genuine parse // method. type Sep struct { node } func NewSep(src string, begin, end int) *Sep { return &Sep{node{nil, begin, end, src[begin:end], nil}} } func addSep(n Node, ps *Parser) { var begin int ch := n.Children() if len(ch) > 0 { begin = ch[len(ch)-1].End() } else { begin = n.Begin() } if begin < ps.pos { addChild(n, NewSep(ps.src, begin, ps.pos)) } } func parseSep(n Node, ps *Parser, sep rune) bool { if ps.peek() == sep { ps.next() addSep(n, ps) return true } return false } func parseSpaces(n Node, ps *Parser) { parseSpacesInner(n, ps, IsSpace) } func parseSpacesAndNewlines(n Node, ps *Parser) { parseSpacesInner(n, ps, IsSpaceOrNewline) } func parseSpacesInner(n Node, ps *Parser, isSpace func(rune) bool) { spaces: for { r := ps.peek() switch { case isSpace(r): ps.next() case r == '`': // line continuation ps.next() switch ps.peek() { case '\n': ps.next() case eof: ps.error(errShouldBeNewline) default: ps.backup() break spaces } default: break spaces } } addSep(n, ps) } func IsSpaceOrNewline(r rune) bool { return IsSpace(r) || r == '\n' } func addChild(p Node, ch Node) { p.n().children = append(p.n().children, ch) ch.n().parent = p } elvish-0.11+ds1/parse/parse_test.go000066400000000000000000000231451323000013700172140ustar00rootroot00000000000000package parse import ( "fmt" "os" "testing" ) func a(c ...interface{}) ast { // Shorthand used for checking Compound and levels beneath. return ast{"Chunk/Pipeline/Form", fs{"Head": "a", "Args": c}} } var goodCases = []struct { src string ast ast }{ // Chunk // Smoke test. {"a;b;c\n;d", ast{"Chunk", fs{"Pipelines": []string{"a", "b", "c", "d"}}}}, // Empty chunk should have Pipelines=nil. {"", ast{"Chunk", fs{"Pipelines": nil}}}, // Superfluous newlines and semicolons should not result in empty // pipelines. {" ;\n\n ls \t ;\n", ast{"Chunk", fs{"Pipelines": []string{"ls \t "}}}}, // Pipeline {"a|b|c|d", ast{ "Chunk/Pipeline", fs{"Forms": []string{"a", "b", "c", "d"}}}}, // Newlines are allowed after pipes. {"a| \n \n b", ast{ "Chunk/Pipeline", fs{"Forms": []string{"a", "b"}}}}, // Comments. {"a#haha\nb#lala", ast{ "Chunk", fs{"Pipelines": []string{"a", "b"}}}}, // Form // Smoke test. {"ls x y", ast{"Chunk/Pipeline/Form", fs{ "Head": "ls", "Args": []string{"x", "y"}}}}, // Assignments. {"k=v k[a][b]=v {a,b[1]}=(ha)", ast{"Chunk/Pipeline/Form", fs{ "Assignments": []string{"k=v", "k[a][b]=v", "{a,b[1]}=(ha)"}}}}, // Temporary assignment. {"k=v k[a][b]=v a", ast{"Chunk/Pipeline/Form", fs{ "Assignments": []string{"k=v", "k[a][b]=v"}, "Head": "a"}}}, // Spacey assignment. {"k=v a b = c d", ast{"Chunk/Pipeline/Form", fs{ "Assignments": []string{"k=v"}, "Vars": []string{"a", "b"}, "Args": []string{"c", "d"}}}}, // Redirections {"a >b", ast{"Chunk/Pipeline/Form", fs{ "Head": "a", "Redirs": []ast{ {"Redir", fs{"Mode": Write, "Right": "b"}}}, }}}, // More redirections {"a >>b 2>b 3>&- 4>&1 5d", ast{"Chunk/Pipeline/Form", fs{ "Head": "a", "Redirs": []ast{ {"Redir", fs{"Mode": Append, "Right": "b"}}, {"Redir", fs{"Left": "2", "Mode": Write, "Right": "b"}}, {"Redir", fs{"Left": "3", "Mode": Write, "RightIsFd": true, "Right": "-"}}, {"Redir", fs{"Left": "4", "Mode": Write, "RightIsFd": true, "Right": "1"}}, {"Redir", fs{"Left": "5", "Mode": Read, "Right": "c"}}, {"Redir", fs{"Left": "6", "Mode": ReadWrite, "Right": "d"}}, }, }}}, // Exitus redirection {"a ?>$e", ast{"Chunk/Pipeline/Form", fs{ "Head": "a", "ExitusRedir": ast{"ExitusRedir", fs{"Dest": "$e"}}, }}}, // Options (structure of MapPair tested below with map) {"a &a=1 x &b=2", ast{"Chunk/Pipeline/Form", fs{ "Head": "a", "Args": []string{"x"}, "Opts": []string{"&a=1", "&b=2"}, }}}, // Compound {`a b"foo"?$c*'xyz'`, a(ast{"Compound", fs{ "Indexings": []string{"b", `"foo"`, "?", "$c", "*", "'xyz'"}}})}, // Indexing {"a $b[c][d][\ne\n]", a(ast{"Compound/Indexing", fs{ "Head": "$b", "Indicies": []string{"c", "d", "\ne\n"}, }})}, // Primary // // Single quote {"a '''x''y'''", a(ast{"Compound/Indexing/Primary", fs{ "Type": SingleQuoted, "Value": "'x'y'", }})}, // Double quote {`a "b\^[\x1b\u548c\U0002CE23\123\n\t\\"`, a(ast{"Compound/Indexing/Primary", fs{ "Type": DoubleQuoted, "Value": "b\x1b\x1b\u548c\U0002CE23\123\n\t\\", }})}, // Wildcard {"a * ?", a( ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "*"}}, ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "?"}}, )}, // Variable {"a $x $&f", a( ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "x"}}, ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "&f"}}, )}, // List {"a [] [ ] [1] [ 2] [3 ] [\n 4 \n5\n 6 7 \n]", a( ast{"Compound/Indexing/Primary", fs{ "Type": List, "Elements": []ast{}}}, ast{"Compound/Indexing/Primary", fs{ "Type": List, "Elements": []ast{}}}, ast{"Compound/Indexing/Primary", fs{ "Type": List, "Elements": []string{"1"}}}, ast{"Compound/Indexing/Primary", fs{ "Type": List, "Elements": []string{"2"}}}, ast{"Compound/Indexing/Primary", fs{ "Type": List, "Elements": []string{"3"}}}, ast{"Compound/Indexing/Primary", fs{ "Type": List, "Elements": []string{"4", "5", "6", "7"}}}, )}, // Map {"a [&k=v] [ &k=v] [&k=v ] [ &k=v ] [ &k= v] [&k= \n v] [\n&a=b &c=d \n &e=f\n\n]", a( ast{"Compound/Indexing/Primary", fs{ "Type": Map, "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}}, ast{"Compound/Indexing/Primary", fs{ "Type": Map, "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}}, ast{"Compound/Indexing/Primary", fs{ "Type": Map, "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}}, ast{"Compound/Indexing/Primary", fs{ "Type": Map, "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}}, ast{"Compound/Indexing/Primary", fs{ "Type": Map, "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}}, ast{"Compound/Indexing/Primary", fs{ "Type": Map, "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}}, ast{"Compound/Indexing/Primary", fs{ "Type": Map, "MapPairs": []ast{ {"MapPair", fs{"Key": "a", "Value": "b"}}, {"MapPair", fs{"Key": "c", "Value": "d"}}, {"MapPair", fs{"Key": "e", "Value": "f"}}, }}}, )}, // Empty map {"a [&] [ &] [& ] [ & ]", a( ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}}, ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}}, ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}}, ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}}, )}, // Lambda {"a []{} [ ]{ } []{ echo 233 } [ x y ]{puts $x $y} { put haha}", a( ast{"Compound/Indexing/Primary", fs{ "Type": Lambda, "Elements": []ast{}, "Chunk": "", }}, ast{"Compound/Indexing/Primary", fs{ "Type": Lambda, "Elements": []ast{}, "Chunk": " ", }}, ast{"Compound/Indexing/Primary", fs{ "Type": Lambda, "Elements": []ast{}, "Chunk": " echo 233 ", }}, ast{"Compound/Indexing/Primary", fs{ "Type": Lambda, "Elements": []string{"x", "y"}, "Chunk": "puts $x $y", }}, ast{"Compound/Indexing/Primary", fs{ "Type": Lambda, "Elements": []ast{}, "Chunk": " put haha", }}, )}, // Lambda with arguments and options {"a [a b &k=v]{}", a( ast{"Compound/Indexing/Primary", fs{ "Type": Lambda, "Elements": []string{"a", "b"}, "MapPairs": []string{"&k=v"}, "Chunk": "", }}, )}, // Output capture {"a () (b;c) (c\nd)", a( ast{"Compound/Indexing/Primary", fs{ "Type": OutputCapture, "Chunk": ""}}, ast{"Compound/Indexing/Primary", fs{ "Type": OutputCapture, "Chunk": ast{ "Chunk", fs{"Pipelines": []string{"b", "c"}}, }}}, ast{"Compound/Indexing/Primary", fs{ "Type": OutputCapture, "Chunk": ast{ "Chunk", fs{"Pipelines": []string{"c", "d"}}, }}}, )}, // Exitus capture {"a ?() ?(b;c)", a( ast{"Compound/Indexing/Primary", fs{ "Type": ExceptionCapture, "Chunk": ""}}, ast{"Compound/Indexing/Primary", fs{ "Type": ExceptionCapture, "Chunk": "b;c", }})}, // Braced {"a {,a,c\ng\n}", a( ast{"Compound/Indexing/Primary", fs{ "Type": Braced, "Braced": []string{"", "a", "c", "g", ""}}})}, // Tilde {"a ~xiaq/go", a( ast{"Compound", fs{ "Indexings": []ast{ {"Indexing/Primary", fs{"Type": Tilde, "Value": "~"}}, {"Indexing/Primary", fs{"Type": Bareword, "Value": "xiaq/go"}}, }, }}, )}, // Line continuation: "`\n" is considered whitespace {"a b`\nc", ast{ "Chunk/Pipeline/Form", fs{"Head": "a", "Args": []string{"b", "c"}}}}, } func TestParse(t *testing.T) { for _, tc := range goodCases { bn, err := Parse("[test]", tc.src) if err != nil { t.Errorf("Parse(%q) returns error: %v", tc.src, err) } err = checkParseTree(bn) if err != nil { t.Errorf("Parse(%q) returns bad parse tree: %v", tc.src, err) fmt.Fprintf(os.Stderr, "Parse tree of %q:\n", tc.src) PprintParseTree(bn, os.Stderr) } err = checkAST(bn, tc.ast) if err != nil { t.Errorf("Parse(%q) returns bad AST: %v", tc.src, err) fmt.Fprintf(os.Stderr, "AST of %q:\n", tc.src) PprintAST(bn, os.Stderr) } } } // checkParseTree checks whether the parse tree part of a Node is well-formed. func checkParseTree(n Node) error { children := n.Children() if len(children) == 0 { return nil } // Parent pointers of all children should point to me. for i, ch := range children { if ch.Parent() != n { return fmt.Errorf("parent of child %d (%s) is wrong: %s", i, summary(ch), summary(n)) } } // The Begin of the first child should be equal to mine. if children[0].Begin() != n.Begin() { return fmt.Errorf("gap between node and first child: %s", summary(n)) } // The End of the last child should be equal to mine. nch := len(children) if children[nch-1].End() != n.End() { return fmt.Errorf("gap between node and last child: %s", summary(n)) } // Consecutive children have consecutive position ranges. for i := 0; i < nch-1; i++ { if children[i].End() != children[i+1].Begin() { return fmt.Errorf("gap between child %d and %d of: %s", i, i+1, summary(n)) } } // Check children recursively. for _, ch := range n.Children() { err := checkParseTree(ch) if err != nil { return err } } return nil } var badCases = []struct { src string pos int // expected Begin position of first error }{ // Empty form. {"a|", 2}, // Unopened parens. {")", 0}, {"]", 0}, {"}", 0}, // Unclosed parens. {"a (", 3}, {"a [", 3}, {"a {", 3}, // Bogus ampersand. {"a & &", 4}, {"a [&", 4}, } func TestParseError(t *testing.T) { for _, tc := range badCases { _, err := Parse("[test]", tc.src) if err == nil { t.Errorf("Parse(%q) returns no error", tc.src) continue } posErr0 := err.(*Error).Entries[0] if posErr0.Context.Begin != tc.pos { t.Errorf("Parse(%q) first error begins at %d, want %d. Errors are:%s\n", tc.src, posErr0.Context.Begin, tc.pos, err) } } } elvish-0.11+ds1/parse/parser.go000066400000000000000000000044631323000013700163410ustar00rootroot00000000000000package parse import ( "bytes" "errors" "fmt" "strings" "unicode/utf8" "github.com/elves/elvish/util" ) // Parser maintains some mutable states of parsing. // // NOTE: The str member is assumed to be valid UF-8. type Parser struct { srcName string src string pos int overEOF int cutsets []map[rune]int errors Error } // NewParser creates a new parser from a piece of source text and its name. func NewParser(srcname, src string) *Parser { return &Parser{srcname, src, 0, 0, []map[rune]int{{}}, Error{}} } // Done tells the parser that parsing has completed. func (ps *Parser) Done() { if ps.pos != len(ps.src) { r, _ := utf8.DecodeRuneInString(ps.src[ps.pos:]) ps.error(fmt.Errorf("unexpected rune %q", r)) } } // Errors gets the parsing errors after calling one of the parse* functions. If // the return value is not nil, it is always of type Error. func (ps *Parser) Errors() error { if len(ps.errors.Entries) > 0 { return &ps.errors } return nil } // Source returns the source code that is being parsed. func (ps *Parser) Source() string { return ps.src } const eof rune = -1 func (ps *Parser) peek() rune { if ps.pos == len(ps.src) { return eof } r, _ := utf8.DecodeRuneInString(ps.src[ps.pos:]) return r } func (ps *Parser) hasPrefix(prefix string) bool { return strings.HasPrefix(ps.src[ps.pos:], prefix) } func (ps *Parser) next() rune { if ps.pos == len(ps.src) { ps.overEOF++ return eof } r, s := utf8.DecodeRuneInString(ps.src[ps.pos:]) ps.pos += s return r } func (ps *Parser) backup() { if ps.overEOF > 0 { ps.overEOF-- return } _, s := utf8.DecodeLastRuneInString(ps.src[:ps.pos]) ps.pos -= s } func (ps *Parser) errorp(begin, end int, e error) { ps.errors.Add(e.Error(), util.NewSourceRange(ps.srcName, ps.src, begin, end, nil)) } func (ps *Parser) error(e error) { end := ps.pos if end < len(ps.src) { end++ } ps.errorp(ps.pos, end, e) } func newError(text string, shouldbe ...string) error { if len(shouldbe) == 0 { return errors.New(text) } var buf bytes.Buffer if len(text) > 0 { buf.WriteString(text + ", ") } buf.WriteString("should be " + shouldbe[0]) for i, opt := range shouldbe[1:] { if i == len(shouldbe)-2 { buf.WriteString(" or ") } else { buf.WriteString(", ") } buf.WriteString(opt) } return errors.New(buf.String()) } elvish-0.11+ds1/parse/pprint.go000066400000000000000000000064011323000013700163530ustar00rootroot00000000000000package parse import ( "fmt" "io" "reflect" "strconv" ) const ( maxL int = 10 maxR = 10 indentInc = 2 ) // PprintAST pretty prints the AST part of a Node. func PprintAST(n Node, wr io.Writer) { pprintAST(n, wr, 0, "") } type field struct { name string tag reflect.StructTag value interface{} } var zeroValue reflect.Value func pprintAST(n Node, wr io.Writer, indent int, leading string) { nodeType := reflect.TypeOf((*Node)(nil)).Elem() var childFields, childrenFields, propertyFields []field nt := reflect.TypeOf(n).Elem() nv := reflect.ValueOf(n).Elem() for i := 0; i < nt.NumField(); i++ { f := nt.Field(i) if f.Anonymous { // embedded node struct, skip continue } ft := f.Type fv := nv.Field(i) if ft.Kind() == reflect.Slice { // list of children if ft.Elem().Implements(nodeType) { childrenFields = append(childrenFields, field{f.Name, f.Tag, fv.Interface()}) continue } } else if child, ok := fv.Interface().(Node); ok { // a child node if reflect.Indirect(fv) != zeroValue { childFields = append(childFields, field{f.Name, f.Tag, child}) } continue } // a property propertyFields = append(propertyFields, field{f.Name, f.Tag, fv.Interface()}) } // has only one child and nothing more : coalesce if len(n.Children()) == 1 && n.Children()[0].SourceText() == n.SourceText() { pprintAST(n.Children()[0], wr, indent, leading+nt.Name()+"/") return } // print heading //b := n.n() //fmt.Fprintf(wr, "%*s%s%s %s %d-%d", indent, "", // wr.leading, nt.Name(), compactQuote(b.source(src)), b.begin, b.end) fmt.Fprintf(wr, "%*s%s%s", indent, "", leading, nt.Name()) // print properties for _, pf := range propertyFields { fmtstring := pf.tag.Get("fmt") if len(fmtstring) > 0 { fmt.Fprintf(wr, " %s="+fmtstring, pf.name, pf.value) } else { value := pf.value if s, ok := value.(string); ok { value = compactQuote(s) } fmt.Fprintf(wr, " %s=%v", pf.name, value) } } fmt.Fprint(wr, "\n") // print lone children recursively for _, chf := range childFields { // TODO the name is omitted pprintAST(chf.value.(Node), wr, indent+indentInc, "") } // print children list recursively for _, chf := range childrenFields { children := reflect.ValueOf(chf.value) if children.Len() == 0 { continue } // fmt.Fprintf(wr, "%*s.%s:\n", indent, "", chf.name) for i := 0; i < children.Len(); i++ { n := children.Index(i).Interface().(Node) pprintAST(n, wr, indent+indentInc, "") } } } // PprintParseTree pretty prints the parse tree part of a Node. func PprintParseTree(n Node, wr io.Writer) { pprintParseTree(n, wr, 0) } func pprintParseTree(n Node, wr io.Writer, indent int) { leading := "" for len(n.Children()) == 1 { leading += reflect.TypeOf(n).Elem().Name() + "/" n = n.Children()[0] } fmt.Fprintf(wr, "%*s%s%s\n", indent, "", leading, summary(n)) for _, ch := range n.Children() { pprintParseTree(ch, wr, indent+indentInc) } } func summary(n Node) string { return fmt.Sprintf("%s %s %d-%d", reflect.TypeOf(n).Elem().Name(), compactQuote(n.SourceText()), n.Begin(), n.End()) } func compactQuote(text string) string { if len(text) > maxL+maxR+3 { text = text[0:maxL] + "..." + text[len(text)-maxR:] } return strconv.Quote(text) } elvish-0.11+ds1/parse/pprint_test.go000066400000000000000000000030631323000013700174130ustar00rootroot00000000000000package parse import ( "bytes" "testing" ) var pprintCases = []struct { src string wantAST string wantParseTree string }{ {"ls $x[0]$y[1];echo", `Chunk Pipeline/Form Compound/Indexing/Primary Type=Bareword Value="ls" Compound Indexing Primary Type=Variable Value="x" Array/Compound/Indexing/Primary Type=Bareword Value="0" Indexing Primary Type=Variable Value="y" Array/Compound/Indexing/Primary Type=Bareword Value="1" Pipeline/Form/Compound/Indexing/Primary Type=Bareword Value="echo" `, `Chunk "ls $x[0]$y[1];echo" 0-18 Pipeline/Form "ls $x[0]$y[1]" 0-13 Compound/Indexing/Primary "ls" 0-2 Sep " " 2-3 Compound "$x[0]$y[1]" 3-13 Indexing "$x[0]" 3-8 Primary "$x" 3-5 Sep "[" 5-6 Array/Compound/Indexing/Primary "0" 6-7 Sep "]" 7-8 Indexing "$y[1]" 8-13 Primary "$y" 8-10 Sep "[" 10-11 Array/Compound/Indexing/Primary "1" 11-12 Sep "]" 12-13 Sep ";" 13-14 Pipeline/Form/Compound/Indexing/Primary "echo" 14-18 `}, } func TestPprint(t *testing.T) { for _, tc := range pprintCases { n, err := Parse("[test]", tc.src) if err != nil { t.Error(err) } var b bytes.Buffer PprintAST(n, &b) ast := b.String() if b.String() != tc.wantAST { t.Errorf("PprintAST(%q):\n%s\nwant:\n%s", tc.src, ast, tc.wantAST) } b = bytes.Buffer{} PprintParseTree(n, &b) pt := b.String() if pt != tc.wantParseTree { t.Errorf("PprintParseTree(%q):\n%s\nwant:\n%s", tc.src, pt, tc.wantParseTree) } } } elvish-0.11+ds1/parse/quote.go000066400000000000000000000040671323000013700162020ustar00rootroot00000000000000package parse import ( "bytes" "unicode" ) // Quote returns a representation of s in elvish syntax. Bareword is tried // first, then single quoted string and finally double quoted string. func Quote(s string) string { s, _ = QuoteAs(s, Bareword) return s } // QuoteAs returns a representation of s in elvish syntax, using the syntax // specified by q, which must be one of Bareword, SingleQuoted, or // DoubleQuoted. It returns the quoted string and the actual quoting. func QuoteAs(s string, q PrimaryType) (string, PrimaryType) { if q == DoubleQuoted { // Everything can be quoted using double quotes, return directly. return quoteDouble(s), DoubleQuoted } if s == "" { return "''", SingleQuoted } // Keep track of whether it is a valid bareword. bare := s[0] != '~' for _, r := range s { if !unicode.IsPrint(r) { // Contains unprintable character; force double quote. return quoteDouble(s), DoubleQuoted } if !allowedInBareword(r, strictExpr) { bare = false } } if q == Bareword && bare { return s, Bareword } return quoteSingle(s), SingleQuoted } func quoteSingle(s string) string { var buf bytes.Buffer buf.WriteByte('\'') for _, r := range s { buf.WriteRune(r) if r == '\'' { buf.WriteByte('\'') } } buf.WriteByte('\'') return buf.String() } func rtohex(r rune, w int) []byte { bytes := make([]byte, w) for i := w - 1; i >= 0; i-- { d := byte(r % 16) r /= 16 if d <= 9 { bytes[i] = '0' + d } else { bytes[i] = 'a' + d - 10 } } return bytes } func quoteDouble(s string) string { var buf bytes.Buffer buf.WriteByte('"') for _, r := range s { if e, ok := doubleUnescape[r]; ok { // Takes care of " and \ as well. buf.WriteByte('\\') buf.WriteRune(e) } else if !unicode.IsPrint(r) { buf.WriteByte('\\') if r <= 0xff { buf.WriteByte('x') buf.Write(rtohex(r, 2)) } else if r <= 0xffff { buf.WriteByte('u') buf.Write(rtohex(r, 4)) } else { buf.WriteByte('U') buf.Write(rtohex(r, 8)) } } else { buf.WriteRune(r) } } buf.WriteByte('"') return buf.String() } elvish-0.11+ds1/parse/quote_test.go000066400000000000000000000015431323000013700172350ustar00rootroot00000000000000package parse import ( "testing" "github.com/elves/elvish/tt" ) var quoteTests = tt.Table{ // Empty string is single-quoted. tt.Args("").Rets(`''`), // Bareword when possible. tt.Args("x-y:z@h/d").Rets("x-y:z@h/d"), // Single quote when there are special characters but no unprintable // characters. tt.Args("x$y[]ef'").Rets("'x$y[]ef'''"), // Tilde needs quoting only leading the expression. tt.Args("~x").Rets("'~x'"), tt.Args("x~").Rets("x~"), // Double quote when there is unprintable char. tt.Args("a\nb").Rets(`"a\nb"`), tt.Args("\x1b\"\\").Rets(`"\e\"\\"`), // Commas and equal signs are always quoted, so that the quoted string is // safe for use everywhere. tt.Args("a,b").Rets(`'a,b'`), tt.Args("a=b").Rets(`'a=b'`), } func TestQuote(t *testing.T) { tt.Test(t, tt.Fn("Quote", Quote).ArgsFmt("(%q)").RetsFmt("%q"), quoteTests) } elvish-0.11+ds1/parse/string.go000066400000000000000000000016671323000013700163560ustar00rootroot00000000000000// Code generated by "stringer -type=PrimaryType,RedirMode -output=string.go"; DO NOT EDIT. package parse import "strconv" const _PrimaryType_name = "BadPrimaryBarewordSingleQuotedDoubleQuotedVariableWildcardTildeExceptionCaptureOutputCaptureListLambdaMapBraced" var _PrimaryType_index = [...]uint8{0, 10, 18, 30, 42, 50, 58, 63, 79, 92, 96, 102, 105, 111} func (i PrimaryType) String() string { if i < 0 || i >= PrimaryType(len(_PrimaryType_index)-1) { return "PrimaryType(" + strconv.FormatInt(int64(i), 10) + ")" } return _PrimaryType_name[_PrimaryType_index[i]:_PrimaryType_index[i+1]] } const _RedirMode_name = "BadRedirModeReadWriteReadWriteAppend" var _RedirMode_index = [...]uint8{0, 12, 16, 21, 30, 36} func (i RedirMode) String() string { if i < 0 || i >= RedirMode(len(_RedirMode_index)-1) { return "RedirMode(" + strconv.FormatInt(int64(i), 10) + ")" } return _RedirMode_name[_RedirMode_index[i]:_RedirMode_index[i+1]] } elvish-0.11+ds1/program/000077500000000000000000000000001323000013700150445ustar00rootroot00000000000000elvish-0.11+ds1/program/daemon/000077500000000000000000000000001323000013700163075ustar00rootroot00000000000000elvish-0.11+ds1/program/daemon/daemon.go000066400000000000000000000051461323000013700201070ustar00rootroot00000000000000// Package daemon provides the entry point of the daemon sub-program and helpers // to spawn a daemon process. package daemon import ( "errors" "fmt" "os" "path/filepath" ) // Daemon keeps configurations for the daemon sub-program. It can be used both // from the main function for running the daemon and from another process // (typically the first Elvish shell session) for spawning a daemon. type Daemon struct { // BinPath is the path to the Elvish binary itself, used when forking. This // field is optional only when spawning the daemon. BinPath string // DbPath is the path to the database. DbPath string // SockPath is the path to the socket on which the daemon will serve // requests. SockPath string // LogPathPrefix is used to derive the name of the log file by adding the // pid. LogPathPrefix string } // Main is the entry point of the daemon sub-program. It simply sets the umask // (if relevant) and runs serve. It always return a nil error, since any errors // encountered is logged in the serve function. func (d *Daemon) Main(serve func(string, string)) error { setUmask() serve(d.SockPath, d.DbPath) return nil } // Spawn spawns a daemon process in the background by invoking BinPath, passing // DbPath, SockPath and LogPathPrefix as command-line arguments after resolving // them to absolute paths. A suitable ProcAttr is chosen depending on the OS and // makes sure that the daemon is detached from the current terminal (so that it // is not affected by I/O or signals in the current terminal), and keeps running // after the current process quits. func (d *Daemon) Spawn() error { binPath := d.BinPath // Determine binPath. if binPath == "" { bin, err := os.Executable() if err != nil { return errors.New("cannot find elvish: " + err.Error()) } binPath = bin } var pathError error abs := func(name string, path string) string { if pathError != nil { return "" } if path == "" { pathError = fmt.Errorf("%s is required for spawning daemon", name) return "" } absPath, err := filepath.Abs(path) if err != nil { pathError = fmt.Errorf("cannot resolve %s to absolute path: %s", name, err) } return absPath } binPath = abs("BinPath", binPath) dbPath := abs("DbPath", d.DbPath) sockPath := abs("SockPath", d.SockPath) logPathPrefix := abs("LogPathPrefix", d.LogPathPrefix) if pathError != nil { return pathError } args := []string{ binPath, "-daemon", "-bin", binPath, "-db", dbPath, "-sock", sockPath, "-logprefix", logPathPrefix, } // TODO Redirect daemon stdout and stderr _, err := os.StartProcess(binPath, args, procAttrForSpawn()) return err } elvish-0.11+ds1/program/daemon/daemon_test.go000066400000000000000000000001361323000013700211400ustar00rootroot00000000000000package daemon import "testing" func TestDaemon(t *testing.T) { // XXX(xiaq): Add tests. } elvish-0.11+ds1/program/daemon/sys_unix.go000066400000000000000000000006471323000013700205260ustar00rootroot00000000000000// +build !windows // +build !plan9 package daemon import ( "os" "syscall" "golang.org/x/sys/unix" ) func setUmask() { unix.Umask(0077) } func procAttrForSpawn() *os.ProcAttr { return &os.ProcAttr{ Dir: "/", // cd to / Env: []string{}, // empty environment Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, Sys: &syscall.SysProcAttr{ Setsid: true, // detach from current terminal }, } } elvish-0.11+ds1/program/daemon/sys_windows.go000066400000000000000000000014121323000013700212240ustar00rootroot00000000000000package daemon import ( "os" "syscall" ) func setUmask() { // NOP on windows. } // A subset of possible process creation flags, value taken from // https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx const ( CREATE_BREAKAWAY_FROM_JOB = 0x01000000 CREATE_NEW_PROCESS_GROUP = 0x00000200 DETACHED_PROCESS = 0x00000008 DaemonCreationFlags = CREATE_BREAKAWAY_FROM_JOB | CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS ) func procAttrForSpawn() *os.ProcAttr { return &os.ProcAttr{ Dir: `C:\`, Env: []string{"SystemRoot=" + os.Getenv("SystemRoot")}, // SystemRoot is needed for net.Listen for some reason Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, Sys: &syscall.SysProcAttr{CreationFlags: DaemonCreationFlags}, } } elvish-0.11+ds1/program/json.go000066400000000000000000000005441323000013700163470ustar00rootroot00000000000000package program import ( "bytes" "fmt" ) func quoteJSON(s string) string { var b bytes.Buffer b.WriteRune('"') for _, r := range s { if r == '\\' { b.WriteString(`\\`) } else if r == '"' { b.WriteString(`\"`) } else if r < 0x20 { fmt.Fprintf(&b, `\u%04x`, r) } else { b.WriteRune(r) } } b.WriteRune('"') return b.String() } elvish-0.11+ds1/program/json_test.go000066400000000000000000000005711323000013700174060ustar00rootroot00000000000000package program import "testing" var quoteJSONTests = []struct { in string want string }{ {`a`, `"a"`}, {`"ab\c`, `"\"ab\\c"`}, {"a\x19\x00", `"a\u0019\u0000"`}, } func TestQuoteJSON(t *testing.T) { for _, test := range quoteJSONTests { out := quoteJSON(test.in) if out != test.want { t.Errorf("quoteJSON(%q) = %q, want %q", test.in, out, test.want) } } } elvish-0.11+ds1/program/program.go000066400000000000000000000104371323000013700170470ustar00rootroot00000000000000// Package program provides the entry point to Elvish. Its subpackages // correspond to subprograms of Elvish. package program // This package sets up the basic environment and calls the appropriate // "subprogram", one of the daemon, the terminal interface, or the web // interface. import ( "flag" "fmt" "io" "log" "os" "runtime/pprof" "strconv" "github.com/elves/elvish/program/daemon" "github.com/elves/elvish/program/shell" "github.com/elves/elvish/program/web" "github.com/elves/elvish/util" ) // defaultPort is the default port on which the web interface runs. The number // is chosen because it resembles "elvi". const defaultWebPort = 3171 var logger = util.GetLogger("[main] ") type flagSet struct { flag.FlagSet Log, LogPrefix, CPUProfile string Help, Version, BuildInfo, JSON bool CodeInArg, CompileOnly bool Web bool Port int Daemon bool Forked int Bin, DB, Sock string } func newFlagSet() *flagSet { f := flagSet{} f.Init("elvish", flag.ContinueOnError) f.Usage = func() { usage(os.Stderr, &f) } f.StringVar(&f.Log, "log", "", "a file to write debug log to") f.StringVar(&f.LogPrefix, "logprefix", "", "the prefix for the daemon log file") f.StringVar(&f.CPUProfile, "cpuprofile", "", "write cpu profile to file") f.BoolVar(&f.Help, "help", false, "show usage help and quit") f.BoolVar(&f.Version, "version", false, "show version and quit") f.BoolVar(&f.BuildInfo, "buildinfo", false, "show build info and quit") f.BoolVar(&f.JSON, "json", false, "show output in JSON. Useful with -buildinfo.") f.BoolVar(&f.CodeInArg, "c", false, "take first argument as code to execute") f.BoolVar(&f.CompileOnly, "compileonly", false, "Parse/Compile but do not execute") f.BoolVar(&f.Web, "web", false, "run backend of web interface") f.IntVar(&f.Port, "port", defaultWebPort, "the port of the web backend") f.BoolVar(&f.Daemon, "daemon", false, "run daemon instead of shell") f.StringVar(&f.Bin, "bin", "", "path to the elvish binary") f.StringVar(&f.DB, "db", "", "path to the database") f.StringVar(&f.Sock, "sock", "", "path to the daemon socket") return &f } // usage prints usage to the specified output. It modifies the flagSet; there is // no API for getting the current output of a flag.FlagSet, so we can neither // use the current output of f to output our own usage string, nor restore the // previous value of f's output. func usage(out io.Writer, f *flagSet) { f.SetOutput(out) fmt.Fprintln(out, "Usage: elvish [flags] [script]") fmt.Fprintln(out, "Supported flags:") f.PrintDefaults() } func Main(allArgs []string) int { flag := newFlagSet() err := flag.Parse(allArgs[1:]) if err != nil { // Error and usage messages are already shown. return 2 } // Handle flags common to all subprograms. if flag.CPUProfile != "" { f, err := os.Create(flag.CPUProfile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } if flag.Log != "" { err = util.SetOutputFile(flag.Log) } else if flag.LogPrefix != "" { err = util.SetOutputFile(flag.LogPrefix + strconv.Itoa(os.Getpid())) } if err != nil { fmt.Fprintln(os.Stderr, err) } return FindProgram(flag).Main(flag.Args()) } // Program represents a subprogram. type Program interface { // Main calls the subprogram with arguments. The return value will be used // as the exit status of the entire program. Main(args []string) int } // FindProgram finds a suitable Program according to flags. It does not have any // side effects. func FindProgram(flag *flagSet) Program { switch { case flag.Help: return ShowHelp{flag} case flag.Version: return ShowVersion{} case flag.BuildInfo: return ShowBuildInfo{flag.JSON} case flag.Daemon: if len(flag.Args()) > 0 { return ShowCorrectUsage{"arguments are not allowed with -daemon", flag} } return Daemon{inner: &daemon.Daemon{ BinPath: flag.Bin, DbPath: flag.DB, SockPath: flag.Sock, LogPathPrefix: flag.LogPrefix, }} case flag.Web: if len(flag.Args()) > 0 { return ShowCorrectUsage{"arguments are not allowed with -web", flag} } if flag.CodeInArg { return ShowCorrectUsage{"-c cannot be used together with -web", flag} } return web.New(flag.Bin, flag.Sock, flag.DB, flag.Port) default: return shell.New(flag.Bin, flag.Sock, flag.DB, flag.CodeInArg, flag.CompileOnly) } } elvish-0.11+ds1/program/program_test.go000066400000000000000000000054151323000013700201060ustar00rootroot00000000000000package program import ( "fmt" "testing" "github.com/elves/elvish/program/shell" "github.com/elves/elvish/program/web" ) var findProgramTests = []struct { args []string checker func(Program) bool }{ {[]string{"-help"}, isShowHelp}, {[]string{"-version"}, isShowVersion}, {[]string{"-buildinfo"}, isShowBuildInfo}, {[]string{"-buildinfo", "-json"}, func(p Program) bool { return p.(ShowBuildInfo).JSON }}, {[]string{}, isShell}, {[]string{"-c", "echo"}, func(p Program) bool { return p.(*shell.Shell).Cmd }}, {[]string{"-compileonly"}, func(p Program) bool { return p.(*shell.Shell).CompileOnly }}, {[]string{"-web"}, isWeb}, {[]string{"-web", "x"}, isShowCorrectUsage}, {[]string{"-web", "-c"}, isShowCorrectUsage}, {[]string{"-web", "-port", "233"}, func(p Program) bool { return p.(*web.Web).Port == 233 }}, {[]string{"-daemon"}, isDaemon}, {[]string{"-daemon", "x"}, isShowCorrectUsage}, {[]string{"-bin", "/elvish"}, func(p Program) bool { return p.(*shell.Shell).BinPath == "/elvish" }}, {[]string{"-db", "/db"}, func(p Program) bool { return p.(*shell.Shell).DbPath == "/db" }}, {[]string{"-sock", "/sock"}, func(p Program) bool { return p.(*shell.Shell).SockPath == "/sock" }}, {[]string{"-web", "-bin", "/elvish"}, func(p Program) bool { return p.(*web.Web).BinPath == "/elvish" }}, {[]string{"-web", "-db", "/db"}, func(p Program) bool { return p.(*web.Web).DbPath == "/db" }}, {[]string{"-web", "-sock", "/sock"}, func(p Program) bool { return p.(*web.Web).SockPath == "/sock" }}, {[]string{"-daemon", "-bin", "/elvish"}, func(p Program) bool { return p.(Daemon).inner.BinPath == "/elvish" }}, {[]string{"-daemon", "-db", "/db"}, func(p Program) bool { return p.(Daemon).inner.DbPath == "/db" }}, {[]string{"-daemon", "-sock", "/sock"}, func(p Program) bool { return p.(Daemon).inner.SockPath == "/sock" }}, } func isShowHelp(p Program) bool { _, ok := p.(ShowHelp); return ok } func isShowCorrectUsage(p Program) bool { _, ok := p.(ShowCorrectUsage); return ok } func isShowVersion(p Program) bool { _, ok := p.(ShowVersion); return ok } func isShowBuildInfo(p Program) bool { _, ok := p.(ShowBuildInfo); return ok } func isDaemon(p Program) bool { _, ok := p.(Daemon); return ok } func isWeb(p Program) bool { _, ok := p.(*web.Web); return ok } func isShell(p Program) bool { _, ok := p.(*shell.Shell); return ok } func TestFindProgram(t *testing.T) { for i, test := range findProgramTests { f := parse(test.args) p := FindProgram(f) if !test.checker(p) { t.Errorf("test #%d (args = %q) failed", i, test.args) } } } func parse(args []string) *flagSet { f := newFlagSet() err := f.Parse(args) if err != nil { panic(fmt.Sprintln("bad flags in test", args)) } return f } elvish-0.11+ds1/program/shell/000077500000000000000000000000001323000013700161535ustar00rootroot00000000000000elvish-0.11+ds1/program/shell/editor.go000066400000000000000000000011311323000013700177640ustar00rootroot00000000000000package shell import ( "bufio" "fmt" "io" "os" "strings" ) type editor interface { ReadLine() (string, error) Close() } type minEditor struct { in *bufio.Reader out io.Writer } func newMinEditor(in, out *os.File) *minEditor { return &minEditor{bufio.NewReader(in), out} } func (ed *minEditor) ReadLine() (string, error) { wd, err := os.Getwd() if err != nil { wd = "?" } fmt.Fprintf(ed.out, "%s> ", wd) line, err := ed.in.ReadString('\n') // Chop off the trailing \r on Windows. line = strings.TrimRight(line, "\r\n") return line, err } func (editor *minEditor) Close() { } elvish-0.11+ds1/program/shell/interact.go000066400000000000000000000036611323000013700203210ustar00rootroot00000000000000package shell import ( "bufio" "fmt" "io" "os" "os/signal" "path/filepath" "syscall" "time" "github.com/elves/elvish/edit" "github.com/elves/elvish/eval" "github.com/elves/elvish/sys" "github.com/elves/elvish/util" ) func interact(ev *eval.Evaler, dataDir string) { // Build Editor. var ed editor if sys.IsATTY(os.Stdin) { sigch := make(chan os.Signal) signal.Notify(sigch, syscall.SIGHUP, syscall.SIGINT, sys.SIGWINCH) ed = edit.NewEditor(os.Stdin, os.Stderr, sigch, ev) } else { ed = newMinEditor(os.Stdin, os.Stderr) } defer ed.Close() // Source rc.elv. if dataDir != "" { err := sourceRC(ev, dataDir) if err != nil { util.PprintError(err) } } // Build readLine function. readLine := func() (string, error) { return ed.ReadLine() } cooldown := time.Second usingBasic := false cmdNum := 0 for { cmdNum++ line, err := readLine() if err == io.EOF { break } else if err != nil { fmt.Println("Editor error:", err) if !usingBasic { fmt.Println("Falling back to basic line editor") readLine = basicReadLine usingBasic = true } else { fmt.Println("Don't know what to do, pid is", os.Getpid()) fmt.Println("Restarting editor in", cooldown) time.Sleep(cooldown) if cooldown < time.Minute { cooldown *= 2 } } continue } // No error; reset cooldown. cooldown = time.Second err = ev.SourceText(eval.NewInteractiveSource(line)) if err != nil { util.PprintError(err) } } } func sourceRC(ev *eval.Evaler, dataDir string) error { absPath, err := filepath.Abs(filepath.Join(dataDir, "rc.elv")) if err != nil { if os.IsNotExist(err) { return nil } return fmt.Errorf("cannot get full path of rc.elv: %v", err) } code, err := readFileUTF8(absPath) return ev.SourceText(eval.NewScriptSource("rc.elv", absPath, code)) } func basicReadLine() (string, error) { stdin := bufio.NewReaderSize(os.Stdin, 0) return stdin.ReadString('\n') } elvish-0.11+ds1/program/shell/script.go000066400000000000000000000024071323000013700200110ustar00rootroot00000000000000package shell import ( "errors" "fmt" "io/ioutil" "path/filepath" "unicode/utf8" "github.com/elves/elvish/eval" "github.com/elves/elvish/parse" ) // script evaluates a script. The returned error contains enough context and can // be printed as-is (with util.PprintError). func script(ev *eval.Evaler, args []string, cmd, compileOnly bool) error { arg0 := args[0] ev.SetArgs(args[1:]) var name, path, code string if cmd { name = "code from -c" path = "" code = arg0 } else { var err error name = arg0 path, err = filepath.Abs(name) if err != nil { return fmt.Errorf("cannot get full path of script %q: %v", name, err) } code, err = readFileUTF8(path) if err != nil { return fmt.Errorf("cannot read script %q: %v", name, err) } } n, err := parse.Parse(name, code) if err != nil { return err } src := eval.NewScriptSource(name, path, code) op, err := ev.Compile(n, src) if err != nil { return err } if compileOnly { return nil } return ev.Eval(op, src) } var errSourceNotUTF8 = errors.New("source is not UTF-8") func readFileUTF8(fname string) (string, error) { bytes, err := ioutil.ReadFile(fname) if err != nil { return "", err } if !utf8.Valid(bytes) { return "", errSourceNotUTF8 } return string(bytes), nil } elvish-0.11+ds1/program/shell/shell.go000066400000000000000000000026771323000013700176250ustar00rootroot00000000000000// Package shell is the entry point for the terminal interface of Elvish. package shell import ( "fmt" "os" "os/signal" "syscall" "github.com/elves/elvish/runtime" "github.com/elves/elvish/sys" "github.com/elves/elvish/util" ) var logger = util.GetLogger("[shell] ") // Shell keeps flags to the shell. type Shell struct { BinPath string SockPath string DbPath string Cmd bool CompileOnly bool } func New(binpath, sockpath, dbpath string, cmd, compileonly bool) *Shell { return &Shell{binpath, sockpath, dbpath, cmd, compileonly} } // Main runs Elvish using the default terminal interface. It blocks until Elvish // quites, and returns the exit code. func (sh *Shell) Main(args []string) int { defer rescue() ev, dataDir := runtime.InitRuntime(sh.BinPath, sh.SockPath, sh.DbPath) defer runtime.CleanupRuntime(ev) handleSignals() if len(args) > 0 { err := script(ev, args, sh.Cmd, sh.CompileOnly) if err != nil { util.PprintError(err) return 2 } } else { interact(ev, dataDir) } return 0 } // Global panic handler. func rescue() { r := recover() if r != nil { println() fmt.Println(r) print(sys.DumpStack()) println("\nexecing recovery shell /bin/sh") syscall.Exec("/bin/sh", []string{"/bin/sh"}, os.Environ()) } } func handleSignals() { sigs := make(chan os.Signal) signal.Notify(sigs) go func() { for sig := range sigs { logger.Println("signal", sig) handleSignal(sig) } }() } elvish-0.11+ds1/program/shell/shell_test.go000066400000000000000000000001351323000013700206470ustar00rootroot00000000000000package shell import "testing" func TestShell(t *testing.T) { // TODO(xiaq): Add tests. } elvish-0.11+ds1/program/shell/signal_unix.go000066400000000000000000000004351323000013700210240ustar00rootroot00000000000000// +build !windows,!plan9 package shell import ( "fmt" "os" "syscall" "github.com/elves/elvish/sys" ) func handleSignal(sig os.Signal) { switch sig { case syscall.SIGHUP: syscall.Kill(0, syscall.SIGHUP) os.Exit(0) case syscall.SIGUSR1: fmt.Print(sys.DumpStack()) } } elvish-0.11+ds1/program/shell/signal_windows.go000066400000000000000000000001041323000013700215240ustar00rootroot00000000000000package shell import ( "os" ) func handleSignal(_ os.Signal) { } elvish-0.11+ds1/program/subprograms.go000066400000000000000000000024351323000013700177430ustar00rootroot00000000000000package program import ( "fmt" "os" "github.com/elves/elvish/build" daemonsvc "github.com/elves/elvish/daemon" "github.com/elves/elvish/program/daemon" ) // ShowHelp shows help message. type ShowHelp struct { flag *flagSet } func (s ShowHelp) Main([]string) int { usage(os.Stdout, s.flag) return 0 } type ShowCorrectUsage struct { message string flag *flagSet } func (s ShowCorrectUsage) Main([]string) int { usage(os.Stderr, s.flag) return 2 } // ShowVersion shows the version. type ShowVersion struct{} func (ShowVersion) Main([]string) int { fmt.Println(build.Version) fmt.Fprintln(os.Stderr, "-version is deprecated and will be removed in 0.12. Use -buildinfo instead.") return 0 } // ShowBuildInfo shows build information. type ShowBuildInfo struct { JSON bool } func (info ShowBuildInfo) Main([]string) int { if info.JSON { fmt.Printf("{version: %s, builder: %s}\n", quoteJSON(build.Version), quoteJSON(build.Builder)) } else { fmt.Println("version:", build.Version) fmt.Println("builder:", build.Builder) } return 0 } // Daemon runs the daemon subprogram. type Daemon struct { inner *daemon.Daemon } func (d Daemon) Main([]string) int { err := d.inner.Main(daemonsvc.Serve) if err != nil { logger.Println("daemon error:", err) return 2 } return 0 } elvish-0.11+ds1/program/web/000077500000000000000000000000001323000013700156215ustar00rootroot00000000000000elvish-0.11+ds1/program/web/embed-html000077500000000000000000000002561323000013700175700ustar00rootroot00000000000000#!/usr/bin/env elvish out = ./embedded_html.go { echo "package web" print "const mainPageHTML = `" cat main.html | sed 's/`/`+"`"+`/g' echo "`" } > $out gofmt -w $out elvish-0.11+ds1/program/web/embedded_html.go000066400000000000000000000042371323000013700207330ustar00rootroot00000000000000package web const mainPageHTML = `
` elvish-0.11+ds1/program/web/main.html000066400000000000000000000041721323000013700174370ustar00rootroot00000000000000
elvish-0.11+ds1/program/web/web.go000066400000000000000000000103401323000013700167230ustar00rootroot00000000000000// Package web is the entry point for the backend of the web interface of // Elvish. package web //go:generate ./embed-html import ( "encoding/json" "fmt" "io" "io/ioutil" "log" "net/http" "os" "github.com/elves/elvish/eval" "github.com/elves/elvish/eval/types" "github.com/elves/elvish/parse" "github.com/elves/elvish/runtime" ) type Web struct { BinPath string SockPath string DbPath string Port int } type httpHandler struct { ev *eval.Evaler } type ExecuteResponse struct { OutBytes string OutValues []types.Value ErrBytes string Err string } func New(binpath, sockpath, dbpath string, port int) *Web { return &Web{binpath, sockpath, dbpath, port} } func (web *Web) Main([]string) int { ev, _ := runtime.InitRuntime(web.BinPath, web.SockPath, web.DbPath) defer runtime.CleanupRuntime(ev) h := httpHandler{ev} http.HandleFunc("/", h.handleMainPage) http.HandleFunc("/execute", h.handleExecute) addr := fmt.Sprintf("localhost:%d", web.Port) log.Println("going to listen", addr) err := http.ListenAndServe(addr, nil) log.Println(err) return 0 } func (h httpHandler) handleMainPage(w http.ResponseWriter, r *http.Request) { _, err := w.Write([]byte(mainPageHTML)) if err != nil { log.Println("cannot write response:", err) } } func (h httpHandler) handleExecute(w http.ResponseWriter, r *http.Request) { bytes, err := ioutil.ReadAll(r.Body) if err != nil { log.Println("cannot read request body:", err) return } code := string(bytes) outBytes, outValues, errBytes, err := evalAndCollect(h.ev, code) errText := "" if err != nil { errText = err.Error() } responseBody, err := json.Marshal( &ExecuteResponse{string(outBytes), outValues, string(errBytes), errText}) if err != nil { log.Println("cannot marshal response body:", err) } _, err = w.Write(responseBody) if err != nil { log.Println("cannot write response:", err) } } const ( outFileBufferSize = 1024 outChanBufferSize = 32 ) // evalAndCollect evaluates a piece of code with null stdin, and stdout and // stderr connected to pipes (value part of stderr being a blackhole), and // return the results collected on stdout and stderr, and the possible error // that occurred. func evalAndCollect(ev *eval.Evaler, code string) ( outBytes []byte, outValues []types.Value, errBytes []byte, err error) { node, err := parse.Parse("[web]", code) if err != nil { return } src := eval.NewInteractiveSource(code) op, err := ev.Compile(node, src) if err != nil { return } outFile, chanOutBytes := makeBytesWriterAndCollect() outChan, chanOutValues := makeValuesWriterAndCollect() errFile, chanErrBytes := makeBytesWriterAndCollect() ports := []*eval.Port{ eval.DevNullClosedChan, {File: outFile, Chan: outChan}, {File: errFile, Chan: eval.BlackholeChan}, } err = ev.EvalWithPorts(ports, op, src) outFile.Close() close(outChan) errFile.Close() return <-chanOutBytes, <-chanOutValues, <-chanErrBytes, err } // makeBytesWriterAndCollect makes an in-memory file that can be written to, and // the written bytes will be collected in a byte slice that will be put on a // channel as soon as the writer is closed. func makeBytesWriterAndCollect() (*os.File, <-chan []byte) { r, w, err := os.Pipe() // os.Pipe returns error only on resource exhaustion. if err != nil { panic(err) } chanCollected := make(chan []byte) go func() { var ( collected []byte buf [outFileBufferSize]byte ) for { n, err := r.Read(buf[:]) collected = append(collected, buf[:n]...) if err != nil { if err != io.EOF { log.Println("error when reading output pipe:", err) } break } } r.Close() chanCollected <- collected }() return w, chanCollected } // makeValuesWriterAndCollect makes a Value channel for writing, and the written // values will be collected in a Value slice that will be put on a channel as // soon as the writer is closed. func makeValuesWriterAndCollect() (chan types.Value, <-chan []types.Value) { chanValues := make(chan types.Value, outChanBufferSize) chanCollected := make(chan []types.Value) go func() { var collected []types.Value for { for v := range chanValues { collected = append(collected, v) } chanCollected <- collected } }() return chanValues, chanCollected } elvish-0.11+ds1/program/web/web_test.go000066400000000000000000000001311323000013700177570ustar00rootroot00000000000000package web import "testing" func TestWeb(t *testing.T) { // TODO(xiaq): Add tests. } elvish-0.11+ds1/runtime/000077500000000000000000000000001323000013700150605ustar00rootroot00000000000000elvish-0.11+ds1/runtime/runtime.go000066400000000000000000000140411323000013700170720ustar00rootroot00000000000000// Package runtime assembles the Elvish runtime. package runtime import ( "errors" "fmt" "net/rpc" "os" "path/filepath" "time" "github.com/boltdb/bolt" "github.com/elves/elvish/daemon" "github.com/elves/elvish/eval" daemonmod "github.com/elves/elvish/eval/daemon" "github.com/elves/elvish/eval/re" daemonp "github.com/elves/elvish/program/daemon" "github.com/elves/elvish/store/storedefs" "github.com/elves/elvish/util" ) var logger = util.GetLogger("[runtime] ") const ( daemonWaitLoops = 100 daemonWaitPerLoop = 10 * time.Millisecond ) type daemonStatus int const ( daemonOK daemonStatus = iota sockfileMissing sockfileOtherError connectionShutdown connectionOtherError daemonInvalidDB daemonOutdated ) const ( daemonWontWorkMsg = "Daemon-related functions will likely not work." connectionShutdownFmt = "Socket file %s exists but is not responding to request. This is likely due to abnormal shutdown of the daemon. Going to remove socket file and re-spawn a daemon.\n" ) var errInvalidDB = errors.New("daemon reported that database is invalid. If you upgraded Elvish from a pre-0.10 version, you need to upgrade your database by following instructions in https://github.com/elves/upgrade-db-for-0.10/") // InitRuntime initializes the runtime. The caller is responsible for calling // CleanupRuntime at some point. func InitRuntime(binpath, sockpath, dbpath string) (*eval.Evaler, string) { var dataDir string var err error // Determine data directory. dataDir, err = storedefs.EnsureDataDir() if err != nil { fmt.Fprintln(os.Stderr, "warning: cannot create data directory ~/.elvish") } else { if dbpath == "" { dbpath = filepath.Join(dataDir, "db") } } // Determine runtime directory. runDir, err := getSecureRunDir() if err != nil { fmt.Fprintln(os.Stderr, "cannot get runtime dir /tmp/elvish-$uid, falling back to data dir ~/.elvish:", err) runDir = dataDir } if sockpath == "" { sockpath = filepath.Join(runDir, "sock") } ev := eval.NewEvaler() ev.SetLibDir(filepath.Join(dataDir, "lib")) ev.InstallModule("re", re.Ns()) if sockpath != "" && dbpath != "" { spawner := &daemonp.Daemon{ BinPath: binpath, DbPath: dbpath, SockPath: sockpath, LogPathPrefix: filepath.Join(runDir, "daemon.log-"), } // TODO(xiaq): Connect to daemon and install daemon module // asynchronously. client, err := connectToDaemon(sockpath, spawner) if err != nil { fmt.Fprintln(os.Stderr, "Cannot connect to daemon:", err) fmt.Fprintln(os.Stderr, daemonWontWorkMsg) } // Even if error is not nil, we install daemon-related functionalities // anyway. Daemon may eventually come online and become functional. ev.InstallDaemonClient(client) ev.InstallModule("daemon", daemonmod.Ns(client, spawner)) } return ev, dataDir } func connectToDaemon(sockpath string, spawner *daemonp.Daemon) (*daemon.Client, error) { cl := daemon.NewClient(sockpath) status, err := detectDaemon(sockpath, cl) shouldSpawn := false switch status { case daemonOK: case sockfileMissing: shouldSpawn = true case sockfileOtherError: return cl, fmt.Errorf("socket file %s inaccessible: %v", sockpath, err) case connectionShutdown: fmt.Fprintf(os.Stderr, connectionShutdownFmt, sockpath) err := os.Remove(sockpath) if err != nil { return cl, fmt.Errorf("failed to remove socket file: %v", err) } shouldSpawn = true case connectionOtherError: return cl, fmt.Errorf("unexpected RPC error on socket %s: %v", sockpath, err) case daemonInvalidDB: return cl, errInvalidDB case daemonOutdated: fmt.Fprintln(os.Stderr, "Daemon is outdated; going to kill old daemon and re-spawn") err := killDaemon(cl) if err != nil { return cl, fmt.Errorf("failed to kill old daemon: %v", err) } shouldSpawn = true default: return cl, fmt.Errorf("code bug: unknown daemon status %d", status) } if !shouldSpawn { return cl, nil } err = spawner.Spawn() if err != nil { return cl, fmt.Errorf("failed to spawn daemon: %v", err) } logger.Println("Spawned daemon") // Wait for daemon to come online for i := 0; i <= daemonWaitLoops; i++ { cl.ResetConn() status, err := detectDaemon(sockpath, cl) switch status { case daemonOK: return cl, nil case sockfileMissing: // Continue waiting case sockfileOtherError: return cl, fmt.Errorf("socket file %s inaccessible: %v", sockpath, err) case connectionShutdown: // Continue waiting case connectionOtherError: return cl, fmt.Errorf("unexpected RPC error on socket %s: %v", sockpath, err) case daemonInvalidDB: return cl, errInvalidDB case daemonOutdated: return cl, fmt.Errorf("code bug: newly spawned daemon is outdated") default: return cl, fmt.Errorf("code bug: unknown daemon status %d", status) } time.Sleep(daemonWaitPerLoop) } return cl, fmt.Errorf("daemon unreachable after waiting for %s", daemonWaitLoops*daemonWaitPerLoop) } func detectDaemon(sockpath string, cl *daemon.Client) (daemonStatus, error) { _, err := os.Stat(sockpath) if err != nil { if os.IsNotExist(err) { return sockfileMissing, err } return sockfileOtherError, err } version, err := cl.Version() if err != nil { switch { case err == rpc.ErrShutdown: return connectionShutdown, err case err.Error() == bolt.ErrInvalid.Error(): return daemonInvalidDB, err default: return connectionOtherError, err } } if version < daemon.Version { return daemonOutdated, nil } return daemonOK, nil } func killDaemon(cl *daemon.Client) error { pid, err := cl.Pid() if err != nil { return fmt.Errorf("cannot get pid of daemon: %v", err) } process, err := os.FindProcess(pid) if err != nil { return fmt.Errorf("cannot find daemon process (pid=%d): %v", pid, err) } return process.Kill() } // CleanupRuntime cleans up the runtime. func CleanupRuntime(ev *eval.Evaler) { if ev.DaemonClient != nil { err := ev.DaemonClient.Close() if err != nil { fmt.Fprintln(os.Stderr, "warning: failed to close connection to daemon:", err) } } ev.Close() } var ( ErrBadOwner = errors.New("bad owner") ErrBadPermission = errors.New("bad permission") ) elvish-0.11+ds1/runtime/runtime_test.go000066400000000000000000000001411323000013700201250ustar00rootroot00000000000000package runtime import "testing" func TestRuntime(t *testing.T) { // TODO(xiaq): Add tests. } elvish-0.11+ds1/runtime/sys_unix.go000066400000000000000000000015141323000013700172710ustar00rootroot00000000000000// +build !windows,!plan9 package runtime import ( "fmt" "os" "path/filepath" "syscall" ) // getSecureRunDir stats elvish-$uid under the default temp dir, creating it if // it doesn't yet exist, and return the directory name if it has the correct // owner and permission. func getSecureRunDir() (string, error) { uid := os.Getuid() runDir := filepath.Join(os.TempDir(), fmt.Sprintf("elvish-%d", uid)) err := os.MkdirAll(runDir, 0700) if err != nil { return "", fmt.Errorf("mkdir: %v", err) } info, err := os.Stat(runDir) if err != nil { return "", err } return runDir, checkExclusiveAccess(info, uid) } func checkExclusiveAccess(info os.FileInfo, uid int) error { stat := info.Sys().(*syscall.Stat_t) if int(stat.Uid) != uid { return ErrBadOwner } if stat.Mode&077 != 0 { return ErrBadPermission } return nil } elvish-0.11+ds1/runtime/sys_windows.go000066400000000000000000000007071323000013700200030ustar00rootroot00000000000000package runtime import ( "fmt" "os" "path/filepath" ) // getSecureRunDir stats elvish-$USERNAME under the default temp dir, creating // it if it doesn't yet exist, and return the directory name. func getSecureRunDir() (string, error) { username := os.Getenv("USERNAME") runDir := filepath.Join(os.TempDir(), "elvish-"+username) err := os.MkdirAll(runDir, 0700) if err != nil { return "", fmt.Errorf("mkdir: %v", err) } return runDir, nil } elvish-0.11+ds1/store/000077500000000000000000000000001323000013700145315ustar00rootroot00000000000000elvish-0.11+ds1/store/cmd.go000066400000000000000000000077021323000013700156310ustar00rootroot00000000000000package store import ( "bytes" "encoding/binary" "github.com/boltdb/bolt" "github.com/elves/elvish/store/storedefs" ) func init() { initDB["initialize command history table"] = func(db *bolt.DB) error { return db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(BucketCmd)) return err }) } } const BucketCmd = "cmd" // NextCmdSeq returns the next sequence number of the command history. func (s *Store) NextCmdSeq() (int, error) { var seq uint64 err := s.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketCmd)) seq = b.Sequence() + 1 return nil }) return int(seq), err } // AddCmd adds a new command to the command history. func (s *Store) AddCmd(cmd string) (int, error) { var ( seq uint64 err error ) err = s.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketCmd)) seq, err = b.NextSequence() if err != nil { return err } return b.Put(marshalSeq(seq), []byte(cmd)) }) return int(seq), err } // RemoveCmd removes a command from command history referenced by // sequence. func (s *Store) RemoveCmd(seq int) error { return s.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketCmd)) return b.Delete(marshalSeq(uint64(seq))) }) } // Cmd queries the command history item with the specified sequence number. func (s *Store) Cmd(seq int) (string, error) { var cmd string err := s.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketCmd)) if v := b.Get(marshalSeq(uint64(seq))); v == nil { return storedefs.ErrNoMatchingCmd } else { cmd = string(v) } return nil }) return cmd, err } // IterateCmds iterates all the commands in the specified range, and calls the // callback with the content of each command sequentially. func (s *Store) IterateCmds(from, upto int, f func(string) bool) error { return s.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketCmd)) c := b.Cursor() for k, v := c.Seek(marshalSeq(uint64(from))); k != nil && unmarshalSeq(k) < uint64(upto); k, v = c.Next() { if !f(string(v)) { break } } return nil }) } // Cmds returns the contents of all commands within the specified range. func (s *Store) Cmds(from, upto int) ([]string, error) { var cmds []string err := s.IterateCmds(from, upto, func(cmd string) bool { cmds = append(cmds, cmd) return true }) return cmds, err } // NextCmd finds the first command after the given sequence number (inclusive) // with the given prefix. func (s *Store) NextCmd(from int, prefix string) (int, string, error) { var ( seq int cmd string found bool ) err := s.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketCmd)) c := b.Cursor() p := []byte(prefix) for k, v := c.Seek(marshalSeq(uint64(from))); k != nil; k, v = c.Next() { if bytes.HasPrefix(v, p) { seq = int(unmarshalSeq(k)) cmd = string(v) found = true break } } return nil }) if !found { return 0, "", storedefs.ErrNoMatchingCmd } return seq, cmd, err } // PrevCmd finds the last command before the given sequence number (exclusive) // with the given prefix. func (s *Store) PrevCmd(upto int, prefix string) (int, string, error) { var ( seq int cmd string found bool ) err := s.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketCmd)) c := b.Cursor() p := []byte(prefix) k, v := c.Seek(marshalSeq(uint64(upto))) if k == nil { // upto > LAST k, v = c.Last() if k == nil { return nil } } else { k, v = c.Prev() // upto exists, find the previous one } for ; k != nil; k, v = c.Prev() { if bytes.HasPrefix(v, p) { seq = int(unmarshalSeq(k)) cmd = string(v) found = true break } } return nil }) if !found { return 0, "", storedefs.ErrNoMatchingCmd } return seq, cmd, err } func marshalSeq(seq uint64) []byte { b := make([]byte, 8) binary.BigEndian.PutUint64(b, uint64(seq)) return b } func unmarshalSeq(key []byte) uint64 { return binary.BigEndian.Uint64(key) } elvish-0.11+ds1/store/cmd_test.go000066400000000000000000000042551323000013700166700ustar00rootroot00000000000000package store import ( "testing" "github.com/elves/elvish/store/storedefs" ) var ( cmds = []string{"echo foo", "put bar", "put lorem", "echo bar"} searches = []struct { next bool seq int prefix string wantedSeq int wantedCmd string wantedErr error }{ {false, 5, "echo", 4, "echo bar", nil}, {false, 5, "put", 3, "put lorem", nil}, {false, 4, "echo", 1, "echo foo", nil}, {false, 3, "f", 0, "", storedefs.ErrNoMatchingCmd}, {false, 1, "", 0, "", storedefs.ErrNoMatchingCmd}, {true, 1, "echo", 1, "echo foo", nil}, {true, 1, "put", 2, "put bar", nil}, {true, 2, "echo", 4, "echo bar", nil}, {true, 4, "put", 0, "", storedefs.ErrNoMatchingCmd}, } ) func TestCmd(t *testing.T) { startSeq, err := tStore.NextCmdSeq() if startSeq != 1 || err != nil { t.Errorf("tStore.NextCmdSeq() => (%v, %v), want (1, nil)", startSeq, err) } for i, cmd := range cmds { wantSeq := startSeq + i seq, err := tStore.AddCmd(cmd) if seq != wantSeq || err != nil { t.Errorf("tStore.AddCmd(%v) => (%v, %v), want (%v, nil)", cmd, seq, err, wantSeq) } } endSeq, err := tStore.NextCmdSeq() wantedEndSeq := startSeq + len(cmds) if endSeq != wantedEndSeq || err != nil { t.Errorf("tStore.NextCmdSeq() => (%v, %v), want (%v, nil)", endSeq, err, wantedEndSeq) } for i, wantedCmd := range cmds { seq := i + startSeq cmd, err := tStore.Cmd(seq) if cmd != wantedCmd || err != nil { t.Errorf("tStore.Cmd(%v) => (%v, %v), want (%v, nil)", seq, cmd, err, wantedCmd) } } for _, tt := range searches { f := tStore.PrevCmd funcname := "tStore.PrevCmd" if tt.next { f = tStore.NextCmd funcname = "tStore.NextCmd" } seq, cmd, err := f(tt.seq, tt.prefix) if seq != tt.wantedSeq || cmd != tt.wantedCmd || err != tt.wantedErr { t.Errorf("%s(%v, %v) => (%v, %v, %v), want (%v, %v, %v)", funcname, tt.seq, tt.prefix, seq, cmd, err, tt.wantedSeq, tt.wantedCmd, tt.wantedErr) } } if err := tStore.RemoveCmd(1); err != nil { t.Error("Failed to remove cmd") } if seq, err := tStore.Cmd(1); err != storedefs.ErrNoMatchingCmd { t.Errorf("Cmd(1) => (%v, %v), want (%v, %v)", seq, err, "", storedefs.ErrNoMatchingCmd) } } elvish-0.11+ds1/store/dir.go000066400000000000000000000047611323000013700156460ustar00rootroot00000000000000package store import ( "sort" "strconv" "github.com/boltdb/bolt" "github.com/elves/elvish/store/storedefs" ) const ( scoreDecay = 0.986 // roughly 0.5^(1/50) scoreIncrement = 10 scorePrecision = 6 ) const BucketDir = "dir" func init() { initDB["initialize directory history table"] = func(db *bolt.DB) error { return db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(BucketDir)) return err }) } } func marshalScore(score float64) []byte { return []byte(strconv.FormatFloat(score, 'E', scorePrecision, 64)) } func unmarshalScore(data []byte) float64 { f, _ := strconv.ParseFloat(string(data), 64) return f } // AddDir adds a directory to the directory history. func (s *Store) AddDir(d string, incFactor float64) error { return s.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketDir)) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { score := unmarshalScore(v) * scoreDecay b.Put(k, marshalScore(score)) } k := []byte(d) score := float64(0) if v := b.Get(k); v != nil { score = unmarshalScore(v) } score = score + scoreIncrement*incFactor return b.Put(k, marshalScore(score)) }) } // AddDir adds a directory and its score to history. func (s *Store) AddDirRaw(d string, score float64) error { return s.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketDir)) return b.Put([]byte(d), marshalScore(score)) }) } // RemoveDir removes a directory record from history. func (s *Store) RemoveDir(d string) error { return s.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketDir)) return b.Delete([]byte(d)) }) } // Dirs lists all directories in the directory history whose names are not // in the blacklist. The results are ordered by scores in descending order. func (s *Store) Dirs(blacklist map[string]struct{}) ([]storedefs.Dir, error) { var dirs []storedefs.Dir err := s.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketDir)) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { d := string(k) if _, ok := blacklist[d]; ok { continue } dirs = append(dirs, storedefs.Dir{ Path: d, Score: unmarshalScore(v), }) } sort.Sort(sort.Reverse(dirList(dirs))) return nil }) return dirs, err } type dirList []storedefs.Dir func (dl dirList) Len() int { return len(dl) } func (dl dirList) Less(i, j int) bool { return dl[i].Score < dl[j].Score } func (dl dirList) Swap(i, j int) { dl[i], dl[j] = dl[j], dl[i] } elvish-0.11+ds1/store/dir_test.go000066400000000000000000000013301323000013700166720ustar00rootroot00000000000000package store import ( "reflect" "testing" "github.com/elves/elvish/store/storedefs" ) var ( dirsToAdd = []string{"/usr/local", "/usr", "/usr/bin", "/usr"} black = map[string]struct{}{"/usr/local": {}} wantedDirs = []storedefs.Dir{ {"/usr", scoreIncrement*scoreDecay*scoreDecay + scoreIncrement}, {"/usr/bin", scoreIncrement * scoreDecay}} ) func TestDir(t *testing.T) { for _, path := range dirsToAdd { err := tStore.AddDir(path, 1) if err != nil { t.Errorf("tStore.AddDir(%q) => %v, want ", path, err) } } dirs, err := tStore.Dirs(black) if err != nil || !reflect.DeepEqual(dirs, wantedDirs) { t.Errorf(`tStore.ListDirs() => (%v, %v), want (%v, )`, dirs, err, wantedDirs) } } elvish-0.11+ds1/store/schema_version.go000066400000000000000000000020461323000013700200670ustar00rootroot00000000000000package store import ( "strconv" "github.com/boltdb/bolt" ) // SchemaVersion is the current schema version. It should be bumped every time a // backwards-incompatible change has been made to the schema. const SchemaVersion = 1 const BucketSchema = "schema" func init() { initDB["record schema version"] = func(db *bolt.DB) error { return db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte(BucketSchema)) if err != nil { return err } return b.Put([]byte("schema"), []byte(strconv.FormatUint(uint64(SchemaVersion), 10))) }) } } // SchemaUpToDate returns whether the database has the current or newer version // of the schema. func SchemaUpToDate(db *bolt.DB) bool { var version uint64 err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketSchema)) if b == nil { return ErrInvalidBucket } if v := b.Get([]byte("version")); v != nil { version, _ = strconv.ParseUint(string(v), 0, 0) } return nil }) if err != nil { return false } return version == SchemaVersion } elvish-0.11+ds1/store/shared_var.go000066400000000000000000000023311323000013700171750ustar00rootroot00000000000000package store import ( "errors" "github.com/boltdb/bolt" ) // ErrNoVar is returned by (*Store).GetSharedVar when there is no such variable. var ErrNoVar = errors.New("no such variable") const BucketSharedVar = "shared_var" func init() { initDB["initialize shared variable table"] = func(db *bolt.DB) error { return db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(BucketSharedVar)) return err }) } } // SharedVar gets the value of a shared variable. func (s *Store) SharedVar(n string) (string, error) { var value string err := s.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketSharedVar)) if v := b.Get([]byte(n)); v == nil { return ErrNoVar } else { value = string(v) return nil } }) return value, err } // SetSharedVar sets the value of a shared variable. func (s *Store) SetSharedVar(n, v string) error { return s.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketSharedVar)) return b.Put([]byte(n), []byte(v)) }) } // DelSharedVar deletes a shared variable. func (s *Store) DelSharedVar(n string) error { return s.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketSharedVar)) return b.Delete([]byte(n)) }) } elvish-0.11+ds1/store/shared_var_test.go000066400000000000000000000021661323000013700202420ustar00rootroot00000000000000package store import "testing" func TestSharedVar(t *testing.T) { varname := "foo" value1 := "lorem ipsum" value2 := "o mores, o tempora" // Getting an nonexistent variable should return ErrNoVar. _, err := tStore.SharedVar(varname) if err != ErrNoVar { t.Error("want ErrNoVar, got", err) } // Setting a variable for the first time creates it. err = tStore.SetSharedVar(varname, value1) if err != nil { t.Error("want no error, got", err) } v, err := tStore.SharedVar(varname) if v != value1 || err != nil { t.Errorf("want %q and no error, got %q and %v", value1, v, err) } // Setting an existing variable updates its value. err = tStore.SetSharedVar(varname, value2) if err != nil { t.Error("want no error, got", err) } v, err = tStore.SharedVar(varname) if v != value2 || err != nil { t.Errorf("want %q and no error, got %q and %v", value2, v, err) } // After deleting a variable, access to it cause ErrNoVar. err = tStore.DelSharedVar(varname) if err != nil { t.Error("want no error, got", err) } _, err = tStore.SharedVar(varname) if err != ErrNoVar { t.Error("want ErrNoVar, got", err) } } elvish-0.11+ds1/store/store.go000066400000000000000000000044121323000013700162150ustar00rootroot00000000000000// Package store abstracts the persistent storage used by elvish. package store import ( "errors" "fmt" "sync" "time" "github.com/elves/elvish/store/storedefs" "github.com/elves/elvish/util" "github.com/boltdb/bolt" ) var logger = util.GetLogger("[store] ") var initDB = map[string](func(*bolt.DB) error){} var ErrInvalidBucket = errors.New("invalid bucket") // Store is the permanent storage backend for elvish. It is not thread-safe. In // particular, the store may be closed while another goroutine is still // accessing the store. To prevent bad things from happening, every time the // main goroutine spawns a new goroutine to operate on the store, it should call // Waits.Add(1) in the main goroutine before spawning another goroutine, and // call Waits.Done() in the spawned goroutine after the operation is finished. type Store struct { db *bolt.DB // Waits is used for registering outstanding operations on the store. waits sync.WaitGroup } var _ storedefs.Store = (*Store)(nil) // DefaultDB returns the default database for storage. func DefaultDB(dbname string) (*bolt.DB, error) { db, err := bolt.Open(dbname, 0644, &bolt.Options{ Timeout: 1 * time.Second, }) return db, err } // NewStore creates a new Store with the default database. func NewStore(dbname string) (*Store, error) { db, err := DefaultDB(dbname) if err != nil { return nil, err } return NewStoreDB(db) } // NewStoreDB creates a new Store with a custom database. The database must be // a Bolt database. func NewStoreDB(db *bolt.DB) (*Store, error) { logger.Println("initializing store") defer logger.Println("initialized store") st := &Store{ db: db, waits: sync.WaitGroup{}, } if SchemaUpToDate(db) { logger.Println("DB schema up to date") } else { for name, fn := range initDB { err := fn(db) if err != nil { return nil, fmt.Errorf("failed to %s: %v", name, err) } } } return st, nil } // Waits returns a WaitGroup used to register outstanding storage requests when // making calls asynchronously. func (s *Store) Waits() *sync.WaitGroup { return &s.waits } // Close waits for all outstanding operations to finish, and closes the // database. func (s *Store) Close() error { if s == nil || s.db == nil { return nil } s.waits.Wait() return s.db.Close() } elvish-0.11+ds1/store/store_test.go000066400000000000000000000007771323000013700172660ustar00rootroot00000000000000package store // This file also sets up the test fixture. import ( "fmt" "io/ioutil" "os" ) var tStore *Store func init() { f, err := ioutil.TempFile("", "elvish.test") if err != nil { panic(fmt.Sprintf("Failed to open temp file: %v", err)) } db, err := DefaultDB(f.Name()) if err != nil { panic(fmt.Sprintf("Failed to create Store instance: %v", err)) } os.Remove(f.Name()) tStore, err = NewStoreDB(db) if err != nil { panic(fmt.Sprintf("Failed to create Store instance: %v", err)) } } elvish-0.11+ds1/store/storedefs/000077500000000000000000000000001323000013700165275ustar00rootroot00000000000000elvish-0.11+ds1/store/storedefs/data_dir.go000066400000000000000000000011411323000013700206220ustar00rootroot00000000000000package storedefs import ( "errors" "os" "github.com/elves/elvish/util" ) // ErrEmptyHOME is the error returned by EnsureDataDir when the environmental // variable HOME is empty. var ErrEmptyHOME = errors.New("environment variable HOME is empty") // EnsureDataDir ensures Elvish's data directory exists, creating it if // necessary. It returns the path to the data directory (never with a // trailing slash) and possible error. func EnsureDataDir() (string, error) { home, err := util.GetHome("") if err != nil { return "", err } ddir := home + "/.elvish" return ddir, os.MkdirAll(ddir, 0700) } elvish-0.11+ds1/store/storedefs/interface.go000066400000000000000000000010541323000013700210160ustar00rootroot00000000000000package storedefs // Store is an interface satisfied by the storage service. type Store interface { NextCmdSeq() (int, error) AddCmd(text string) (int, error) Cmd(seq int) (string, error) Cmds(from, upto int) ([]string, error) NextCmd(from int, prefix string) (int, string, error) PrevCmd(upto int, prefix string) (int, string, error) AddDir(dir string, incFactor float64) error Dirs(blacklist map[string]struct{}) ([]Dir, error) SharedVar(name string) (string, error) SetSharedVar(name, value string) error DelSharedVar(name string) error } elvish-0.11+ds1/store/storedefs/storedefs.go000066400000000000000000000007241323000013700210570ustar00rootroot00000000000000// Package storedefs contains definitions used by the store package. package storedefs import "errors" // NoBlacklist is an empty blacklist, to be used in GetDirs. var NoBlacklist = map[string]struct{}{} // ErrNoMatchingCmd is the error returned when a LastCmd or FirstCmd query // completes with no result. var ErrNoMatchingCmd = errors.New("no matching command line") // Dir is an entry in the directory history. type Dir struct { Path string Score float64 } elvish-0.11+ds1/store/storedefs/storedefs_test.go000066400000000000000000000001441323000013700221120ustar00rootroot00000000000000package storedefs import "testing" func TestStoreDefs(t *testing.T) { // TODO(xiaq): Add tests } elvish-0.11+ds1/sys/000077500000000000000000000000001323000013700142135ustar00rootroot00000000000000elvish-0.11+ds1/sys/console_windows.go000066400000000000000000000036611323000013700177640ustar00rootroot00000000000000package sys import ( "errors" "unsafe" "golang.org/x/sys/windows" ) var ( readConsoleInput = kernel32.NewProc("ReadConsoleInputW") errNr0 = errors.New("ReadConsoleInput reads 0 records") ) // ReadInputEvent wraps ReadConsoleInput into a Go-friendly interface. func ReadInputEvent(h windows.Handle) (InputEvent, error) { var buf [1]InputRecord nr, err := ReadConsoleInput(h, buf[:]) if err != nil { return nil, err } else if nr == 0 { return nil, errNr0 } return buf[0].GetEvent(), nil } // ReadConsoleInput input wraps the homonymous Windows API call. // // BOOL WINAPI ReadConsoleInput( // _In_ HANDLE hConsoleInput, // _Out_ PINPUT_RECORD lpBuffer, // _In_ DWORD nLength, // _Out_ LPDWORD lpNumberOfEventsRead // ); func ReadConsoleInput(h windows.Handle, buf []InputRecord) (int, error) { var nr uintptr r, _, err := readConsoleInput.Call(uintptr(h), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), uintptr(unsafe.Pointer(&nr))) if r != 0 { err = nil } return int(nr), err } // InputEvent is either a KeyEvent, MouseEvent, WindowBufferSizeEvent, // MenuEvent or FocusEvent. type InputEvent interface { isInputEvent() } func (*KeyEvent) isInputEvent() {} func (*MouseEvent) isInputEvent() {} func (*WindowBufferSizeEvent) isInputEvent() {} func (*MenuEvent) isInputEvent() {} func (*FocusEvent) isInputEvent() {} // GetEvent converts InputRecord to InputEvent. func (input *InputRecord) GetEvent() InputEvent { switch input.EventType { case KEY_EVENT: return (*KeyEvent)(unsafe.Pointer(&input.Event)) case MOUSE_EVENT: return (*MouseEvent)(unsafe.Pointer(&input.Event)) case WINDOW_BUFFER_SIZE_EVENT: return (*WindowBufferSizeEvent)(unsafe.Pointer(&input.Event)) case MENU_EVENT: return (*MenuEvent)(unsafe.Pointer(&input.Event)) case FOCUS_EVENT: return (*FocusEvent)(unsafe.Pointer(&input.Event)) default: return nil } } elvish-0.11+ds1/sys/dumpstack.go000066400000000000000000000004061323000013700165350ustar00rootroot00000000000000package sys import "runtime" const dumpStackBufSizeInit = 4096 func DumpStack() string { buf := make([]byte, dumpStackBufSizeInit) for { n := runtime.Stack(buf, true) if n < cap(buf) { return string(buf[:n]) } buf = make([]byte, cap(buf)*2) } } elvish-0.11+ds1/sys/fdset_freebsd.go000066400000000000000000000021421323000013700173400ustar00rootroot00000000000000// For whatever reason, on FreeBSD the only field of FdSet is called // X__fds_bits; on other Unices it is called Bits. This difference is irrelevant // for C programs, as POSIX defines a set of macros for accessing FdSet, which // hide the underlying difference. However since Elvish does not cgo and relies // on the auto-generated struct definitions, it has to cope with the difference. package sys import ( "reflect" "syscall" ) var nFdBits = (uint)(reflect.TypeOf(syscall.FdSet{}.X__fds_bits[0]).Size() * 8) type FdSet syscall.FdSet func (fs *FdSet) s() *syscall.FdSet { return (*syscall.FdSet)(fs) } func NewFdSet(fds ...int) *FdSet { fs := &FdSet{} fs.Set(fds...) return fs } func (fs *FdSet) Clear(fds ...int) { for _, fd := range fds { u := uint(fd) fs.X__fds_bits[u/nFdBits] &= ^(1 << (u % nFdBits)) } } func (fs *FdSet) IsSet(fd int) bool { u := uint(fd) return fs.X__fds_bits[u/nFdBits]&(1<<(u%nFdBits)) != 0 } func (fs *FdSet) Set(fds ...int) { for _, fd := range fds { u := uint(fd) fs.X__fds_bits[u/nFdBits] |= 1 << (u % nFdBits) } } func (fs *FdSet) Zero() { *fs = FdSet{} } elvish-0.11+ds1/sys/fdset_notfreebsd.go000066400000000000000000000013301323000013700200570ustar00rootroot00000000000000// +build !freebsd,!windows,!plan9 package sys import ( "syscall" "unsafe" ) var nFdBits = uint(8 * unsafe.Sizeof(syscall.FdSet{}.Bits[0])) type FdSet syscall.FdSet func (fs *FdSet) s() *syscall.FdSet { return (*syscall.FdSet)(fs) } func NewFdSet(fds ...int) *FdSet { fs := &FdSet{} fs.Set(fds...) return fs } func (fs *FdSet) Clear(fds ...int) { for _, fd := range fds { u := uint(fd) fs.Bits[u/nFdBits] &= ^(1 << (u % nFdBits)) } } func (fs *FdSet) IsSet(fd int) bool { u := uint(fd) return fs.Bits[u/nFdBits]&(1<<(u%nFdBits)) != 0 } func (fs *FdSet) Set(fds ...int) { for _, fd := range fds { u := uint(fd) fs.Bits[u/nFdBits] |= 1 << (u % nFdBits) } } func (fs *FdSet) Zero() { *fs = FdSet{} } elvish-0.11+ds1/sys/ioctl.go000066400000000000000000000004641323000013700156600ustar00rootroot00000000000000// +build !windows,!plan9 package sys import ( "os" "golang.org/x/sys/unix" ) // Ioctl wraps the ioctl syscall. func Ioctl(fd int, req uintptr, arg uintptr) error { _, _, e := unix.Syscall( unix.SYS_IOCTL, uintptr(fd), req, arg) if e != 0 { return os.NewSyscallError("ioctl", e) } return nil } elvish-0.11+ds1/sys/ioctl_bsd.go000066400000000000000000000006541323000013700165110ustar00rootroot00000000000000// +build darwin dragonfly freebsd netbsd openbsd // Copyright 2015 go-termios Author. All Rights Reserved. // https://github.com/go-termios/termios // Author: John Lenton package sys import "golang.org/x/sys/unix" const ( getAttrIOCTL = unix.TIOCGETA setAttrNowIOCTL = unix.TIOCSETA setAttrDrainIOCTL = unix.TIOCSETAW setAttrFlushIOCTL = unix.TIOCSETAF flushIOCTL = unix.TIOCFLUSH ) elvish-0.11+ds1/sys/ioctl_notbsd.go000066400000000000000000000006071323000013700172300ustar00rootroot00000000000000// +build linux solaris // Copyright 2015 go-termios Author. All Rights Reserved. // https://github.com/go-termios/termios // Author: John Lenton package sys import "golang.org/x/sys/unix" const ( getAttrIOCTL = unix.TCGETS setAttrNowIOCTL = unix.TCSETS setAttrDrainIOCTL = unix.TCSETSW setAttrFlushIOCTL = unix.TCSETSF flushIOCTL = unix.TCFLSH ) elvish-0.11+ds1/sys/isatty_unix.go000066400000000000000000000004211323000013700171170ustar00rootroot00000000000000// +build !windows,!plan9 package sys import ( "os" "unsafe" ) // IsATTY returns true if the given file is a terminal. func IsATTY(file *os.File) bool { var term Termios err := Ioctl(int(file.Fd()), getAttrIOCTL, uintptr(unsafe.Pointer(&term))) return err == nil } elvish-0.11+ds1/sys/isatty_windows.go000066400000000000000000000004171323000013700176330ustar00rootroot00000000000000// +build windows package sys import ( "os" "github.com/mattn/go-isatty" ) // IsATTY returns true if the given file descriptor is a terminal. func IsATTY(file *os.File) bool { fd := uintptr(file.Fd()) return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd) } elvish-0.11+ds1/sys/select_bsd.go000066400000000000000000000003511323000013700166500ustar00rootroot00000000000000// +build darwin dragonfly freebsd netbsd openbsd package sys import "syscall" func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *syscall.Timeval) (err error) { return syscall.Select(nfd, r.s(), w.s(), e.s(), timeout) } elvish-0.11+ds1/sys/select_linux.go000066400000000000000000000003201323000013700172330ustar00rootroot00000000000000// +build linux package sys import "syscall" func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *syscall.Timeval) error { _, err := syscall.Select(nfd, r.s(), w.s(), e.s(), timeout) return err } elvish-0.11+ds1/sys/select_test.go000066400000000000000000000020021323000013700170520ustar00rootroot00000000000000// +build !windows package sys import ( "syscall" "testing" ) func TestFdSet(t *testing.T) { fs := NewFdSet(42, 233) fs.Set(77) fds := []int{42, 233, 77} for _, i := range fds { if !fs.IsSet(i) { t.Errorf("fs.IsSet(%d) => false, want true", i) } } fs.Clear(233) if fs.IsSet(233) { t.Errorf("fs.IsSet(233) => true, want false") } fs.Zero() for _, i := range fds { if fs.IsSet(i) { t.Errorf("fs.IsSet(%d) => true, want false", i) } } } func TestSelect(t *testing.T) { var p1, p2 [2]int mustNil(syscall.Pipe(p1[:])) mustNil(syscall.Pipe(p2[:])) fs := NewFdSet(p1[0], p2[0]) var maxfd int if p1[0] > p2[0] { maxfd = p1[0] + 1 } else { maxfd = p2[0] + 1 } go func() { syscall.Write(p1[1], []byte("to p1")) syscall.Write(p2[1], []byte("to p2")) syscall.Close(p1[1]) syscall.Close(p2[1]) }() e := Select(maxfd+1, fs, nil, nil, nil) if e != nil { t.Errorf("Select(%v, %v, nil, nil, nil) => %v, want ", maxfd+1, fs, e) } syscall.Close(p1[0]) syscall.Close(p2[0]) } elvish-0.11+ds1/sys/sys.go000066400000000000000000000001101323000013700153500ustar00rootroot00000000000000// Package sys provide convenient wrappers around syscalls. package sys elvish-0.11+ds1/sys/systemdll_windows.go000066400000000000000000000001501323000013700203300ustar00rootroot00000000000000package sys import "golang.org/x/sys/windows" var kernel32 = windows.NewLazySystemDLL("kernel32.dll") elvish-0.11+ds1/sys/tc.go000066400000000000000000000005661323000013700151570ustar00rootroot00000000000000// +build !windows,!plan9 package sys import ( "unsafe" "golang.org/x/sys/unix" ) func Tcgetpgrp(fd int) (int, error) { var pid int errno := Ioctl(fd, unix.TIOCGPGRP, uintptr(unsafe.Pointer(&pid))) if errno == nil { return pid, nil } return -1, errno } func Tcsetpgrp(fd int, pid int) error { return Ioctl(fd, unix.TIOCSPGRP, uintptr(unsafe.Pointer(&pid))) } elvish-0.11+ds1/sys/termios.go000066400000000000000000000031331323000013700162240ustar00rootroot00000000000000// +build !windows,!plan9 // Copyright 2015 go-termios Author. All Rights Reserved. // https://github.com/go-termios/termios // Author: John Lenton package sys import ( "unsafe" "golang.org/x/sys/unix" ) // Termios represents terminal attributes. type Termios unix.Termios // NewTermiosFromFd extracts the terminal attribute of the given file // descriptor. func NewTermiosFromFd(fd int) (*Termios, error) { var term Termios if err := Ioctl(fd, getAttrIOCTL, uintptr(unsafe.Pointer(&term))); err != nil { return nil, err } return &term, nil } // ApplyToFd applies term to the given file descriptor. func (term *Termios) ApplyToFd(fd int) error { return Ioctl(fd, setAttrNowIOCTL, uintptr(unsafe.Pointer(term))) } // Copy returns a copy of term. func (term *Termios) Copy() *Termios { v := *term return &v } // SetVTime sets the timeout in deciseconds for noncanonical read. func (term *Termios) SetVTime(v uint8) { term.Cc[unix.VTIME] = v } // SetVMin sets the minimal number of characters for noncanonical read. func (term *Termios) SetVMin(v uint8) { term.Cc[unix.VMIN] = v } // SetICanon sets the canonical flag. func (term *Termios) SetICanon(v bool) { setFlag(&term.Lflag, unix.ICANON, v) } // SetEcho sets the echo flag. func (term *Termios) SetEcho(v bool) { setFlag(&term.Lflag, unix.ECHO, v) } // SetICRNL sets the CRNL iflag bit func (term *Termios) SetICRNL(v bool) { setFlag(&term.Iflag, unix.ICRNL, v) } // FlushInput discards data written to a file descriptor but not read. func FlushInput(fd int) error { return Ioctl(fd, flushIOCTL, uintptr(unix.TCIFLUSH)) } elvish-0.11+ds1/sys/termios_32bitflag.go000066400000000000000000000004731323000013700200650ustar00rootroot00000000000000// +build 386,darwin arm,darwin dragonfly freebsd linux netbsd openbsd solaris package sys // The type of Termios.Lflag is different on different platforms. // This file is for those where Lflag is uint32. func setFlag(flag *uint32, mask uint32, v bool) { if v { *flag |= mask } else { *flag &= ^mask } } elvish-0.11+ds1/sys/termios_64bitflag.go000066400000000000000000000004201323000013700200620ustar00rootroot00000000000000// +build amd64,darwin arm64,darwin package sys // The type of Termios.Lflag is different on different platforms. // This file is for those where Lflag is uint64. func setFlag(flag *uint64, mask uint64, v bool) { if v { *flag |= mask } else { *flag &= ^mask } } elvish-0.11+ds1/sys/testutil_test.go000066400000000000000000000001251323000013700174540ustar00rootroot00000000000000package sys func mustNil(e error) { if e != nil { panic("error is not nil") } } elvish-0.11+ds1/sys/types_gen_windows.go000066400000000000000000000001751323000013700203140ustar00rootroot00000000000000//go:generate cmd /c go tool cgo -godefs types_src_windows.go > ztypes_windows.go && gofmt -w ztypes_windows.go package sys elvish-0.11+ds1/sys/types_src_windows.go000066400000000000000000000011101323000013700203200ustar00rootroot00000000000000// +build ignore package sys /* #include */ import "C" type ( Coord C.COORD InputRecord C.INPUT_RECORD KeyEvent C.KEY_EVENT_RECORD MouseEvent C.MOUSE_EVENT_RECORD WindowBufferSizeEvent C.WINDOW_BUFFER_SIZE_RECORD MenuEvent C.MENU_EVENT_RECORD FocusEvent C.FOCUS_EVENT_RECORD ) const ( KEY_EVENT = C.KEY_EVENT MOUSE_EVENT = C.MOUSE_EVENT WINDOW_BUFFER_SIZE_EVENT = C.WINDOW_BUFFER_SIZE_EVENT MENU_EVENT = C.MENU_EVENT FOCUS_EVENT = C.FOCUS_EVENT ) elvish-0.11+ds1/sys/wait_windows.go000066400000000000000000000024751323000013700172700ustar00rootroot00000000000000package sys import ( "errors" "unsafe" "golang.org/x/sys/windows" ) const ( INFINITE = 0xFFFFFFFF ) const ( WAIT_OBJECT_0 = 0 WAIT_ABANDONED_0 = 0x00000080 WAIT_TIMEOUT = 0x00000102 WAIT_FAILED = 0xFFFFFFFF ) var ( waitForMultipleObjects = kernel32.NewProc("WaitForMultipleObjects") errTimeout = errors.New("WaitForMultipleObjects timeout") ) // WaitForMultipleObjects blocks until any of the objects is triggerd or // timeout. // // DWORD WINAPI WaitForMultipleObjects( // _In_ DWORD nCount, // _In_ const HANDLE *lpHandles, // _In_ BOOL bWaitAll, // _In_ DWORD dwMilliseconds // ); func WaitForMultipleObjects(handles []windows.Handle, waitAll bool, timeout uint32) (trigger int, abandoned bool, err error) { count := uintptr(len(handles)) ret, _, err := waitForMultipleObjects.Call(count, uintptr(unsafe.Pointer(&handles[0])), boolToUintptr(waitAll), uintptr(timeout)) switch { case WAIT_OBJECT_0 <= ret && ret < WAIT_OBJECT_0+count: return int(ret - WAIT_OBJECT_0), false, nil case WAIT_ABANDONED_0 <= ret && ret < WAIT_ABANDONED_0+count: return int(ret - WAIT_ABANDONED_0), true, nil case ret == WAIT_TIMEOUT: return -1, false, errTimeout default: return -1, false, err } } func boolToUintptr(b bool) uintptr { if b { return 1 } return 0 } elvish-0.11+ds1/sys/waitforread_unix.go000066400000000000000000000012631323000013700201160ustar00rootroot00000000000000// +build !windows,!plan9 package sys import "os" // WaitForRead blocks until any of the given files is ready to be read. It // returns a boolean array indicating which files are ready to be read and // possible errors. // // It is implemented with select(2) on Unix and WaitForMultipleObjects on // Windows. func WaitForRead(files ...*os.File) (ready []bool, err error) { maxfd := 0 fdset := NewFdSet() for _, file := range files { fd := int(file.Fd()) if maxfd < fd { maxfd = fd } fdset.Set(fd) } err = Select(maxfd+1, fdset, nil, nil, nil) ready = make([]bool, len(files)) for i, file := range files { ready[i] = fdset.IsSet(int(file.Fd())) } return ready, err } elvish-0.11+ds1/sys/waitforread_unix_test.go000066400000000000000000000010151323000013700211500ustar00rootroot00000000000000// +build !windows,!plan9 package sys import ( "io" "os" "testing" ) func TestWaitForRead(t *testing.T) { r0, w0, err := os.Pipe() mustNil(err) r1, w1, err := os.Pipe() mustNil(err) defer closeAll(r0, w0, r1, w1) w0.WriteString("x") ready, err := WaitForRead(r0, r1) if err != nil { t.Error("WaitForRead errors:", err) } if !ready[0] { t.Error("Want ready[0]") } if ready[1] { t.Error("Don't want ready[1]") } } func closeAll(files ...io.Closer) { for _, file := range files { file.Close() } } elvish-0.11+ds1/sys/winsize_unix.go000066400000000000000000000021021323000013700172700ustar00rootroot00000000000000// +build !windows,!plan9 // Copyright 2015 go-termios Author. All Rights Reserved. // https://github.com/go-termios/termios // Author: John Lenton package sys import ( "fmt" "os" "syscall" "unsafe" "golang.org/x/sys/unix" ) // SIGWINCH is the Window size change signal. const SIGWINCH = syscall.SIGWINCH // winSize mirrors struct winsize in the C header. // The following declaration matches struct winsize in the headers of // Linux and FreeBSD. type winSize struct { row uint16 col uint16 Xpixel uint16 Ypixel uint16 } // GetWinsize queries the size of the terminal referenced by the given file. func GetWinsize(file *os.File) (row, col int) { fd := int(file.Fd()) ws := winSize{} if err := Ioctl(fd, unix.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws))); err != nil { fmt.Printf("error in winSize: %v", err) return -1, -1 } // Pick up a reasonable value for row and col // if they equal zero in special case, // e.g. serial console if ws.col == 0 { ws.col = 80 } if ws.row == 0 { ws.row = 24 } return int(ws.row), int(ws.col) } elvish-0.11+ds1/sys/winsize_windows.go000066400000000000000000000012361323000013700200060ustar00rootroot00000000000000package sys import ( "fmt" "os" "syscall" "golang.org/x/sys/windows" ) // SIGWINCH is the Window size change signal. On Windows this signal does not // exist, so we use -1, an impossible value for signals. const SIGWINCH = syscall.Signal(-1) // GetWinsize queries the size of the terminal referenced by the given file. func GetWinsize(file *os.File) (row, col int) { var info windows.ConsoleScreenBufferInfo err := windows.GetConsoleScreenBufferInfo(windows.Handle(file.Fd()), &info) if err != nil { fmt.Printf("error in winSize: %v", err) return -1, -1 } window := info.Window return int(window.Bottom - window.Top), int(window.Right - window.Left) } elvish-0.11+ds1/sys/ztypes_windows.go000066400000000000000000000015231323000013700176530ustar00rootroot00000000000000// Created by cgo -godefs - DO NOT EDIT // cgo.exe -godefs types_src_windows.go package sys type ( Coord struct { X int16 Y int16 } InputRecord struct { EventType uint16 Pad_cgo_0 [2]byte Event [16]byte } KeyEvent struct { BKeyDown int32 WRepeatCount uint16 WVirtualKeyCode uint16 WVirtualScanCode uint16 UChar [2]byte DwControlKeyState uint32 } MouseEvent struct { DwMousePosition Coord DwButtonState uint32 DwControlKeyState uint32 DwEventFlags uint32 } WindowBufferSizeEvent struct { DwSize Coord } MenuEvent struct { DwCommandId uint32 } FocusEvent struct { BSetFocus int32 } ) const ( KEY_EVENT = 0x1 MOUSE_EVENT = 0x2 WINDOW_BUFFER_SIZE_EVENT = 0x4 MENU_EVENT = 0x8 FOCUS_EVENT = 0x10 ) elvish-0.11+ds1/tt/000077500000000000000000000000001323000013700140245ustar00rootroot00000000000000elvish-0.11+ds1/tt/tt.go000066400000000000000000000065461323000013700150150ustar00rootroot00000000000000// Package tt supports table-driven tests with little boilerplate. // // See the test case for this package for example usage. package tt import ( "bytes" "fmt" "reflect" ) // Table represents a test table. type Table []*Case // Case represents a test case. It is created by the C function, and offers // setters that augment and return itself; those calls can be chained like // C(...).Rets(...). type Case struct { args []interface{} retsMatchers [][]interface{} } // Args returns a new Case with the given arguments. func Args(args ...interface{}) *Case { return &Case{args: args} } // Rets modifies the test case so that it requires the return values to match // the given values. It returns the receiver. func (c *Case) Rets(matchers ...interface{}) *Case { c.retsMatchers = append(c.retsMatchers, matchers) return c } func match(matchers, actual []interface{}) bool { for i, matcher := range matchers { // TODO: Support custom matching strategy if !reflect.DeepEqual(matcher, actual[i]) { return false } } return true } // FnToTest describes a function to test. type FnToTest struct { name string body interface{} argsFmt string retsFmt string } // Fn makes a new FnToTest with the given function name and body. func Fn(name string, body interface{}) *FnToTest { return &FnToTest{name: name, body: body} } // ArgsFmt sets the string for formatting arguments in test error messages, and // return fn itself. func (fn *FnToTest) ArgsFmt(s string) *FnToTest { fn.argsFmt = s return fn } // RetsFmt sets the string for formatting return values in test error messages, // and return fn itself. func (fn *FnToTest) RetsFmt(s string) *FnToTest { fn.retsFmt = s return fn } // T is the interface for accessing testing.T. type T interface { Errorf(format string, args ...interface{}) } // Test tests a function against test cases. func Test(t T, fn *FnToTest, tests Table) { for _, test := range tests { rets := call(fn.body, test.args) for _, retsMatcher := range test.retsMatchers { if !match(retsMatcher, rets) { var argsString, retsString, wantRetsString string if fn.argsFmt == "" { argsString = sprintArgs(test.args...) } else { argsString = fmt.Sprintf(fn.argsFmt, test.args...) } if fn.retsFmt == "" { retsString = sprintRets(rets...) wantRetsString = sprintRets(retsMatcher...) } else { retsString = fmt.Sprintf(fn.retsFmt, rets...) wantRetsString = fmt.Sprintf(fn.retsFmt, retsMatcher...) } t.Errorf("%s(%s) -> %s, want %s", fn.name, argsString, retsString, wantRetsString) } } } } func sprintArgs(args ...interface{}) string { return sprintCommaDelimited(args...) } func sprintRets(rets ...interface{}) string { if len(rets) == 1 { return fmt.Sprint(rets[0]) } return "(" + sprintCommaDelimited(rets...) + ")" } func sprintCommaDelimited(args ...interface{}) string { var b bytes.Buffer for i, arg := range args { if i > 0 { b.WriteString(", ") } fmt.Fprint(&b, arg) } return b.String() } func call(fn interface{}, args []interface{}) []interface{} { argsReflect := make([]reflect.Value, len(args)) for i, arg := range args { argsReflect[i] = reflect.ValueOf(arg) } retsReflect := reflect.ValueOf(fn).Call(argsReflect) rets := make([]interface{}, len(retsReflect)) for i, retReflect := range retsReflect { rets[i] = retReflect.Interface() } return rets } elvish-0.11+ds1/tt/tt_test.go000066400000000000000000000031151323000013700160410ustar00rootroot00000000000000package tt import ( "fmt" "testing" ) // testT implements the T interface and is used to verify the Test function's // interaction with T. type testT []string func (t *testT) Errorf(format string, args ...interface{}) { *t = append(*t, fmt.Sprintf(format, args...)) } // Simple functions to test. func add(x, y int) int { return x + y } func addsub(x int, y int) (int, int) { return x + y, x - y } func TestTTPass(t *testing.T) { var testT testT Test(&testT, Fn("addsub", addsub), Table{ Args(1, 10).Rets(11, -9), }) if len(testT) > 0 { t.Errorf("Test errors when test should pass") } } func TestTTFailDefaultFmtOneReturn(t *testing.T) { var testT testT Test(&testT, Fn("add", add), Table{Args(1, 10).Rets(12)}, ) assertOneError(t, testT, "add(1, 10) -> 11, want 12") } func TestTTFailDefaultFmtMultiReturn(t *testing.T) { var testT testT Test(&testT, Fn("addsub", addsub), Table{Args(1, 10).Rets(11, -90)}, ) assertOneError(t, testT, "addsub(1, 10) -> (11, -9), want (11, -90)") } func TestTTFailCustomFmt(t *testing.T) { var testT testT Test(&testT, Fn("addsub", addsub).ArgsFmt("x = %d, y = %d").RetsFmt("(a = %d, b = %d)"), Table{Args(1, 10).Rets(11, -90)}, ) assertOneError(t, testT, "addsub(x = 1, y = 10) -> (a = 11, b = -9), want (a = 11, b = -90)") } func assertOneError(t *testing.T, testT testT, want string) { switch len(testT) { case 0: t.Errorf("Test didn't error when it should") case 1: if testT[0] != want { t.Errorf("Test wrote message %q, want %q", testT[0], want) } default: t.Errorf("Test wrote too many error messages") } } elvish-0.11+ds1/util/000077500000000000000000000000001323000013700143525ustar00rootroot00000000000000elvish-0.11+ds1/util/camel_to_dashed.go000066400000000000000000000006651323000013700200030ustar00rootroot00000000000000package util import ( "bytes" "unicode" ) // CamelToDashed converts a CamelCaseIdentifier to a dash-separated-identifier, // or a camelCaseIdentifier to a -dash-separated-identifier. func CamelToDashed(camel string) string { var buf bytes.Buffer for i, r := range camel { if (i == 0 && unicode.IsLower(r)) || (i > 0 && unicode.IsUpper(r)) { buf.WriteRune('-') } buf.WriteRune(unicode.ToLower(r)) } return buf.String() } elvish-0.11+ds1/util/camel_to_dashed_test.go000066400000000000000000000006571323000013700210430ustar00rootroot00000000000000package util import "testing" var tests = []struct { camel string want string }{ {"CamelCase", "camel-case"}, {"camelCase", "-camel-case"}, {"123", "123"}, {"你好", "你好"}, } func TestCamelToDashed(t *testing.T) { for _, test := range tests { camel, want := test.camel, test.want dashed := CamelToDashed(camel) if dashed != want { t.Errorf("CamelToDashed(%q) => %q, want %q", camel, dashed, want) } } } elvish-0.11+ds1/util/ceildiv.go000066400000000000000000000002131323000013700163140ustar00rootroot00000000000000package util // CeilDiv computes ceil(float(a)/b) without using float arithmetics. func CeilDiv(a, b int) int { return (a + b - 1) / b } elvish-0.11+ds1/util/ceildiv_test.go000066400000000000000000000005051323000013700173570ustar00rootroot00000000000000package util import "testing" var ceilDivTests = []struct { a, b, out int }{ {9, 3, 3}, {10, 3, 4}, {11, 3, 4}, {12, 3, 4}, } func TestCeilDiv(t *testing.T) { for _, tt := range ceilDivTests { if o := CeilDiv(tt.a, tt.b); o != tt.out { t.Errorf("CeilDiv(%v, %v) => %v, want %v", tt.a, tt.b, o, tt.out) } } } elvish-0.11+ds1/util/claim.go000066400000000000000000000042021323000013700157640ustar00rootroot00000000000000package util import ( "errors" "io/ioutil" "os" "strconv" "strings" ) // ErrClaimFileBadPattern is thrown when the pattern argument passed to // ClaimFile does not contain exactly one asterisk. var ErrClaimFileBadPattern = errors.New("ClaimFile: pattern must contain exactly one asterisk") // ClaimFile takes a directory and a pattern string containing exactly one // asterisk (e.g. "a*.log"). It opens a file in that directory, with a filename // matching the template, with "*" replaced by a number. That number is one plus // the largest of all existing files matching the template. If no such file // exists, "*" is replaced by 1. The file is opened for read and write, with // permission 0666 (before umask). // // For example, if the directory /tmp/elvish contains a1.log, a2.log and a9.log, // calling ClaimFile("/tmp/elvish", "a*.log") will open a10.log. If the // directory has no files matching the pattern, this same call will open a1.log. // // This function is useful for automatically determining unique names for log // files. Unique filenames can also be derived by embedding the PID, but using // this function preserves the chronical order of the files. // // This function is concurrency-safe: it always opens a new, unclaimed file and // is not subject to race condition. func ClaimFile(dir, pattern string) (*os.File, error) { if strings.Count(pattern, "*") != 1 { return nil, ErrClaimFileBadPattern } asterisk := strings.IndexByte(pattern, '*') prefix, suffix := pattern[:asterisk], pattern[asterisk+1:] files, err := ioutil.ReadDir(dir) if err != nil { return nil, err } max := 0 for _, file := range files { name := file.Name() if len(name) > len(prefix)+len(suffix) && strings.HasPrefix(name, prefix) && strings.HasSuffix(name, suffix) { core := name[len(prefix) : len(name)-len(suffix)] if coreNum, err := strconv.Atoi(core); err == nil { if max < coreNum { max = coreNum } } } } for i := max + 1; ; i++ { name := prefix + strconv.Itoa(i) + suffix f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0666) if err == nil { return f, nil } if !os.IsExist(err) { return nil, err } } } elvish-0.11+ds1/util/claim_test.go000066400000000000000000000012331323000013700170240ustar00rootroot00000000000000package util import ( "os" "testing" ) var claimFileTests = []struct { pattern string wantFileName string }{ {"a*.log", "a9.log"}, {"*.txt", "1.txt"}, } func TestClaimFile(t *testing.T) { InTempDir(func(tmpdir string) { touch("a0.log") touch("a1.log") touch("a8.log") for _, test := range claimFileTests { f, err := ClaimFile(".", test.pattern) if err != nil { t.Errorf("ClaimFile errors: %v", err) } if f.Name() != test.wantFileName { t.Errorf("ClaimFile claims %s, want %s", f.Name(), test.wantFileName) } } }) } func touch(fname string) { f, err := os.Create(fname) if err != nil { panic(err) } f.Close() } elvish-0.11+ds1/util/deepprint.go000066400000000000000000000030151323000013700166720ustar00rootroot00000000000000package util import ( "bytes" "fmt" "reflect" ) // DeepPrint is like printing with the %#v formatter of fmt, but it prints // pointer fields recursively. func DeepPrint(x interface{}) string { b := &bytes.Buffer{} deepPrint(b, reflect.ValueOf(x)) return b.String() } func deepPrint(b *bytes.Buffer, v reflect.Value) { i := v.Interface() t := v.Type() // GoStringer if g, ok := i.(fmt.GoStringer); ok { b.WriteString(g.GoString()) return } // nil switch v.Kind() { case reflect.Interface, reflect.Map, reflect.Slice, reflect.Ptr: if v.IsNil() { b.WriteString("nil") return } } switch v.Kind() { case reflect.Array, reflect.Slice, reflect.Map, reflect.Struct: // Composite kinds b.WriteString(t.String()) b.WriteRune('{') switch v.Kind() { case reflect.Array, reflect.Slice: for i := 0; i < v.Len(); i++ { if i > 0 { b.WriteString(", ") } deepPrint(b, v.Index(i)) } case reflect.Map: keys := v.MapKeys() for i, k := range keys { if i > 0 { b.WriteString(", ") } deepPrint(b, k) b.WriteString(": ") deepPrint(b, v.MapIndex(k)) } case reflect.Struct: for i := 0; i < t.NumField(); i++ { if i > 0 { b.WriteString(", ") } b.WriteString(t.Field(i).Name) b.WriteString(": ") deepPrint(b, v.Field(i)) } } b.WriteRune('}') case reflect.Ptr: b.WriteRune('&') deepPrint(b, reflect.Indirect(v)) return case reflect.Interface: deepPrint(b, v.Elem()) return default: fmt.Fprintf(b, "%#v", i) return } } elvish-0.11+ds1/util/deepprint_test.go000066400000000000000000000023031323000013700177300ustar00rootroot00000000000000package util import ( "testing" ) type S struct { I int S string Pt *T G G } type T struct { M map[string]string } type G struct { } type U struct { I int S string } func (g G) GoString() string { return "" } var deepPrintTests = []struct { in interface{} wanted string }{ {1, "1"}, {"foobar", `"foobar"`}, {[]int{42, 44}, `[]int{42, 44}`}, {[]int(nil), `nil`}, {(*int)(nil), `nil`}, {&S{42, "DON'T PANIC", &T{map[string]string{"foo": "bar"}}, G{}}, `&util.S{I: 42, S: "DON'T PANIC", Pt: &util.T{M: map[string]string{"foo": "bar"}}, G: }`}, {[]interface{}{&U{42, "DON'T PANIC"}, 42, "DON'T PANIC"}, `[]interface {}{&util.U{I: 42, S: "DON'T PANIC"}, 42, "DON'T PANIC"}`}, } func TestDeepPrint(t *testing.T) { for _, tt := range deepPrintTests { if out := DeepPrint(tt.in); out != tt.wanted { t.Errorf("DeepPrint(%v) => %#q, want %#q", tt.in, out, tt.wanted) } } // Test map. in := map[string]int{"foo": 42, "bar": 233} out := DeepPrint(in) wanted1 := `map[string]int{"foo": 42, "bar": 233}` wanted2 := `map[string]int{"bar": 233, "foo": 42}` if out != wanted1 && out != wanted2 { t.Errorf("DeepPrint(%v) => %#q, want %#q or %#q", in, out, wanted1, wanted2) } } elvish-0.11+ds1/util/fullnames.go000066400000000000000000000012441323000013700166700ustar00rootroot00000000000000package util import ( "os" "sort" ) // FullNames returns the full names of non-hidden files under a directory. The // directory name should end in a slash. If the directory cannot be listed, it // returns nil. // // The output should be the same as globbing dir + "*". It is used for testing // globbing. func FullNames(dir string) []string { f, err := os.Open(dir) if err != nil { return nil } names, err := f.Readdirnames(-1) f.Close() if err != nil { return nil } fullnames := make([]string, 0, len(names)) for _, name := range names { if name[0] != '.' { fullnames = append(fullnames, dir+name) } } sort.Strings(fullnames) return fullnames } elvish-0.11+ds1/util/fullnames_test.go000066400000000000000000000006501323000013700177270ustar00rootroot00000000000000package util import ( "reflect" "runtime" "testing" ) func TestFullNames(t *testing.T) { var dirs []string if runtime.GOOS == "windows" { dirs = []string{`C:\`, `C:\Users\`} } else { dirs = []string{"/", "/usr"} } for _, dir := range dirs { wantNames := ls(dir) names := FullNames(dir) if !reflect.DeepEqual(names, wantNames) { t.Errorf(`FullNames(%q) -> %s, want %s`, dir, names, wantNames) } } } elvish-0.11+ds1/util/fullnames_unix_test.go000066400000000000000000000005161323000013700207730ustar00rootroot00000000000000// +build !windows,!plan9 package util import ( "os/exec" "sort" "strings" ) func ls(dir string) []string { output, err := exec.Command("ls", dir).Output() mustOK(err) names := strings.Split(strings.Trim(string(output), "\n"), "\n") for i := range names { names[i] = dir + names[i] } sort.Strings(names) return names } elvish-0.11+ds1/util/fullnames_windows_test.go000066400000000000000000000016631323000013700215060ustar00rootroot00000000000000package util import ( "os/exec" "sort" "strings" "syscall" ) func ls(dir string) []string { cmd := exec.Command("cmd") cmd.SysProcAttr = &syscall.SysProcAttr{ CmdLine: "cmd /C dir /A /B " + dir, } output, err := cmd.Output() mustOK(err) names := strings.Split(strings.Trim(string(output), "\r\n"), "\r\n") for i := range names { names[i] = dir + names[i] } // Remove filenames that start with ".". // XXX: This behavior only serves to make current behavior of FullNames, // which always treat dotfiles as hidden, legal; the validness of this // behavior is quetionable. However, since FullNames is also depended by the // glob package for testing, changing FullNames requires changing the // behavior of globbing as well. filtered := make([]string, 0, len(names)) for _, name := range names { if !strings.HasPrefix(name, dir+".") { filtered = append(filtered, name) } } sort.Strings(filtered) return filtered } elvish-0.11+ds1/util/gethome.go000066400000000000000000000013331323000013700163310ustar00rootroot00000000000000package util import ( "fmt" "os" "os/user" "strings" ) // GetHome finds the home directory of a specified user. When given an empty // string, it finds the home directory of the current user. func GetHome(uname string) (string, error) { if uname == "" { // Use $HOME as override if we are looking for the home of the current // variable. home := os.Getenv("HOME") if home != "" { return strings.TrimRight(home, pathSep), nil } } // Look up the user. var u *user.User var err error if uname == "" { u, err = user.Current() } else { u, err = user.Lookup(uname) } if err != nil { return "", fmt.Errorf("can't resolve ~%s: %s", uname, err.Error()) } return strings.TrimRight(u.HomeDir, "/"), nil } elvish-0.11+ds1/util/getwd.go000066400000000000000000000010731323000013700160140ustar00rootroot00000000000000package util import ( "os" "path/filepath" "strings" ) var pathSep = string(filepath.Separator) // Getwd returns path of the working directory in a format suitable as the // prompt. func Getwd() string { pwd, err := os.Getwd() if err != nil { return "?" } return TildeAbbr(pwd) } // TildeAbbr abbreviates the user's home directory to ~. func TildeAbbr(path string) string { home, err := GetHome("") if err == nil { if path == home { return "~" } else if strings.HasPrefix(path, home+pathSep) { return "~" + path[len(home):] } } return path } elvish-0.11+ds1/util/getwd_test.go000066400000000000000000000022461323000013700170560ustar00rootroot00000000000000package util import ( "os" "path" "path/filepath" "runtime" "testing" ) func TestGetwd(t *testing.T) { InTempDir(func(tmpdir string) { // On some systems /tmp is a symlink. tmpdir, err := filepath.EvalSymlinks(tmpdir) if err != nil { panic(err) } // Override $HOME to make sure that tmpdir is not abbreviatable. os.Setenv("HOME", "/does/not/exist") if gotwd := Getwd(); gotwd != tmpdir { t.Errorf("Getwd() -> %v, want %v", gotwd, tmpdir) } // Override $HOME to trick GetHome. os.Setenv("HOME", tmpdir) if gotwd := Getwd(); gotwd != "~" { t.Errorf("Getwd() -> %v, want ~", gotwd) } mustOK(os.Mkdir("a", 0700)) mustOK(os.Chdir("a")) if gotwd := Getwd(); gotwd != filepath.Join("~", "a") { t.Errorf("Getwd() -> %v, want ~/a", gotwd) } // On macOS os.Getwd will still return the old path name in face of // directory being removed. Hence we only test this on Linux. // TODO(xiaq): Check the behavior on other BSDs and relax this condition // if possible. if runtime.GOOS == "linux" { mustOK(os.Remove(path.Join(tmpdir, "a"))) if gotwd := Getwd(); gotwd != "?" { t.Errorf("Getwd() -> %v, want ?", gotwd) } } }) } elvish-0.11+ds1/util/limits.go000066400000000000000000000004171323000013700162040ustar00rootroot00000000000000package util // Limit values for uint and int. // // NOTE: The math package contains similar constants for explicitly sized // integer types, but lack those for uint and int. const ( MaxUint = ^uint(0) MinUint = 0 MaxInt = int(MaxUint >> 1) MinInt = -MaxInt - 1 ) elvish-0.11+ds1/util/log.go000066400000000000000000000024421323000013700154640ustar00rootroot00000000000000package util import ( "io" "io/ioutil" "log" "os" ) var ( out io.Writer = ioutil.Discard // If out is set by SetOutputFile, outFile is set and keeps the same value // as out. Otherwise, outFile is nil. outFile *os.File loggers []*log.Logger ) // GetLogger gets a logger with a prefix. func GetLogger(prefix string) *log.Logger { logger := log.New(out, prefix, log.LstdFlags) loggers = append(loggers, logger) return logger } // SetOutput redirects the output of all loggers obtained with GetLogger to the // new io.Writer. If the old output was a file opened by SetOutputFile, it is // closed. func SetOutput(newout io.Writer) { if outFile != nil { outFile.Close() outFile = nil } out = newout outFile = nil for _, logger := range loggers { logger.SetOutput(out) } } // SetOutputFile redirects the output of all loggers obtained with GetLogger to // the named file. If the old output was a file opened by SetOutputFile, it is // closed. The new file is truncated. SetOutFile("") is equivalent to // SetOutput(ioutil.Discard). func SetOutputFile(fname string) error { if fname == "" { SetOutput(ioutil.Discard) return nil } file, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { return err } SetOutput(file) outFile = file return nil } elvish-0.11+ds1/util/multierror.go000066400000000000000000000015211323000013700171040ustar00rootroot00000000000000package util import "bytes" // MultiError pack multiple errors into one error. type MultiError struct { Errors []error } func (es MultiError) Error() string { switch len(es.Errors) { case 0: return "no error" case 1: return es.Errors[0].Error() default: var buf bytes.Buffer buf.WriteString("multiple errors: ") for i, e := range es.Errors { if i > 0 { buf.WriteString("; ") } buf.WriteString(e.Error()) } return buf.String() } } // Errors concatenate multiple errors into one. If all errors are nil, it // returns nil, otherwise the return value is a MultiError containing all the // non-nil arguments. func Errors(errs ...error) error { var nonNil []error for _, err := range errs { if err != nil { nonNil = append(nonNil, err) } } if len(nonNil) == 0 { return nil } return MultiError{nonNil} } elvish-0.11+ds1/util/pprinter.go000066400000000000000000000007611323000013700165500ustar00rootroot00000000000000package util import ( "fmt" "os" ) // Pprinter wraps the Pprint function. type Pprinter interface { // Pprint takes an indentation string and pretty-prints. Pprint(indent string) string } // PprintError pretty-prints an error if it implements Pprinter, and prints it // in bold and red otherwise. func PprintError(err error) { if pprinter, ok := err.(Pprinter); ok { fmt.Fprintln(os.Stderr, pprinter.Pprint("")) } else { fmt.Fprintf(os.Stderr, "\033[31;1m%s\033[m", err.Error()) } } elvish-0.11+ds1/util/search.go000066400000000000000000000007711323000013700161530ustar00rootroot00000000000000package util import ( "os" "path/filepath" "strings" ) // DontSearch determines whether the path to an external command should be // taken literally and not searched. func DontSearch(exe string) bool { return exe == ".." || strings.ContainsRune(exe, filepath.Separator) } // IsExecutable determines whether path refers to an executable file. func IsExecutable(path string) bool { fi, err := os.Stat(path) if err != nil { return false } fm := fi.Mode() return !fm.IsDir() && (fm&0111 != 0) } elvish-0.11+ds1/util/source_range.go000066400000000000000000000102711323000013700173560ustar00rootroot00000000000000package util import ( "bytes" "fmt" "strings" ) // SourceRange is a range of text in a source code. It can point to another // SourceRange, thus forming a linked list. It is used for tracebacks. type SourceRange struct { Name string Source string Begin int End int Next *SourceRange savedPprintInfo *rangePprintInfo } // NewSourceRange creates a new SourceRange. func NewSourceRange(name, source string, begin, end int, next *SourceRange) *SourceRange { return &SourceRange{name, source, begin, end, next, nil} } // rangePprintInfo is information about the source range that are needed for // pretty-printing. type rangePprintInfo struct { // Head is the piece of text immediately before Culprit, extending to, but // not including the closest line boundary. If Culprit already starts after // a line boundary, Head is an empty string. Head string // Culprit is Source[Begin:End], with any trailing newlines stripped. Culprit string // Tail is the piece of text immediately after Culprit, extending to, but // not including the closet line boundary. If Culprit already ends before a // line boundary, Tail is an empty string. Tail string // BeginLine is the (1-based) line number that the first character of Culprit is on. BeginLine int // EndLine is the (1-based) line number that the last character of Culprit is on. EndLine int } // Variables controlling the style of the culprit. var ( CulpritLineBegin = "\033[1;4m" CulpritLineEnd = "\033[m" CulpritPlaceHolder = "^" ) func (sr *SourceRange) pprintInfo() *rangePprintInfo { if sr.savedPprintInfo != nil { return sr.savedPprintInfo } before := sr.Source[:sr.Begin] culprit := sr.Source[sr.Begin:sr.End] after := sr.Source[sr.End:] head := lastLine(before) beginLine := strings.Count(before, "\n") + 1 // If the culprit ends with a newline, stripe it. Otherwise, tail is nonempty. var tail string if strings.HasSuffix(culprit, "\n") { culprit = culprit[:len(culprit)-1] } else { tail = firstLine(after) } endLine := beginLine + strings.Count(culprit, "\n") sr.savedPprintInfo = &rangePprintInfo{head, culprit, tail, beginLine, endLine} return sr.savedPprintInfo } // Pprint pretty-prints a SourceContext. func (sr *SourceRange) Pprint(sourceIndent string) string { if err := sr.checkPosition(); err != nil { return err.Error() } return (sr.Name + ", " + sr.lineRange() + "\n" + sourceIndent + sr.relevantSource(sourceIndent)) } // PprintCompact pretty-prints a SourceContext, with no line break between the // source position range description and relevant source excerpt. func (sr *SourceRange) PprintCompact(sourceIndent string) string { if err := sr.checkPosition(); err != nil { return err.Error() } desc := sr.Name + ", " + sr.lineRange() + " " // Extra indent so that following lines line up with the first line. descIndent := strings.Repeat(" ", Wcswidth(desc)) return desc + sr.relevantSource(sourceIndent+descIndent) } func (sr *SourceRange) checkPosition() error { if sr.Begin == -1 { return fmt.Errorf("%s, unknown position", sr.Name) } else if sr.Begin < 0 || sr.End > len(sr.Source) || sr.Begin > sr.End { return fmt.Errorf("%s, invalid position %d-%d", sr.Name, sr.Begin, sr.End) } return nil } func (sr *SourceRange) lineRange() string { info := sr.pprintInfo() if info.BeginLine == info.EndLine { return fmt.Sprintf("line %d:", info.BeginLine) } return fmt.Sprintf("line %d-%d:", info.BeginLine, info.EndLine) } func (sr *SourceRange) relevantSource(sourceIndent string) string { info := sr.pprintInfo() var buf bytes.Buffer buf.WriteString(info.Head) culprit := info.Culprit if culprit == "" { culprit = CulpritPlaceHolder } for i, line := range strings.Split(culprit, "\n") { if i > 0 { buf.WriteByte('\n') buf.WriteString(sourceIndent) } buf.WriteString(CulpritLineBegin) buf.WriteString(line) buf.WriteString(CulpritLineEnd) } buf.WriteString(info.Tail) return buf.String() } func firstLine(s string) string { i := strings.IndexByte(s, '\n') if i == -1 { return s } return s[:i] } func lastLine(s string) string { // When s does not contain '\n', LastIndexByte returns -1, which happens to // be what we want. return s[strings.LastIndexByte(s, '\n')+1:] } elvish-0.11+ds1/util/source_range_test.go000066400000000000000000000030421323000013700204130ustar00rootroot00000000000000package util import ( "strings" "testing" ) var sourceRangeTests = []struct { *SourceRange indent string wantPprint string wantPprintCompact string }{ // Single-line culprit {parseSourceRange("echo (bad)", "(", ")", true), "_", ` [test], line 1: _echo <(bad)>`[1:], `[test], line 1: echo <(bad)>`, }, // Multi-line culprit {parseSourceRange("echo (bad\nbad)", "(", ")", true), "_", ` [test], line 1-2: _echo <(bad> _`[1:], ` [test], line 1-2: echo <(bad> _ `[1:], }, // Empty culprit {parseSourceRange("echo x", "x", "x", false), "", ` [test], line 1: echo <^>x`[1:], "[test], line 1: echo <^>x", }, } func TestSourceRange(t *testing.T) { CulpritLineBegin = "<" CulpritLineEnd = ">" for i, test := range sourceRangeTests { gotPprint := test.SourceRange.Pprint(test.indent) if gotPprint != test.wantPprint { t.Errorf("test%d.Pprint(%q) = %q, want %q", i, test.indent, gotPprint, test.wantPprint) } gotPprintCompact := test.SourceRange.PprintCompact(test.indent) if gotPprintCompact != test.wantPprintCompact { t.Errorf("test%d.PprintCompact(%q) = %q, want %q", i, test.indent, gotPprintCompact, test.wantPprintCompact) } } } // Parse a string into a source range, using the first appearance of certain // texts as start and end positions. func parseSourceRange(s, starter, ender string, endAfter bool) *SourceRange { end := strings.Index(s, ender) if endAfter { end += len(ender) } return NewSourceRange("[test]", s, strings.Index(s, starter), end, nil) } elvish-0.11+ds1/util/strings.go000066400000000000000000000040001323000013700163640ustar00rootroot00000000000000package util import ( "errors" "strings" ) // ErrIndexOutOfRange is returned when out-of-range errors occur. var ErrIndexOutOfRange = errors.New("substring out of range") // FindContext takes a position in a text and finds its line number, // corresponding line and column numbers. Line and column numbers are counted // from 0. Used in diagnostic messages. func FindContext(text string, pos int) (lineno, colno int, line string) { var i, linestart int var r rune for i, r = range text { if i == pos { break } if r == '\n' { lineno++ linestart = i + 1 colno = 0 } else { colno++ } } line = strings.SplitN(text[linestart:], "\n", 2)[0] return } // FindFirstEOL returns the index of the first '\n'. When there is no '\n', the // length of s is returned. func FindFirstEOL(s string) int { eol := strings.IndexRune(s, '\n') if eol == -1 { eol = len(s) } return eol } // FindLastSOL returns an index just after the last '\n'. func FindLastSOL(s string) int { return strings.LastIndex(s, "\n") + 1 } // SubstringByRune returns the range of the i-th rune (inclusive) through the // j-th rune (exclusive) in s. func SubstringByRune(s string, low, high int) (string, error) { if low > high || low < 0 || high < 0 { return "", ErrIndexOutOfRange } var bLow, bHigh, j int for i := range s { if j == low { bLow = i } if j == high { bHigh = i } j++ } if j < high { return "", ErrIndexOutOfRange } if low == high { return "", nil } if j == high { bHigh = len(s) } return s[bLow:bHigh], nil } // NthRune returns the n-th rune of s. func NthRune(s string, n int) (rune, error) { if n < 0 { return 0, ErrIndexOutOfRange } var j int for _, r := range s { if j == n { return r, nil } j++ } return 0, ErrIndexOutOfRange } // MatchSubseq returns whether pattern is a subsequence of s. func MatchSubseq(s, pattern string) bool { for _, p := range pattern { i := strings.IndexRune(s, p) if i == -1 { return false } s = s[i+len(string(p)):] } return true } elvish-0.11+ds1/util/strings_test.go000066400000000000000000000053141323000013700174340ustar00rootroot00000000000000package util import "testing" var findContextTests = []struct { text string pos int lineno, colno int line string }{ {"a\nb", 2, 1, 0, "b"}, } func TestFindContext(t *testing.T) { for _, tt := range findContextTests { lineno, colno, line := FindContext(tt.text, tt.pos) if lineno != tt.lineno || colno != tt.colno || line != tt.line { t.Errorf("FindContext(%v, %v) => (%v, %v, %v), want (%v, %v, %v)", tt.text, tt.pos, lineno, colno, line, tt.lineno, tt.colno, tt.line) } } } var SubstringByRuneTests = []struct { s string low, high int wantedStr string wantedErr error }{ {"Hello world", 1, 4, "ell", nil}, {"你好世界", 0, 0, "", nil}, {"你好世界", 1, 1, "", nil}, {"你好世界", 1, 2, "好", nil}, {"你好世界", 1, 4, "好世界", nil}, {"你好世界", -1, -1, "", ErrIndexOutOfRange}, {"你好世界", 0, 5, "", ErrIndexOutOfRange}, {"你好世界", 5, 5, "", ErrIndexOutOfRange}, } func TestSubstringByRune(t *testing.T) { for _, tt := range SubstringByRuneTests { s, e := SubstringByRune(tt.s, tt.low, tt.high) if s != tt.wantedStr || e != tt.wantedErr { t.Errorf("SubstringByRune(%q, %v, %d) => (%q, %v), want (%q, %v)", tt.s, tt.low, tt.high, s, e, tt.wantedStr, tt.wantedErr) } } } var NthRuneTests = []struct { s string n int wantedRune rune wantedErr error }{ {"你好世界", -1, 0, ErrIndexOutOfRange}, {"你好世界", 0, '你', nil}, {"你好世界", 4, 0, ErrIndexOutOfRange}, } func TestNthRune(t *testing.T) { for _, tt := range NthRuneTests { r, e := NthRune(tt.s, tt.n) if r != tt.wantedRune || e != tt.wantedErr { t.Errorf("NthRune(%q, %v) => (%q, %v), want (%q, %v)", tt.s, tt.n, r, e, tt.wantedRune, tt.wantedErr) } } } var EOLSOLTests = []struct { s string wantFirstEOL, wantLastSOL int }{ {"0", 1, 0}, {"\n12", 0, 1}, {"01\n", 2, 3}, {"01\n34", 2, 3}, } func TestEOLSOL(t *testing.T) { for _, tc := range EOLSOLTests { eol := FindFirstEOL(tc.s) if eol != tc.wantFirstEOL { t.Errorf("FindFirstEOL(%q) => %d, want %d", tc.s, eol, tc.wantFirstEOL) } sol := FindLastSOL(tc.s) if sol != tc.wantLastSOL { t.Errorf("FindLastSOL(%q) => %d, want %d", tc.s, sol, tc.wantLastSOL) } } } var MatchSubseqTests = []struct { s, p string want bool }{ {"elvish", "e", true}, {"elvish", "elh", true}, {"elvish", "sh", true}, {"elves/elvish", "l/e", true}, {"elves/elvish", "e/e", true}, {"elvish", "le", false}, {"elvish", "evii", false}, } func TestMatchSubseq(t *testing.T) { for _, tc := range MatchSubseqTests { b := MatchSubseq(tc.s, tc.p) if b != tc.want { t.Errorf("MatchSubseq(%q, %q) -> %v, want %v", tc.s, tc.p, b, tc.want) } } } elvish-0.11+ds1/util/subseq.go000066400000000000000000000007371323000013700162120ustar00rootroot00000000000000package util import "unicode/utf8" // HasSubseq determines whether s has t as its subsequence. A string t is a // subsequence of a string s if and only if there is a possible sequence of // steps of deleting characters from s that result in t. func HasSubseq(s, t string) bool { i, j := 0, 0 for i < len(s) && j < len(t) { s0, di := utf8.DecodeRuneInString(s[i:]) t0, dj := utf8.DecodeRuneInString(t[j:]) i += di if s0 == t0 { j += dj } } return j == len(t) } elvish-0.11+ds1/util/subseq_test.go000066400000000000000000000011541323000013700172430ustar00rootroot00000000000000package util import "testing" var hasSubseqTests = []struct { s, t string want bool }{ {"", "", true}, {"a", "", true}, {"a", "a", true}, {"ab", "a", true}, {"ab", "b", true}, {"abc", "ac", true}, {"abcdefg", "bg", true}, {"abcdefg", "ga", false}, {"foo lorem ipsum", "f l i", true}, {"foo lorem ipsum", "oo o pm", true}, {"你好世界", "好", true}, {"你好世界", "好界", true}, } func TestHasSubseq(t *testing.T) { for _, test := range hasSubseqTests { if b := HasSubseq(test.s, test.t); b != test.want { t.Errorf("HasSubseq(%q, %q) = %v, want %v", test.s, test.t, b, test.want) } } } elvish-0.11+ds1/util/tempdir.go000066400000000000000000000034031323000013700163450ustar00rootroot00000000000000package util import ( "fmt" "io/ioutil" "os" "path/filepath" ) // WithTempDirs creates a requested number of temporary directories and runs a // function, passing the paths of the temporary directories; the passed paths // all have their symlinks resolved using filepath.EvalSymlinks. After the // function returns, it removes the temporary directories. It panics if it // cannot make a temporary directory, and prints an error message to stderr if // it cannot remove the temporary directories. // // It is useful in tests. func WithTempDirs(n int, f func([]string)) { tmpdirs := make([]string, n) for i := range tmpdirs { tmpdir, err := ioutil.TempDir("", "elvishtest.") if err != nil { panic(err) } tmpdirs[i], err = filepath.EvalSymlinks(tmpdir) if err != nil { panic(err) } } defer func() { for _, tmpdir := range tmpdirs { err := os.RemoveAll(tmpdir) if err != nil { fmt.Fprintln(os.Stderr, "Warning: failed to remove temp dir", tmpdir) } } }() f(tmpdirs) } // WithTempDir is like with WithTempDirs, except that it always creates one // temporary directory and pass the function a string instead of []string. func WithTempDir(f func(string)) { WithTempDirs(1, func(s []string) { f(s[0]) }) } // InTempDir is like WithTempDir, but also cd into the directory before running // the function, and cd backs after running the function if possible. // // It panics if it could not get the working directory or change directory. // // It is useful in tests. func InTempDir(f func(string)) { WithTempDir(func(tmpdir string) { oldpwd, err := os.Getwd() if err != nil { panic(err) } mustChdir(tmpdir) defer mustChdir(oldpwd) f(tmpdir) }) } func mustChdir(dir string) { err := os.Chdir(dir) if err != nil { panic(err) } } elvish-0.11+ds1/util/tempdir_test.go000066400000000000000000000025451323000013700174120ustar00rootroot00000000000000package util import ( "os" "path/filepath" "testing" ) func TestWithTempDirs_PassesDirs(t *testing.T) { WithTempDirs(10, func(dirs []string) { for _, dir := range dirs { stat, err := os.Stat(dir) if err != nil { t.Errorf("WithTempDir passes %q, but it cannot be stat'ed", dir) } if !stat.IsDir() { t.Errorf("WithTempDir passes %q, but it is not dir", dir) } } }) } func TestWithTempDir_RemovesDirs(t *testing.T) { var tempDirs []string WithTempDirs(10, func(dirs []string) { tempDirs = dirs }) for _, dir := range tempDirs { _, err := os.Stat(dir) if err == nil { t.Errorf("After WithTempDir returns, %q still exists", dir) } } } func TestInTempDir_CDIn(t *testing.T) { InTempDir(func(tmpDir string) { pwd := getPwd() evaledTmpDir, err := filepath.EvalSymlinks(tmpDir) if err != nil { panic(err) } if pwd != evaledTmpDir { t.Errorf("In InTempDir, working dir (%q) != EvalSymlinks(argument) (%q)", pwd, evaledTmpDir) } }) } func TestInTempDir_CDOut(t *testing.T) { before := getPwd() InTempDir(func(tmpDir string) {}) after := getPwd() if before != after { t.Errorf("With InTempDir, working dir before %q != after %q", before, after) } } func getPwd() string { dir, err := os.Getwd() if err != nil { panic(err) } dir, err = filepath.EvalSymlinks(dir) if err != nil { panic(err) } return dir } elvish-0.11+ds1/util/test_utils.go000066400000000000000000000001121323000013700170720ustar00rootroot00000000000000package util func mustOK(err error) { if err != nil { panic(err) } } elvish-0.11+ds1/util/throw.go000066400000000000000000000027011323000013700160440ustar00rootroot00000000000000package util // Thrown wraps an error that was raised by Throw, so that it can be recognized // by Catch. type Thrown struct { Wrapped error } func (t Thrown) Error() string { return "thrown: " + t.Wrapped.Error() } // Throw panics with err wrapped properly so that it can be catched by Catch. func Throw(err error) { panic(Thrown{err}) } // Catch tries to catch an error thrown by Throw and stop the panic. If the // panic is not caused by Throw, the panic is not stopped. It should be called // directly from defer. func Catch(perr *error) { r := recover() if r == nil { return } if exc, ok := r.(Thrown); ok { *perr = exc.Wrapped } else { panic(r) } } // PCall calls a function and catches anything Thrown'n and returns it. It does // not protect against panics not using Throw, nor can it distinguish between // nothing thrown and Throw(nil). func PCall(f func()) (e error) { defer Catch(&e) f() // If we reach here, f didn't throw anything. return nil } // Throws returns whether calling f throws out a certain error (using Throw). It // is useful for testing. func Throws(f func(), e error) bool { return PCall(f) == e } // ThrowsAny returns whether calling f throws out anything that is not nil. It // is useful for testing. func ThrowsAny(f func()) bool { return PCall(f) != nil } // DoesntThrow returns whether calling f does not throw anything. It is useful // for testing. func DoesntThrow(f func()) bool { return PCall(f) == nil } elvish-0.11+ds1/util/throw_test.go000066400000000000000000000047551323000013700171160ustar00rootroot00000000000000package util import ( "errors" "testing" ) func recoverPanic(f func()) (recovered interface{}) { defer func() { recovered = recover() }() f() return nil } func TestThrowAndCatch(t *testing.T) { tothrow := errors.New("an error to throw") // Throw should cause a panic f := func() { Throw(tothrow) } if recoverPanic(f) == nil { t.Errorf("Throw did not cause a panic") } // Catch should catch what was thrown caught := func() (err error) { defer Catch(&err) Throw(tothrow) return nil }() if caught != tothrow { t.Errorf("thrown %v, but caught %v", tothrow, caught) } // Catch should not recover panics not caused by Throw var err error f = func() { defer Catch(&err) panic(errors.New("233")) } _ = recoverPanic(f) if err != nil { t.Errorf("Catch recovered panic not caused via Throw") } // Catch should do nothing when there is no panic err = nil f = func() { defer Catch(&err) } f() if err != nil { t.Errorf("Catch recovered something when there is no panic") } } // errToThrow is the error to throw in test cases. var errToThrow = errors.New("error to throw") func TestPCall(t *testing.T) { // PCall catches throws if PCall(func() { Throw(errToThrow) }) != errToThrow { t.Errorf("PCall does not catch throws") } // PCall returns nil when nothing has been thrown if PCall(func() {}) != nil { t.Errorf("PCall returns non-nil when nothing has been thrown") } // PCall returns nil when nil has been thrown if PCall(func() { Throw(nil) }) != nil { t.Errorf("PCall returns non-nil when nil has been thrown") } } func TestThrows(t *testing.T) { if Throws(func() { Throw(errToThrow) }, errToThrow) != true { t.Errorf("Throws returns false when function throws wanted error") } if Throws(func() { Throw(errToThrow) }, errors.New("")) != false { t.Errorf("Throws returns true when function throws unwanted error") } if Throws(func() {}, errToThrow) != false { t.Errorf("Throws returns true when function does not throw") } } func TestThrowsAny(t *testing.T) { if ThrowsAny(func() { Throw(errToThrow) }) != true { t.Errorf("ThrowsAny returns false when function throws non-nil") } if ThrowsAny(func() {}) != false { t.Errorf("ThrowsAny returns true when function does not throw") } } func TestDoesnotThrow(t *testing.T) { if DoesntThrow(func() { Throw(errToThrow) }) != false { t.Errorf("DoesntThrow returns true when function throws") } if DoesntThrow(func() {}) != true { t.Errorf("DoesntThrow returns false when function doesn't throw") } } elvish-0.11+ds1/util/util.go000066400000000000000000000000711323000013700156540ustar00rootroot00000000000000// Package util contains utility functions. package util elvish-0.11+ds1/util/wcwidth.go000066400000000000000000000124411323000013700163540ustar00rootroot00000000000000package util import ( "sort" "strings" ) var wcwidthOverride = map[rune]int{} // Taken from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c (public domain) var combining = [][2]rune{ {0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489}, {0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603}, {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670}, {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, {0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A}, {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902}, {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D}, {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981}, {0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD}, {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C}, {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD}, {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C}, {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D}, {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0}, {0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC}, {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D}, {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC}, {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35}, {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030}, {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039}, {0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F}, {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD}, {0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922}, {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B}, {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, {0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF}, {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063}, {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F}, {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B}, {0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169}, {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, {0xE0100, 0xE01EF}, } func isCombining(r rune) bool { n := len(combining) i := sort.Search(n, func(i int) bool { return r <= combining[i][1] }) return i < n && r >= combining[i][0] } // Wcwidth returns the width of a rune when displayed on the terminal. func Wcwidth(r rune) int { if w, ok := wcwidthOverride[r]; ok { return w } if r == 0 || r < 32 || (0x7f <= r && r < 0xa0) || // Control character isCombining(r) { return 0 } if r >= 0x1100 && (r <= 0x115f || /* Hangul Jamo init. consonants */ r == 0x2329 || r == 0x232a || (r >= 0x2e80 && r <= 0xa4cf && r != 0x303f) || /* CJK ... Yi */ (r >= 0xac00 && r <= 0xd7a3) || /* Hangul Syllables */ (r >= 0xf900 && r <= 0xfaff) || /* CJK Compatibility Ideographs */ (r >= 0xfe10 && r <= 0xfe19) || /* Vertical forms */ (r >= 0xfe30 && r <= 0xfe6f) || /* CJK Compatibility Forms */ (r >= 0xff00 && r <= 0xff60) || /* Fullwidth Forms */ (r >= 0xffe0 && r <= 0xffe6) || /* Fullwidth Forms */ (r >= 0x20000 && r <= 0x2fffd) || /* CJK Extensions */ (r >= 0x30000 && r <= 0x3fffd) || /* Reserved for historical Chinese scripts */ (r >= 0x1f300 && r <= 0x1f6ff)) { // Miscellaneous Symbols and Pictographs ... Geometric Shapes Extended return 2 } return 1 } // OverrideWcwidth overrides the wcwidth of a rune to be a specific non-negative // value. OverrideWcwidth panics if w < 0. func OverrideWcwidth(r rune, w int) { if w < 0 { panic("negative width") } wcwidthOverride[r] = w } // UnoverrideWcwidth removes the override of a rune. func UnoverrideWcwidth(r rune) { delete(wcwidthOverride, r) } // Wcswidth returns the width of a string when displayed on the terminal, // assuming no soft line breaks. func Wcswidth(s string) (w int) { for _, r := range s { w += Wcwidth(r) } return } // TrimWcwidth trims the string s so that it has a width of at most wmax. func TrimWcwidth(s string, wmax int) string { w := 0 for i, r := range s { w += Wcwidth(r) if w > wmax { return s[:i] } } return s } // ForceWcwidth forces the string s to the given display width by trimming and // padding. func ForceWcwidth(s string, width int) string { w := 0 for i, r := range s { w0 := Wcwidth(r) w += w0 if w > width { w -= w0 s = s[:i] break } } return s + strings.Repeat(" ", width-w) } // TrimEachLineWcwidth trims each line of s so that it is no wider than the // specified width. func TrimEachLineWcwidth(s string, width int) string { lines := strings.Split(s, "\n") for i := range lines { lines[i] = TrimWcwidth(lines[i], width) } return strings.Join(lines, "\n") } elvish-0.11+ds1/util/wcwidth_test.go000066400000000000000000000026311323000013700174130ustar00rootroot00000000000000package util import ( "testing" ) var wcwidthTests = []struct { in rune wanted int }{ {'\u0301', 0}, // Combining acute accent {'a', 1}, {'Ω', 1}, {'好', 2}, {'か', 2}, } func TestWcwidth(t *testing.T) { for _, tt := range wcwidthTests { out := Wcwidth(tt.in) if out != tt.wanted { t.Errorf("wcwidth(%q) => %v, want %v", tt.in, out, tt.wanted) } } } func TestOverrideWcwidth(t *testing.T) { r := '❱' oldw := Wcwidth(r) w := oldw + 1 OverrideWcwidth(r, w) if Wcwidth(r) != w { t.Errorf("Wcwidth(%q) != %d after OverrideWcwidth", r, w) } UnoverrideWcwidth(r) if Wcwidth(r) != oldw { t.Errorf("Wcwidth(%q) != %d after UnoverrideWcwidth", r, oldw) } } func TestTrimWcwidth(t *testing.T) { if TrimWcwidth("abc", 2) != "ab" { t.Errorf("TrimWcwidth #1 fails") } if TrimWcwidth("你好", 3) != "你" { t.Errorf("TrimWcwidth #2 fails") } } func TestForceWcwidth(t *testing.T) { for i, c := range []struct { s string w int want string }{ // Triming {"abc", 2, "ab"}, {"你好", 2, "你"}, // Padding {"abc", 4, "abc "}, {"你好", 5, "你好 "}, // Trimming and Padding {"你好", 3, "你 "}, } { if got := ForceWcwidth(c.s, c.w); got != c.want { t.Errorf("ForceWcwidth #%d fails", i) } } } func TestTrimEachLineWcwidth(t *testing.T) { if TrimEachLineWcwidth("abcdefg\n你好", 3) != "abc\n你" { t.Errorf("TestTrimEachLineWcwidth fails") } }