Codebase list elvish / db17015
New upstream version 0.9 Shengjing Zhu 6 years ago
219 changed file(s) with 23363 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 *.go filter=goimports
0 # Compiled Object files, Static and Dynamic libs (Shared Objects)
1 *.o
2 *.a
3 *.so
4
5 # Folders
6 _obj
7 _test
8
9 # Architecture specific extensions/prefixes
10 *.[568vq]
11 [568vq].out
12
13 *.cgo1.go
14 *.cgo2.c
15 _cgo_defun.c
16 _cgo_gotypes.go
17 _cgo_export.*
18
19 _testmain.go
20
21 *.exe
22
23 # Project specific
24 /t/ # Small applications for manual testing go inside this directory
25 /cover/
26 /elvish
0 language: go
1 go:
2 - 1.6
3 - 1.7
4 - 1.8
5 sudo: false
6 os:
7 - linux
8 - osx
9 script:
10 make travis
0 # Notes for Contributors
1
2 ## Testing
3
4 Always run unit tests before committing. `make` will take care of this.
5
6 ## Generated files
7
8 Some files are generated from other files. They should be commmited into the repository for this package to be go-getable. Run `go generate ./...` to regenerate them in case you modified the source.
9
10 ## Formatting the Code
11
12 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.
13
14 To automate this you can set up a `goimports` filter for Git by putting this in `~/.gitconfig`:
15
16 [filter "goimports"]
17 clean = goimports
18 smudge = cat
19
20 Git will then always run `goimports` for you before comitting, 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).
21
22 ## Licensing
23
24 By contributing, you agree to license your code under the same license as existing source code of elvish. See the LICENSE file.
0 FROM golang:onbuild
0 # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
1
2
3 [[projects]]
4 branch = "master"
5 name = "github.com/kr/pty"
6 packages = ["."]
7 revision = "ce7fa45920dc37a92de8377972e52bc55ffa8d57"
8
9 [[projects]]
10 name = "github.com/mattn/go-isatty"
11 packages = ["."]
12 revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe"
13 version = "v0.0.2"
14
15 [[projects]]
16 name = "github.com/mattn/go-sqlite3"
17 packages = ["."]
18 revision = "ca5e3819723d8eeaf170ad510e7da1d6d2e94a08"
19 version = "v1.2.0"
20
21 [[projects]]
22 branch = "master"
23 name = "github.com/xiaq/persistent"
24 packages = ["vector"]
25 revision = "06adb7b7bfda3d72748014cca677223091779411"
26
27 [[projects]]
28 branch = "master"
29 name = "golang.org/x/net"
30 packages = ["context"]
31 revision = "1a68b1313cf4ad7778376e82641197b60c02f65c"
32
33 [[projects]]
34 branch = "master"
35 name = "golang.org/x/sys"
36 packages = ["unix"]
37 revision = "1e99a4f9d247b28c670884b9a8d6801f39a47b77"
38
39 [solve-meta]
40 analyzer-name = "dep"
41 analyzer-version = 1
42 inputs-digest = "9e65cb2e0a3498e95abcc9d7146399bb8ca87856f7f87fbf4d2257f2c4ff38e0"
43 solver-name = "gps-cdcl"
44 solver-version = 1
0 [[constraint]]
1 name = "github.com/kr/pty"
2 branch = "master"
3
4 [[constraint]]
5 name = "github.com/mattn/go-isatty"
6 version = "0.0.2"
7
8 [[constraint]]
9 name = "github.com/mattn/go-sqlite3"
10 version = "1.2.0"
11
12 [[constraint]]
13 name = "golang.org/x/sys"
14 branch = "master"
0 Copyright (c) elvish developers and contributors, All rights reserved.
1
2 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
3
4 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.
0 PKG_BASE := github.com/elves/elvish
1 PKGS := $(shell go list ./... | grep -v /vendor/)
2 PKG_COVERS := $(shell go list ./... | grep -v '^$(PKG_BASE)/vendor/' | grep -v '^$(PKG_BASE)$$' | sed "s|^$(PKG_BASE)/|cover/|" | sed 's/$$/.cover/')
3 COVER_MODE := set
4
5 FIRST_GOPATH=$(shell go env GOPATH | cut -d: -f1)
6
7 default: get test
8
9 get:
10 go get .
11
12 generate:
13 go generate ./...
14
15 test:
16 go test $(PKGS)
17
18 cover/%.cover: %
19 mkdir -p $(dir $@)
20 go test -coverprofile=$@ -covermode=$(COVER_MODE) ./$<
21
22 cover/all: $(PKG_COVERS)
23 echo mode: $(COVER_MODE) > $@
24 for f in $(PKG_COVERS); do test -f $$f && sed 1d $$f >> $@ || true; done
25
26 # We would love to test for coverage in pull requests, but it's now
27 # bettered turned off for two reasons:
28 #
29 # 1) The goverall badge will always show the "latest" coverage, even if that
30 # comes from a PR.
31 #
32 # 2) Some of the tests have fluctuating coverage (the test against
33 # edit.tty.AsyncReader), and goveralls will put a big cross on the PR when the
34 # coverage happens to drop.
35 goveralls: cover/all
36 test "$(TRAVIS_PULL_REQUEST)" = false \
37 && go get -u github.com/mattn/goveralls \
38 && $(FIRST_GOPATH)/bin/goveralls -coverprofile=cover/all -service=travis-ci \
39 || echo "not sending to coveralls"
40
41 upload: get
42 tar cfz elvish.tar.gz -C $(FIRST_GOPATH)/bin elvish
43 test "$(TRAVIS_GO_VERSION)" = 1.8 -a "$(TRAVIS_PULL_REQUEST)" = false \
44 && test -n "$(TRAVIS_TAG)" -o "$(TRAVIS_BRANCH)" = master \
45 && curl http://ul.elvish.io:6060/ -F name=elvish-$(if $(TRAVIS_TAG),$(TRAVIS_TAG)-,)$(TRAVIS_OS_NAME).tar.gz \
46 -F token=$$UPLOAD_TOKEN -F file=@./elvish.tar.gz\
47 || echo "not uploading"
48
49 travis: goveralls upload
50
51 .PHONY: default get generate test goveralls upload travis
0 # A friendly and expressive Unix shell
1
2 [![GoDoc](http://godoc.org/github.com/elves/elvish?status.svg)](http://godoc.org/github.com/elves/elvish)
3 [![Build Status on Travis](https://travis-ci.org/elves/elvish.svg?branch=master)](https://travis-ci.org/elves/elvish)
4 [![Coverage Status](https://coveralls.io/repos/github/elves/elvish/badge.svg?branch=master)](https://coveralls.io/github/elves/elvish?branch=master)
5 [![Go Report Card](https://goreportcard.com/badge/github.com/elves/elvish)](https://goreportcard.com/report/github.com/elves/elvish)
6 [![License](https://img.shields.io/badge/License-BSD%202--Clause-orange.svg)](https://opensource.org/licenses/BSD-2-Clause)
7
8 This project aims to explore the potentials of the Unix shell. It is a work in
9 progress; things will change without warning. The [issues list](https://github.com/elves/elvish/issues) contains many things I'm working on.
10
11 Discuss Elvish by joining #elvish on freenode, the [Gitter room](https://gitter.im/elves/elvish-public) or the [international user group on Telegram](https://telegram.me/elvish). Thanks to [fishroom](https://github.com/tuna/fishroom), the IRC channel, Gitter room and Telegram group are interconnected. Chinese speakers are also welcome in #elvish-zh on freenode, or the [Chinese user group on Telegram](https://telegram.me/elvishzh)!
12
13 ## Screenshot
14
15 Elvish looks like this:
16
17 ![syntax highlighting](https://raw.githubusercontent.com/elves/images/master/syntax.png)
18
19 ## Prebuilt binaries
20
21 64-bit Linux: `curl -s https://dl.elvish.io/elvish-linux.tar.gz | sudo tar vxz -C /usr/local/bin`
22
23 64-bit Mac OS X: `curl -s https://dl.elvish.io/elvish-osx.tar.gz | sudo tar vxz -C /usr/local/bin`
24
25 Try `cn.dl.elvish.io` if you are in China and the download is too slow. (In the future `dl.elvish.io` will resolve to a Chinese IP for users from China, but that hasn't happended yet.)
26
27 See also [Building Elvish](#building-elvish).
28
29 ### RPM package
30
31 RPM Package for Fedora is available in [FZUG Repo](https://github.com/FZUG/repo/wiki/Add-FZUG-Repository).
32
33 First add the FZUG repo:
34
35 ```
36 dnf config-manager --add-repo=http://repo.fdzh.org/FZUG/FZUG.repo
37 ```
38
39 Then install Elvish:
40
41 ```
42 dnf install elvish
43 ```
44
45 ## Getting Started
46
47 **Note**: Elvish is constantly tested under Terminal.app, libvte-based terminals and tmux. Some function keys might not work in other terminals like xterm (#328).
48
49 Elvish mimics bash and zsh in a lot of places. The following shows some key differences and highlights, as well as some common tasks:
50
51 * Put your startup script in `~/.elvish/rc.elv`. There is no `alias` yet, but you can achieve the goal by defining a function:
52
53 ```sh
54 fn ls { e:ls --color $@ }
55 ```
56
57 The `e:` prefix (for "external") ensures that the external command named `ls` will be called. Otherwise this definition will result in infinite recursion.
58
59 * The left and right prompts can be customized by assigning functions to `le:prompt` and `le:rprompt`. Their outputs are concatenated (with no spaces in between) before being used as the respective prompts. The following simulates the default prompts but uses fancy Unicode:
60
61 ```sh
62 # "tilde-abbr" abbreviates home directory to a tilde.
63 le:prompt = { tilde-abbr $pwd; put '❱ ' }
64 # "constantly" returns a function that always writes the same value(s) to output.
65 le:rprompt = (constantly `whoami`✸`hostname`)
66 ```
67
68 * Press Up to search through history. It uses what you have typed to do prefix match. To cancel, press Escape.
69
70 ![history](https://raw.githubusercontent.com/elves/images/master/history.png)
71
72 * Press Tab to start completion. Use arrow key and Tab to select the candidate; press Enter, or just continue typing to accept. To cancel, press Escape.
73
74 ![tab completion](https://raw.githubusercontent.com/elves/images/master/completion.png)
75
76 * Press Ctrl-N to start navigation mode. Press Ctrl-H to show hidden files; press again to hide. Press tab to append selected filename to your command. Likewise, pressing Escape gets you back to the default (insert) mode.
77
78 ![navigation mode](https://raw.githubusercontent.com/elves/images/master/navigation.png)
79
80 * Try typing `echo [` and press Enter. Elvish knows that the command is unfinished due to the unclosed `[` and inserts a newline instead of accepting the command. Moreover, common errors like syntax errors and missing variables are highlighted in real time.
81
82 * Elvish remembers which directories you have visited. Press Ctrl-L to list visited directories. Like in completion, use Up, Down and Tab to navigate and use Enter to accept (which `cd`s into the selected directory). Press Escape to cancel.
83
84 ![location mode](https://raw.githubusercontent.com/elves/images/master/location.png)
85
86 Type to filter:
87
88 ![location mode, filtering](https://raw.githubusercontent.com/elves/images/master/location-filter.png)
89
90 The filtering algorithm takes your filter and adds `**` to both sides of each path component. So `g/di` becomes pattern `**g**/**di**`, so it matches /home/xiaq/**g**o/elves/elvish/e**di**t.
91
92 * **NOTE**: Default key bindings as listed above are subject to change in the future; but the functionality will not go away.
93
94 * Elvish doesn't support history expansion like `!!`. Instead, it has a "bang mode", triggered by `Alt-,`, that provides the same functionality. For example, if you typed a command but forgot to add `sudo`, you can then type `sudo ` and press `Alt-,` twice to fix it:
95
96 ![bang mode](https://raw.githubusercontent.com/elves/images/master/bang.png)
97
98 * Lists look like `[a b c]`, and maps look like `[&key1=value1 &key2=value2]`. Unlike other shells, a list never expands to multiple words, unless you explicitly explode it by prefixing the variable name with `@`:
99 ```sh
100 ~> li = [1 2 3]
101 ~> put $li
102 ▶ [1 2 3]
103 ~> put $@li
104 ▶ 1
105 ▶ 2
106 ▶ 3
107 ~> map = [&k1=v1 &k2=v2]
108 ~> echo $map[k1]
109 v1
110 ```
111
112 * Environment variables live in a separate `E:` (for "environment") namespace and must be explicitly qualified:
113 ```sh
114 ~> put $E:HOME
115 ▶ /home/xiaq
116 ~> E:PATH=$E:PATH":/bin"
117 ```
118
119 * You can manipulate search paths through the special list `$paths`, which is synced with `$E:PATH`:
120 ```sh
121 ~> echo $paths
122 [/bin /sbin]
123 ~> paths = [/opt/bin $@paths /usr/bin]
124 ~> echo $paths
125 [/opt/bin /bin /sbin /usr/bin]
126 ~> echo $E:PATH
127 /opt/bin:/bin:/sbin:/usr/bin
128 ```
129
130 * You can manipulate the keybinding through the map `$le:binding`. For example, this binds Ctrl-L to clearing the terminal: `le:binding[insert][Ctrl-L]={ clear > /dev/tty }`. The first index is the mode and the second is the key. (Yes, the braces enclose a lambda.)
131
132 Use `pprint $le:binding` to get a nice (albeit long) view of the current keybinding.
133
134 **NOTE**: Bindings for letters modified by Alt are case-sensitive. For instance, `Alt-a` means pressing `Alt` and `A`, while `Alt-A` means pressing `Alt`, `Shift` and `A`.
135
136 * There is no interpolation inside double quotes (yet). Use implicit string concatenation:
137 ```sh
138 ~> name = xiaq
139 ~> echo "My name is "$name"."
140 My name is xiaq.
141 ```
142
143 * Elementary floating-point arithmetics as well as comparisons are builtin. Unfortunately, you have to use prefix notation:
144 ```sh
145 ~> + 1 2
146 ▶ 3
147 ~> / `* 2 3` 4
148 ▶ 1.5
149 ~> / (* 2 3) 4 # parentheses are equivalent to backquotes, but look nicer in arithmetics
150 ▶ 1.5
151 ~> > 1 2 # ">" may be used as a command name
152
153 ~> < 1 2 # "<" may also be used as a command name; silence means "true"
154 ```
155
156 * Functions are defined with `fn`. You can name arguments:
157 ```sh
158 ~> fn square [x]{
159 * $x $x
160 }
161 ~> square 4
162 ▶ 16
163 ```
164
165 * Output of some builtin commands start with a funny "▶". It is not part of the output itself, but shows that such commands output a stream of values instead of bytes. As such, their internal structures as well as boundaries between values are preserved. This allows us to manipulate structured data in the shell; more on this later.
166
167
168 ## Building Elvish
169
170 Go >= 1.6 is required. Linux is fully supported. It is likely to work on BSDs and Mac OS X. Windows is **not** supported yet.
171
172 Elvish is a go-gettable package, and can be installed using `go get github.com/elves/elvish`.
173
174 If you are lazy and use `bash` or `zsh` now, here is something you can copy-paste into your terminal:
175
176 ```sh
177 export GOPATH=$HOME/go
178 export PATH=$PATH:$GOPATH/bin
179 mkdir -p $GOPATH
180
181 go get github.com/elves/elvish
182
183 for f in ~/.bashrc ~/.zshrc; do
184 printf 'export %s=%s\n' GOPATH '$HOME/go' PATH '$PATH:$GOPATH/bin' >> $f
185 done
186 ```
187
188 [How To Write Go Code](http://golang.org/doc/code.html) explains how `$GOPATH` works.
189
190 For macOS users, you can build Elvish via [homebrew](http://brew.sh):
191
192 ```sh
193 brew install --HEAD elvish
194 ```
195
196
197 ## Name
198
199 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". It also rhymes with [fish](https://fishshell.com), one of shells that influenced the philosophy of Elvish.
200
201 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.
202
203 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".
0 // Package API provides the API to the daemon RPC service.
1 package api
2
3 import "github.com/elves/elvish/store/storedefs"
4
5 const (
6 // ServiceName is the name of the RPC service exposed by the daemon.
7 ServiceName = "Daemon"
8
9 // Version is the API version. It should be bumped any time the API changes.
10 Version = -97
11 )
12
13 // Basic requests.
14
15 type VersionRequest struct{}
16
17 type VersionResponse struct {
18 Version int
19 }
20
21 func (c *Client) Version() (int, error) {
22 req := &VersionRequest{}
23 res := &VersionResponse{}
24 err := c.CallDaemon("Version", req, res)
25 return res.Version, err
26 }
27
28 type PidRequest struct{}
29
30 type PidResponse struct {
31 Pid int
32 }
33
34 func (c *Client) Pid() (int, error) {
35 req := &PidRequest{}
36 res := &PidResponse{}
37 err := c.CallDaemon("Pid", req, res)
38 return res.Pid, err
39 }
40
41 // Cmd requests.
42
43 type NextCmdSeqRequest struct{}
44
45 type NextCmdSeqResponse struct {
46 Seq int
47 }
48
49 func (c *Client) NextCmdSeq() (int, error) {
50 req := &NextCmdRequest{}
51 res := &NextCmdSeqResponse{}
52 err := c.CallDaemon("NextCmdSeq", req, res)
53 return res.Seq, err
54 }
55
56 type AddCmdRequest struct {
57 Text string
58 }
59
60 type AddCmdResponse struct {
61 Seq int
62 }
63
64 func (c *Client) AddCmd(text string) (int, error) {
65 req := &AddCmdRequest{text}
66 res := &AddCmdResponse{}
67 err := c.CallDaemon("AddCmd", req, res)
68 return res.Seq, err
69 }
70
71 type CmdRequest struct {
72 Seq int
73 }
74
75 type CmdResponse struct {
76 Text string
77 }
78
79 func (c *Client) Cmd(seq int) (string, error) {
80 req := &CmdRequest{seq}
81 res := &CmdResponse{}
82 err := c.CallDaemon("Cmd", req, res)
83 return res.Text, err
84 }
85
86 type CmdsRequest struct {
87 From int
88 Upto int
89 }
90
91 type CmdsResponse struct {
92 Cmds []string
93 }
94
95 func (c *Client) Cmds(from, upto int) ([]string, error) {
96 req := &CmdsRequest{from, upto}
97 res := &CmdsResponse{}
98 err := c.CallDaemon("Cmds", req, res)
99 return res.Cmds, err
100 }
101
102 type NextCmdRequest struct {
103 From int
104 Prefix string
105 }
106
107 type NextCmdResponse struct {
108 Seq int
109 Text string
110 }
111
112 func (c *Client) NextCmd(from int, prefix string) (int, string, error) {
113 req := &NextCmdRequest{from, prefix}
114 res := &NextCmdResponse{}
115 err := c.CallDaemon("NextCmd", req, res)
116 return res.Seq, res.Text, err
117 }
118
119 type PrevCmdRequest struct {
120 Upto int
121 Prefix string
122 }
123
124 type PrevCmdResponse struct {
125 Seq int
126 Text string
127 }
128
129 func (c *Client) PrevCmd(upto int, prefix string) (int, string, error) {
130 req := &PrevCmdRequest{upto, prefix}
131 res := &PrevCmdResponse{}
132 err := c.CallDaemon("PrevCmd", req, res)
133 return res.Seq, res.Text, err
134 }
135
136 // Dir requests.
137
138 type AddDirRequest struct {
139 Dir string
140 IncFactor float64
141 }
142
143 type AddDirResponse struct{}
144
145 func (c *Client) AddDir(dir string, incFactor float64) error {
146 req := &AddDirRequest{dir, incFactor}
147 res := &AddDirResponse{}
148 err := c.CallDaemon("AddDir", req, res)
149 return err
150 }
151
152 type DirsRequest struct {
153 Blacklist map[string]struct{}
154 }
155
156 type DirsResponse struct {
157 Dirs []storedefs.Dir
158 }
159
160 func (c *Client) Dirs(blacklist map[string]struct{}) ([]storedefs.Dir, error) {
161 req := &DirsRequest{blacklist}
162 res := &DirsResponse{}
163 err := c.CallDaemon("Dirs", req, res)
164 return res.Dirs, err
165 }
166
167 // SharedVar requests.
168
169 type SharedVarRequest struct {
170 Name string
171 }
172
173 type SharedVarResponse struct {
174 Value string
175 }
176
177 func (c *Client) SharedVar(name string) (string, error) {
178 req := &SharedVarRequest{name}
179 res := &SharedVarResponse{}
180 err := c.CallDaemon("SharedVar", req, res)
181 return res.Value, err
182 }
183
184 type SetSharedVarRequest struct {
185 Name string
186 Value string
187 }
188
189 type SetSharedVarResponse struct{}
190
191 func (c *Client) SetSharedVar(name, value string) error {
192 req := &SetSharedVarRequest{name, value}
193 res := &SetSharedVarResponse{}
194 return c.CallDaemon("SetSharedVar", req, res)
195 }
196
197 type DelSharedVarRequest struct {
198 Name string
199 }
200
201 type DelSharedVarResponse struct{}
202
203 func (c *Client) DelSharedVar(name string) error {
204 req := &DelSharedVarRequest{}
205 res := &DelSharedVarResponse{}
206 return c.CallDaemon("DelSharedVar", req, res)
207 }
0 package api
1
2 import "testing"
3
4 func TestAPI(t *testing.T) {
5 // TODO(xiaq): Add tests.
6 }
0 package api
1
2 import (
3 "errors"
4 "net/rpc"
5 "sync"
6 )
7
8 var ErrDaemonOffline = errors.New("daemon offline")
9
10 type Client struct {
11 sockPath string
12 rpcClient *rpc.Client
13 waits sync.WaitGroup
14 }
15
16 func NewClient(sockPath string) *Client {
17 return &Client{sockPath, nil, sync.WaitGroup{}}
18 }
19
20 func (c *Client) SockPath() string {
21 return c.sockPath
22 }
23
24 func (c *Client) Waits() *sync.WaitGroup {
25 return &c.waits
26 }
27
28 func (c *Client) CallDaemon(f string, req, res interface{}) error {
29 err := c.connect()
30 if err != nil {
31 return err
32 }
33 err = c.rpcClient.Call(ServiceName+"."+f, req, res)
34 if err == rpc.ErrShutdown {
35 // Clear rpcClient so as to reconnect next time
36 c.rpcClient = nil
37 }
38 return err
39 }
40
41 func (c *Client) Close() error {
42 c.waits.Wait()
43 return c.rpcClient.Close()
44 }
45
46 func (c *Client) connect() error {
47 rpcClient, err := rpc.Dial("unix", c.sockPath)
48 if err != nil {
49 return err
50 }
51 c.rpcClient = rpcClient
52 return nil
53 }
0 // Package exec provides the entry point of the daemon sub-program and helpers
1 // to spawn a daemon process.
2 package daemon
3
4 import (
5 "errors"
6 "log"
7 "os"
8 "path"
9 "path/filepath"
10 "strconv"
11 "strings"
12 "syscall"
13
14 "github.com/elves/elvish/util"
15 )
16
17 // Daemon keeps configurations for the daemon process.
18 type Daemon struct {
19 Forked int
20 BinPath string
21 DbPath string
22 SockPath string
23 LogPathPrefix string
24 }
25
26 // closeFd is used in syscall.ProcAttr.Files to signify closing a fd.
27 const closeFd = ^uintptr(0)
28
29 // Main is the entry point of the daemon sub-program.
30 func (d *Daemon) Main(serve func(string, string)) int {
31 switch d.Forked {
32 case 0:
33 errored := false
34 absify := func(f string, s *string) {
35 if *s == "" {
36 log.Println("flag", f, "is required for daemon")
37 errored = true
38 return
39 }
40 p, err := filepath.Abs(*s)
41 if err != nil {
42 log.Println("abs:", err)
43 errored = true
44 } else {
45 *s = p
46 }
47 }
48 absify("-bin", &d.BinPath)
49 absify("-db", &d.DbPath)
50 absify("-sock", &d.SockPath)
51 absify("-logprefix", &d.LogPathPrefix)
52 if errored {
53 return 2
54 }
55
56 syscall.Umask(0077)
57 return d.pseudoFork(
58 &syscall.ProcAttr{
59 // cd to /
60 Dir: "/",
61 // empty environment
62 Env: nil,
63 // inherit stderr only for logging
64 Files: []uintptr{closeFd, closeFd, 2},
65 Sys: &syscall.SysProcAttr{Setsid: true},
66 })
67 case 1:
68 return d.pseudoFork(
69 &syscall.ProcAttr{
70 Files: []uintptr{closeFd, closeFd, 2},
71 })
72 case 2:
73 serve(d.SockPath, d.DbPath)
74 return 0
75 default:
76 return 2
77 }
78 }
79
80 // Spawn spawns a daemon in the background. It is supposed to be called from a
81 // client.
82 func (d *Daemon) Spawn() error {
83 binPath := d.BinPath
84 // Determine binPath.
85 if binPath == "" {
86 if len(os.Args) > 0 && path.IsAbs(os.Args[0]) {
87 binPath = os.Args[0]
88 } else {
89 // Find elvish in PATH
90 paths := strings.Split(os.Getenv("PATH"), ":")
91 result, err := util.Search(paths, "elvish")
92 if err != nil {
93 return errors.New("cannot find elvish: " + err.Error())
94 }
95 binPath = result
96 }
97 }
98 return forkExec(nil, 0, binPath, d.DbPath, d.SockPath, d.LogPathPrefix)
99 }
100
101 // pseudoFork forks a daemon. It is supposed to be called from the daemon.
102 func (d *Daemon) pseudoFork(attr *syscall.ProcAttr) int {
103 err := forkExec(attr, d.Forked+1, d.BinPath, d.DbPath, d.SockPath, d.LogPathPrefix)
104 if err != nil {
105 return 2
106 }
107 return 0
108 }
109
110 func forkExec(attr *syscall.ProcAttr, forkLevel int, binPath, dbPath, sockPath, logPathPrefix string) error {
111 _, err := syscall.ForkExec(binPath, []string{
112 binPath,
113 "-daemon",
114 "-forked", strconv.Itoa(forkLevel),
115 "-bin", binPath,
116 "-db", dbPath,
117 "-sock", sockPath,
118 "-logprefix", logPathPrefix,
119 }, attr)
120 return err
121 }
0 package daemon
1
2 import "testing"
3
4 func TestDaemon(t *testing.T) {
5 // XXX(xiaq): Add tests.
6 }
0 // Package service implements the daemon service for mediating access to the
1 // storage backend.
2 package service
3
4 import (
5 "net"
6 "net/rpc"
7 "os"
8 "os/signal"
9 "syscall"
10
11 "github.com/elves/elvish/daemon/api"
12 "github.com/elves/elvish/store"
13 "github.com/elves/elvish/util"
14 )
15
16 var logger = util.GetLogger("[daemon] ")
17
18 // Serve runs the daemon service. It does not return.
19 func Serve(sockpath, dbpath string) {
20 logger.Println("pid is", syscall.Getpid())
21
22 st, err := store.NewStore(dbpath)
23 if err != nil {
24 logger.Printf("failed to create storage: %v", err)
25 logger.Println("aborting")
26 os.Exit(2)
27 }
28
29 logger.Println("going to listen", sockpath)
30 listener, err := net.Listen("unix", sockpath)
31 if err != nil {
32 logger.Printf("failed to listen on %s: %v", sockpath, err)
33 logger.Println("aborting")
34 os.Exit(2)
35 }
36
37 quitSignals := make(chan os.Signal)
38 signal.Notify(quitSignals, syscall.SIGTERM, syscall.SIGINT)
39 go func() {
40 sig := <-quitSignals
41 logger.Printf("received signal %s", sig)
42 err := os.Remove(sockpath)
43 if err != nil {
44 logger.Printf("failed to remove socket %s: %v", sockpath, err)
45 }
46 err = st.Close()
47 if err != nil {
48 logger.Printf("failed to close storage: %v", err)
49 }
50 err = listener.Close()
51 if err != nil {
52 logger.Printf("failed to close listener: %v", err)
53 }
54 logger.Println("listener closed, waiting to exit")
55 }()
56
57 service := &Service{st}
58 rpc.RegisterName(api.ServiceName, service)
59
60 logger.Println("starting to serve RPC calls")
61 rpc.Accept(listener)
62
63 logger.Println("exiting")
64 }
65
66 // Service provides the daemon RPC service.
67 type Service struct {
68 store *store.Store
69 }
70
71 func (s *Service) Version(req *api.VersionRequest, res *api.VersionResponse) error {
72 res.Version = api.Version
73 return nil
74 }
75
76 func (s *Service) Pid(req *api.PidRequest, res *api.PidResponse) error {
77 res.Pid = syscall.Getpid()
78 return nil
79 }
80
81 func (s *Service) NextCmdSeq(req *api.NextCmdSeqRequest, res *api.NextCmdSeqResponse) error {
82 seq, err := s.store.NextCmdSeq()
83 res.Seq = seq
84 return err
85 }
86
87 func (s *Service) AddCmd(req *api.AddCmdRequest, res *api.AddCmdResponse) error {
88 seq, err := s.store.AddCmd(req.Text)
89 res.Seq = seq
90 return err
91 }
92
93 func (s *Service) Cmd(req *api.CmdRequest, res *api.CmdResponse) error {
94 text, err := s.store.Cmd(req.Seq)
95 res.Text = text
96 return err
97 }
98
99 func (s *Service) Cmds(req *api.CmdsRequest, res *api.CmdsResponse) error {
100 cmds, err := s.store.Cmds(req.From, req.Upto)
101 res.Cmds = cmds
102 return err
103 }
104
105 func (s *Service) NextCmd(req *api.NextCmdRequest, res *api.NextCmdResponse) error {
106 seq, text, err := s.store.NextCmd(req.From, req.Prefix)
107 res.Seq, res.Text = seq, text
108 return err
109 }
110
111 func (s *Service) PrevCmd(req *api.PrevCmdRequest, res *api.PrevCmdResponse) error {
112 seq, text, err := s.store.PrevCmd(req.Upto, req.Prefix)
113 res.Seq, res.Text = seq, text
114 return err
115 }
116
117 func (s *Service) AddDir(req *api.AddDirRequest, res *api.AddDirResponse) error {
118 return s.store.AddDir(req.Dir, req.IncFactor)
119 }
120
121 func (s *Service) Dirs(req *api.DirsRequest, res *api.DirsResponse) error {
122 dirs, err := s.store.GetDirs(req.Blacklist)
123 res.Dirs = dirs
124 return err
125 }
126
127 func (s *Service) SharedVar(req *api.SharedVarRequest, res *api.SharedVarResponse) error {
128 value, err := s.store.GetSharedVar(req.Name)
129 res.Value = value
130 return err
131 }
132
133 func (s *Service) SetSharedVar(req *api.SetSharedVarRequest, res *api.SetSharedVarResponse) error {
134 return s.store.SetSharedVar(req.Name, req.Value)
135 }
136
137 func (s *Service) DelSharedVar(req *api.DelSharedVarRequest, res *api.DelSharedVarResponse) error {
138 return s.store.DelSharedVar(req.Name)
139 }
0 package service
1
2 import "testing"
3
4 func TestService(t *testing.T) {
5 // TODO(xiaq): Add tests.
6 }
0 package edit
1
2 import "github.com/elves/elvish/eval"
3
4 var _ = registerVariable("abbr", func() eval.Variable {
5 return eval.NewPtrVariableWithValidator(
6 eval.NewMap(make(map[eval.Value]eval.Value)), eval.ShouldBeMap)
7 })
8
9 func (ed *Editor) abbr() eval.Map {
10 return ed.variables["abbr"].Get().(eval.Map)
11 }
12
13 func (ed *Editor) abbrIterate(cb func(abbr, full string) bool) {
14 m := ed.abbr()
15 m.IterateKey(func(k eval.Value) bool {
16 abbr, ok := k.(eval.String)
17 if !ok {
18 return true
19 }
20 full, ok := m.IndexOne(k).(eval.String)
21 if !ok {
22 return true
23 }
24 return cb(string(abbr), string(full))
25 })
26 }
0 package edit
1
2 type action struct {
3 typ actionType
4 returnLine string
5 returnErr error
6 }
7
8 type actionType int
9
10 const (
11 noAction actionType = iota
12 reprocessKey
13 exitReadLine
14 )
0 package edit
1
2 import (
3 "bufio"
4 "errors"
5 "os"
6 "sync"
7
8 "github.com/elves/elvish/edit/ui"
9 "github.com/elves/elvish/eval"
10 )
11
12 // This file implements types and functions for interactions with the
13 // Elvishscript runtime.
14
15 var (
16 errNotNav = errors.New("not in navigation mode")
17 errMustBeString = errors.New("must be string")
18 errEditorInvalid = errors.New("internal error: editor not set up")
19 errEditorInactive = errors.New("editor inactive")
20 )
21
22 // BuiltinFn records an editor builtin.
23 type BuiltinFn struct {
24 name string
25 impl func(ed *Editor)
26 }
27
28 var _ eval.CallableValue = &BuiltinFn{}
29
30 // Kind returns "fn".
31 func (*BuiltinFn) Kind() string {
32 return "fn"
33 }
34
35 // Repr returns the representation of a builtin function as a variable name.
36 func (bf *BuiltinFn) Repr(int) string {
37 return "$" + bf.name
38 }
39
40 // Call calls a builtin function.
41 func (bf *BuiltinFn) Call(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
42 eval.TakeNoOpt(opts)
43 eval.TakeNoArg(args)
44 ed, ok := ec.Editor.(*Editor)
45 if !ok {
46 throw(errEditorInvalid)
47 }
48 if !ed.active {
49 throw(errEditorInactive)
50 }
51 bf.impl(ed)
52 }
53
54 // installModules installs edit: and edit:* modules.
55 func installModules(modules map[string]eval.Namespace, ed *Editor) {
56 // Construct the edit: module, starting with builtins.
57 ns := makeNamespaceFromBuiltins(builtinMaps[""])
58
59 // Populate binding tables in the variable $binding.
60 // TODO Make binding specific to the Editor.
61 binding := &eval.Struct{
62 []string{
63 modeInsert, modeCommand, modeCompletion, modeNavigation, modeHistory,
64 modeHistoryListing, modeLocation, modeLastCmd, modeListing},
65 []eval.Variable{
66 eval.NewRoVariable(BindingTable{keyBindings[modeInsert]}),
67 eval.NewRoVariable(BindingTable{keyBindings[modeCommand]}),
68 eval.NewRoVariable(BindingTable{keyBindings[modeCompletion]}),
69 eval.NewRoVariable(BindingTable{keyBindings[modeNavigation]}),
70 eval.NewRoVariable(BindingTable{keyBindings[modeHistory]}),
71 eval.NewRoVariable(BindingTable{keyBindings[modeHistoryListing]}),
72 eval.NewRoVariable(BindingTable{keyBindings[modeLocation]}),
73 eval.NewRoVariable(BindingTable{keyBindings[modeLastCmd]}),
74 eval.NewRoVariable(BindingTable{keyBindings[modeListing]}),
75 },
76 }
77 ns["binding"] = eval.NewRoVariable(binding)
78
79 // Editor configurations.
80 for name, variable := range ed.variables {
81 ns[name] = variable
82 }
83
84 // Internal states.
85 ns["history"] = eval.NewRoVariable(History{&ed.historyMutex, ed.daemon})
86 ns["current-command"] = eval.MakeVariableFromCallback(
87 func(v eval.Value) {
88 if !ed.active {
89 throw(errEditorInactive)
90 }
91 if s, ok := v.(eval.String); ok {
92 ed.line = string(s)
93 ed.dot = len(ed.line)
94 } else {
95 throw(errMustBeString)
96 }
97 },
98 func() eval.Value { return eval.String(ed.line) },
99 )
100 ns["selected-file"] = eval.MakeRoVariableFromCallback(
101 func() eval.Value {
102 if !ed.active {
103 throw(errEditorInactive)
104 }
105 nav, ok := ed.mode.(*navigation)
106 if !ok {
107 throw(errNotNav)
108 }
109 return eval.String(nav.current.selectedName())
110 },
111 )
112
113 // Completers.
114 for _, bac := range argCompletersData {
115 ns[eval.FnPrefix+bac.name] = eval.NewRoVariable(bac)
116 }
117
118 // Functions.
119 eval.AddBuiltinFns(ns,
120 &eval.BuiltinFn{"edit:complete-getopt", complGetopt},
121 &eval.BuiltinFn{"edit:complex-candidate", outputComplexCandidate},
122 &eval.BuiltinFn{"edit:styled", styled},
123 &eval.BuiltinFn{"edit:-dump-buf", _dumpBuf},
124 )
125
126 modules["edit"] = ns
127 // Install other modules.
128 for module, builtins := range builtinMaps {
129 if module != "" {
130 modules["edit:"+module] = makeNamespaceFromBuiltins(builtins)
131 }
132 }
133 }
134
135 // CallFn calls an Fn, displaying its outputs and possible errors as editor
136 // notifications. It is the preferred way to call a Fn while the editor is
137 // active.
138 func (ed *Editor) CallFn(fn eval.CallableValue, args ...eval.Value) {
139 if b, ok := fn.(*BuiltinFn); ok {
140 // Builtin function: quick path.
141 b.impl(ed)
142 return
143 }
144
145 rout, chanOut, ports, err := makePorts()
146 if err != nil {
147 return
148 }
149
150 // Goroutines to collect output.
151 var wg sync.WaitGroup
152 wg.Add(2)
153 go func() {
154 rd := bufio.NewReader(rout)
155 for {
156 line, err := rd.ReadString('\n')
157 if err != nil {
158 break
159 }
160 ed.Notify("[bytes output] %s", line[:len(line)-1])
161 }
162 rout.Close()
163 wg.Done()
164 }()
165 go func() {
166 for v := range chanOut {
167 ed.Notify("[value output] %s", v.Repr(eval.NoPretty))
168 }
169 wg.Done()
170 }()
171
172 // XXX There is no source to pass to NewTopEvalCtx.
173 ec := eval.NewTopEvalCtx(ed.evaler, "[editor]", "", ports)
174 ex := ec.PCall(fn, args, eval.NoOpts)
175 if ex != nil {
176 ed.Notify("function error: %s", ex.Error())
177 }
178
179 eval.ClosePorts(ports)
180 wg.Wait()
181 ed.refresh(true, true)
182 }
183
184 // makePorts connects stdin to /dev/null and a closed channel, identifies
185 // stdout and stderr and connects them to a pipe and channel. It returns the
186 // other end of stdout and the resulting []*eval.Port. The caller is
187 // responsible for closing the returned file and calling eval.ClosePorts on the
188 // ports.
189 func makePorts() (*os.File, chan eval.Value, []*eval.Port, error) {
190 // Output
191 rout, out, err := os.Pipe()
192 if err != nil {
193 logger.Println(err)
194 return nil, nil, nil, err
195 }
196 chanOut := make(chan eval.Value)
197
198 return rout, chanOut, []*eval.Port{
199 eval.DevNullClosedChan,
200 {File: out, CloseFile: true, Chan: chanOut, CloseChan: true},
201 {File: out, Chan: chanOut},
202 }, nil
203 }
204
205 // callPrompt calls a Fn, assuming that it is a prompt. It calls the Fn with no
206 // arguments and closed input, and converts its outputs to styled objects.
207 func callPrompt(ed *Editor, fn eval.Callable) []*ui.Styled {
208 ports := []*eval.Port{eval.DevNullClosedChan, {File: os.Stdout}, {File: os.Stderr}}
209
210 // XXX There is no source to pass to NewTopEvalCtx.
211 ec := eval.NewTopEvalCtx(ed.evaler, "[editor prompt]", "", ports)
212 values, err := ec.PCaptureOutput(fn, nil, eval.NoOpts)
213 if err != nil {
214 ed.Notify("prompt function error: %v", err)
215 return nil
216 }
217
218 var ss []*ui.Styled
219 for _, v := range values {
220 if s, ok := v.(*ui.Styled); ok {
221 ss = append(ss, s)
222 } else {
223 ss = append(ss, &ui.Styled{eval.ToString(v), ui.Styles{}})
224 }
225 }
226 return ss
227 }
228
229 // callArgCompleter calls a Fn, assuming that it is an arg completer. It calls
230 // the Fn with specified arguments and closed input, and converts its output to
231 // candidate objects.
232 func callArgCompleter(fn eval.CallableValue,
233 ev *eval.Evaler, words []string) ([]rawCandidate, error) {
234
235 // Quick path for builtin arg completers.
236 if builtin, ok := fn.(*builtinArgCompleter); ok {
237 return builtin.impl(words, ev)
238 }
239
240 ports := []*eval.Port{
241 eval.DevNullClosedChan, {File: os.Stdout}, {File: os.Stderr}}
242
243 args := make([]eval.Value, len(words))
244 for i, word := range words {
245 args[i] = eval.String(word)
246 }
247
248 // XXX There is no source to pass to NewTopEvalCtx.
249 ec := eval.NewTopEvalCtx(ev, "[editor completer]", "", ports)
250 values, err := ec.PCaptureOutput(fn, args, eval.NoOpts)
251 if err != nil {
252 return nil, errors.New("completer error: " + err.Error())
253 }
254
255 cands := make([]rawCandidate, len(values))
256 for i, v := range values {
257 switch v := v.(type) {
258 case rawCandidate:
259 cands[i] = v
260 case eval.String:
261 cands[i] = plainCandidate(v)
262 default:
263 return nil, errors.New("completer must output string or candidate")
264 }
265 }
266 return cands, nil
267 }
268
269 // outputComplexCandidate composes a complexCandidate from its args.
270 func outputComplexCandidate(ec *eval.EvalCtx, a []eval.Value, o map[string]eval.Value) {
271 var style string
272
273 c := &complexCandidate{}
274
275 eval.ScanArgs(a, &c.stem)
276 eval.ScanOpts(o,
277 eval.Opt{"code-suffix", &c.codeSuffix, eval.String("")},
278 eval.Opt{"display-suffix", &c.displaySuffix, eval.String("")},
279 eval.Opt{"style", &style, eval.String("")},
280 )
281 if style != "" {
282 c.style = ui.StylesFromString(style)
283 }
284
285 out := ec.OutputChan()
286 out <- c
287 }
0 package edit
1
2 import (
3 "testing"
4
5 "github.com/elves/elvish/eval"
6 "github.com/elves/elvish/util"
7 )
8
9 func TestBuiltinFn(t *testing.T) {
10 called := false
11 builtinFn := &BuiltinFn{"foobar", func(*Editor) {
12 if called {
13 t.Errorf("builtin impl called multiple times, called not reset")
14 }
15 called = true
16 }}
17
18 if kind := builtinFn.Kind(); kind != "fn" {
19 t.Errorf("Kind of BuiltinFn should be fn, is %q", kind)
20 }
21 if repr := builtinFn.Repr(10); repr != "$foobar" {
22 t.Errorf("Repr of BuiltinFn should be $foobar, is %q", repr)
23 }
24
25 ec := &eval.EvalCtx{Evaler: &eval.Evaler{}}
26
27 if !util.Throws(func() { builtinFn.Call(ec, nil, nil) }, errEditorInvalid) {
28 t.Errorf("BuiltinFn should error when Editor is nil, didn't")
29 }
30
31 ec.Editor = &Editor{active: false}
32 if !util.Throws(func() { builtinFn.Call(ec, nil, nil) }, errEditorInactive) {
33 t.Errorf("BuiltinFn should error when Editor is inactive, didn't")
34 }
35
36 ec.Editor = &Editor{active: true}
37
38 if !util.Throws(func() {
39 builtinFn.Call(ec, []eval.Value{eval.String("2")}, nil)
40 }, eval.ErrNoArgAccepted) {
41 t.Errorf("BuiltinFn should error when argument was supplied, didn't")
42 }
43
44 if !util.Throws(func() {
45 builtinFn.Call(ec, nil, map[string]eval.Value{"a": eval.String("b")})
46 }, eval.ErrNoOptAccepted) {
47 t.Errorf("BuiltinFn should error when option was supplied, didn't")
48 }
49
50 builtinFn.Call(ec, nil, nil)
51 if !called {
52 t.Errorf("BuiltinFn should call its implementation, didn't")
53 }
54 }
0 package edit
1
2 // Trivial utilities for the elvishscript API.
3
4 import (
5 "fmt"
6
7 "github.com/elves/elvish/util"
8 )
9
10 func throw(e error) {
11 util.Throw(e)
12 }
13
14 func maybeThrow(e error) {
15 if e != nil {
16 util.Throw(e)
17 }
18 }
19
20 func throwf(format string, args ...interface{}) {
21 util.Throw(fmt.Errorf(format, args...))
22 }
0 package edit
1
2 import (
3 "errors"
4
5 "github.com/elves/elvish/eval"
6 )
7
8 // The $edit:completer map, and its default values.
9
10 var (
11 // ErrCompleterMustBeFn is thrown if the user has put a non-function entry
12 // in $edit:completer, and that entry needs to be used for completion.
13 // TODO(xiaq): Detect the type violation when the user modifies
14 // $edit:completer.
15 ErrCompleterMustBeFn = errors.New("completer must be fn")
16 // ErrCompleterArgMustBeString is thrown when a builtin argument completer
17 // is called with non-string arguments.
18 ErrCompleterArgMustBeString = errors.New("arguments to arg completers must be string")
19 // ErrTooFewArguments is thrown when a builtin argument completer is called
20 // with too few arguments.
21 ErrTooFewArguments = errors.New("too few arguments")
22 )
23
24 var (
25 argCompletersData = map[string]*builtinArgCompleter{
26 "": {"complete-filename", complFilename},
27 "sudo": {"complete-sudo", complSudo},
28 }
29 )
30
31 var _ = registerVariable("completer", argCompleterVariable)
32
33 func argCompleterVariable() eval.Variable {
34 m := map[eval.Value]eval.Value{}
35 for k, v := range argCompletersData {
36 m[eval.String(k)] = v
37 }
38 return eval.NewPtrVariableWithValidator(eval.NewMap(m), eval.ShouldBeMap)
39 }
40
41 func (ed *Editor) argCompleter() eval.Map {
42 return ed.variables["completer"].Get().(eval.Map)
43 }
44
45 func completeArg(words []string, ev *eval.Evaler) ([]rawCandidate, error) {
46 logger.Printf("completing argument: %q", words)
47 // XXX(xiaq): not the best way to get argCompleter.
48 m := ev.Editor.(*Editor).argCompleter()
49 var v eval.Value
50 if m.HasKey(eval.String(words[0])) {
51 v = m.IndexOne(eval.String(words[0]))
52 } else {
53 v = m.IndexOne(eval.String(""))
54 }
55 fn, ok := v.(eval.CallableValue)
56 if !ok {
57 return nil, ErrCompleterMustBeFn
58 }
59 return callArgCompleter(fn, ev, words)
60 }
61
62 type builtinArgCompleter struct {
63 name string
64 impl func([]string, *eval.Evaler) ([]rawCandidate, error)
65 }
66
67 var _ eval.CallableValue = &builtinArgCompleter{}
68
69 func (bac *builtinArgCompleter) Kind() string {
70 return "fn"
71 }
72
73 func (bac *builtinArgCompleter) Repr(int) string {
74 return "$edit:&" + bac.name
75 }
76
77 func (bac *builtinArgCompleter) Call(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
78 eval.TakeNoOpt(opts)
79 words := make([]string, len(args))
80 for i, arg := range args {
81 s, ok := arg.(eval.String)
82 if !ok {
83 throw(ErrCompleterArgMustBeString)
84 }
85 words[i] = string(s)
86 }
87 cands, err := bac.impl(words, ec.Evaler)
88 maybeThrow(err)
89 out := ec.OutputChan()
90 for _, cand := range cands {
91 out <- cand
92 }
93 }
94
95 func complFilename(words []string, ev *eval.Evaler) ([]rawCandidate, error) {
96 if len(words) < 1 {
97 return nil, ErrTooFewArguments
98 }
99 return complFilenameInner(words[len(words)-1], false)
100 }
101
102 func complSudo(words []string, ev *eval.Evaler) ([]rawCandidate, error) {
103 if len(words) < 2 {
104 return nil, ErrTooFewArguments
105 }
106 if len(words) == 2 {
107 return complFormHeadInner(words[1], ev)
108 }
109 return completeArg(words[1:], ev)
110 }
0 package edit
1
2 import (
3 "github.com/elves/elvish/edit/ui"
4 "github.com/elves/elvish/eval"
5 "github.com/elves/elvish/parse"
6 )
7
8 func getBinding(mode string, k ui.Key) eval.CallableValue {
9 bindings := keyBindings[mode]
10 if bindings == nil {
11 return nil
12 }
13 v, ok := bindings[k]
14 if ok {
15 return v
16 }
17 return bindings[ui.Default]
18 }
19
20 // BindingTable adapts a binding table to eval.IndexSetter, so that it can be
21 // manipulated in elvish script.
22 type BindingTable struct {
23 inner map[ui.Key]eval.CallableValue
24 }
25
26 // Kind returns "map".
27 func (BindingTable) Kind() string {
28 return "map"
29 }
30
31 // Repr returns the representation of the binding table as if it were an
32 // ordinary map.
33 func (bt BindingTable) Repr(indent int) string {
34 var builder eval.MapReprBuilder
35 builder.Indent = indent
36 for k, v := range bt.inner {
37 builder.WritePair(parse.Quote(k.String()), indent+2, v.Repr(indent+2))
38 }
39 return builder.String()
40 }
41
42 // IndexOne returns the value with the specified map key. The map key is first
43 // converted into an internal Key struct.
44 func (bt BindingTable) IndexOne(idx eval.Value) eval.Value {
45 return bt.inner[ui.ToKey(idx)]
46 }
47
48 // IndexSet sets the value with the specified map key. The map key is first
49 // converted into an internal Key struct. The set value must be a callable one,
50 // otherwise an error is thrown.
51 func (bt BindingTable) IndexSet(idx, v eval.Value) {
52 key := ui.ToKey(idx)
53 f, ok := v.(eval.CallableValue)
54 if !ok {
55 throwf("want function, got %s", v.Kind())
56 }
57 bt.inner[key] = f
58 }
0 package edit
1
2 import (
3 "strings"
4
5 "github.com/elves/elvish/edit/ui"
6 "github.com/elves/elvish/util"
7 )
8
9 // cell is an indivisible unit on the screen. It is not necessarily 1 column
10 // wide.
11 type cell struct {
12 string
13 width byte
14 style string
15 }
16
17 // Pos is the position within a buffer.
18 type Pos struct {
19 line, col int
20 }
21
22 var invalidPos = Pos{-1, -1}
23
24 // cellsWidth returns the total width of a slice of cells.
25 func cellsWidth(cs []cell) int {
26 w := 0
27 for _, c := range cs {
28 w += int(c.width)
29 }
30 return w
31 }
32
33 func makeSpacing(n int) []cell {
34 s := make([]cell, n)
35 for i := 0; i < n; i++ {
36 s[i].string = " "
37 s[i].width = 1
38 }
39 return s
40 }
41
42 func compareCells(r1, r2 []cell) (bool, int) {
43 for i, c := range r1 {
44 if i >= len(r2) || c != r2[i] {
45 return false, i
46 }
47 }
48 if len(r1) < len(r2) {
49 return false, len(r1)
50 }
51 return true, 0
52 }
53
54 // buffer reflects a continuous range of lines on the terminal.
55 //
56 // The Unix terminal API provides only awkward ways of querying the terminal
57 // buffer, so we keep an internal reflection and do one-way synchronizations
58 // (buffer -> terminal, and not the other way around). This requires us to
59 // exactly match the terminal's idea of the width of characters (wcwidth) and
60 // where to insert soft carriage returns, so there could be bugs.
61 type buffer struct {
62 width, col, indent int
63 // eagerWrap controls whether to wrap line as soon as the cursor reaches the
64 // right edge of the terminal. This is not often desirable as it creates
65 // unneessary line breaks, but is is useful when echoing the user input.
66 // will otherwise
67 eagerWrap bool
68 // lines the content of the buffer.
69 lines [][]cell
70 dot Pos // dot is what the user perceives as the cursor.
71 }
72
73 // newBuffer builds a new buffer, with one empty line.
74 func newBuffer(width int) *buffer {
75 return &buffer{width: width, lines: [][]cell{make([]cell, 0, width)}}
76 }
77
78 func (b *buffer) setIndent(indent int) *buffer {
79 b.indent = indent
80 return b
81 }
82
83 func (b *buffer) setEagerWrap(v bool) *buffer {
84 b.eagerWrap = v
85 return b
86 }
87
88 func (b *buffer) setLines(lines ...[]cell) *buffer {
89 b.lines = lines
90 b.col = cellsWidth(lines[len(lines)-1])
91 return b
92 }
93
94 func (b *buffer) setDot(dot Pos) *buffer {
95 b.dot = dot
96 return b
97 }
98
99 func (b *buffer) cursor() Pos {
100 return Pos{len(b.lines) - 1, b.col}
101 }
102
103 func buffersHeight(bufs ...*buffer) (l int) {
104 for _, buf := range bufs {
105 if buf != nil {
106 l += len(buf.lines)
107 }
108 }
109 return
110 }
111
112 // Low level buffer mutations.
113
114 func (b *buffer) appendLine() {
115 b.lines = append(b.lines, make([]cell, 0, b.width))
116 b.col = 0
117 }
118
119 func (b *buffer) appendCell(c cell) {
120 n := len(b.lines)
121 b.lines[n-1] = append(b.lines[n-1], c)
122 b.col += int(c.width)
123 }
124
125 // High-level buffer mutations.
126
127 func (b *buffer) newline() {
128 b.appendLine()
129
130 if b.indent > 0 {
131 for i := 0; i < b.indent; i++ {
132 b.appendCell(cell{string: " ", width: 1})
133 }
134 }
135 }
136
137 // write appends a single rune to a buffer, wrapping the line when needed. If
138 // the rune is a control character, it will be written using the caret notation
139 // (like ^X) and gets the additional style of styleForControlChar.
140 func (b *buffer) write(r rune, style string) {
141 if r == '\n' {
142 b.newline()
143 return
144 }
145 wd := util.Wcwidth(r)
146 c := cell{string(r), byte(wd), style}
147 if r < 0x20 || r == 0x7f {
148 wd = 2
149 if style != "" {
150 style = style + ";" + styleForControlChar.String()
151 } else {
152 style = styleForControlChar.String()
153 }
154 c = cell{"^" + string(r^0x40), 2, style}
155 }
156
157 if b.col+wd > b.width {
158 b.newline()
159 b.appendCell(c)
160 } else {
161 b.appendCell(c)
162 if b.col == b.width && b.eagerWrap {
163 b.newline()
164 }
165 }
166 }
167
168 // writes appends every rune of a string to a buffer, all with the same style.
169 func (b *buffer) writes(text, style string) {
170 for _, r := range text {
171 b.write(r, style)
172 }
173 }
174
175 // writePadding writes w spaces.
176 func (b *buffer) writePadding(w int, style string) {
177 b.writes(strings.Repeat(" ", w), style)
178 }
179
180 // writeStyleds writes a slice of styled structs.
181 func (b *buffer) writeStyleds(ss []*ui.Styled) {
182 for _, s := range ss {
183 b.writes(s.Text, s.Styles.String())
184 }
185 }
186
187 // trimToLines trims a buffer to the lines [low, high).
188 func (b *buffer) trimToLines(low, high int) {
189 for i := 0; i < low; i++ {
190 b.lines[i] = nil
191 }
192 for i := high; i < len(b.lines); i++ {
193 b.lines[i] = nil
194 }
195 b.lines = b.lines[low:high]
196 b.dot.line -= low
197 if b.dot.line < 0 {
198 b.dot.line = 0
199 }
200 }
201
202 func (b *buffer) extend(b2 *buffer, moveDot bool) {
203 if b2 != nil && b2.lines != nil {
204 if moveDot {
205 b.dot.line = b2.dot.line + len(b.lines)
206 b.dot.col = b2.dot.col
207 }
208 b.lines = append(b.lines, b2.lines...)
209 b.col = b2.col
210 }
211 }
212
213 // extendRight extends b to the right. It pads each line in b to be at least of
214 // width w and appends the corresponding line in b2 to it, making new lines in b
215 // when b2 has more lines than b.
216 // BUG(xiaq): after calling extendRight, the widths of some lines can exceed
217 // b.width.
218 func (b *buffer) extendRight(b2 *buffer, w int) {
219 i := 0
220 for ; i < len(b.lines) && i < len(b2.lines); i++ {
221 if w0 := cellsWidth(b.lines[i]); w0 < w {
222 b.lines[i] = append(b.lines[i], makeSpacing(w-w0)...)
223 }
224 b.lines[i] = append(b.lines[i], b2.lines[i]...)
225 }
226 for ; i < len(b2.lines); i++ {
227 row := append(makeSpacing(w), b2.lines[i]...)
228 b.lines = append(b.lines, row)
229 }
230 b.col = cellsWidth(b.lines[len(b.lines)-1])
231 }
0 package edit
1
2 import (
3 "reflect"
4 "testing"
5 )
6
7 var cellsWidthTests = []struct {
8 cells []cell
9 wantWidth int
10 }{
11 {[]cell{}, 0},
12 {[]cell{{"a", 1, ""}, {"好", 2, ""}}, 3},
13 }
14
15 func TestCellsWidth(t *testing.T) {
16 for _, test := range cellsWidthTests {
17 if width := cellsWidth(test.cells); width != test.wantWidth {
18 t.Errorf("cellsWidth(%v) = %v, want %v",
19 test.cells, width, test.wantWidth)
20 }
21 }
22 }
23
24 var makeSpacingTests = []struct {
25 n int
26 want []cell
27 }{
28 {0, []cell{}},
29 {1, []cell{{" ", 1, ""}}},
30 {4, []cell{{" ", 1, ""}, {" ", 1, ""}, {" ", 1, ""}, {" ", 1, ""}}},
31 }
32
33 func TestMakeSpacing(t *testing.T) {
34 for _, test := range makeSpacingTests {
35 if got := makeSpacing(test.n); !reflect.DeepEqual(got, test.want) {
36 t.Errorf("makeSpacing(%v) = %v, want %v", test.n, got, test.want)
37 }
38 }
39 }
40
41 var compareCellsTests = []struct {
42 cells1 []cell
43 cells2 []cell
44 wantEqual bool
45 wantIndex int
46 }{
47 {[]cell{}, []cell{}, true, 0},
48 {[]cell{}, []cell{{"a", 1, ""}}, false, 0},
49 {
50 []cell{{"a", 1, ""}, {"好", 2, ""}, {"b", 1, ""}},
51 []cell{{"a", 1, ""}, {"好", 2, ""}, {"c", 1, ""}},
52 false, 2,
53 },
54 {
55 []cell{{"a", 1, ""}, {"好", 2, ""}, {"b", 1, ""}},
56 []cell{{"a", 1, ""}, {"好", 2, "1"}, {"c", 1, ""}},
57 false, 1,
58 },
59 }
60
61 func TestCompareCells(t *testing.T) {
62 for _, test := range compareCellsTests {
63 equal, index := compareCells(test.cells1, test.cells2)
64 if equal != test.wantEqual || index != test.wantIndex {
65 t.Errorf("compareCells(%v, %v) = (%v, %v), want (%v, %v)",
66 test.cells1, test.cells2,
67 equal, index, test.wantEqual, test.wantIndex)
68 }
69 }
70 }
71
72 var bufferCursorTests = []struct {
73 buf *buffer
74 want Pos
75 }{
76 {newBuffer(10), Pos{0, 0}},
77 {newBuffer(10).setLines([]cell{{"a", 1, ""}}, []cell{{"好", 2, ""}}),
78 Pos{1, 2}},
79 }
80
81 func TestBufferCursor(t *testing.T) {
82 for _, test := range bufferCursorTests {
83 if p := test.buf.cursor(); p != test.want {
84 t.Errorf("(%v).cursor() = %v, want %v", test.buf, p, test.want)
85 }
86 }
87 }
88
89 var buffersHeighTests = []struct {
90 buffers []*buffer
91 want int
92 }{
93 {nil, 0},
94 {[]*buffer{newBuffer(10)}, 1},
95 {[]*buffer{
96 newBuffer(10).setLines([]cell{}, []cell{}),
97 newBuffer(10),
98 newBuffer(10).setLines([]cell{}, []cell{})},
99 5},
100 }
101
102 func TestBuffersHeight(t *testing.T) {
103 for _, test := range buffersHeighTests {
104 if h := buffersHeight(test.buffers...); h != test.want {
105 t.Errorf("buffersHeight(%v...) = %v, want %v",
106 test.buffers, h, test.want)
107 }
108 }
109 }
110
111 var bufferWritesTests = []struct {
112 buf *buffer
113 text string
114 style string
115 want *buffer
116 }{
117 // Writing nothing.
118 {newBuffer(10), "", "", newBuffer(10)},
119 // Writing a single rune.
120 {newBuffer(10), "a", "1", newBuffer(10).setLines([]cell{{"a", 1, "1"}})},
121 // Writing control character.
122 {newBuffer(10), "\033", "",
123 newBuffer(10).setLines(
124 []cell{{"^[", 2, styleForControlChar.String()}},
125 )},
126 // Writing styled control character.
127 {newBuffer(10), "a\033b", "1",
128 newBuffer(10).setLines(
129 []cell{
130 {"a", 1, "1"},
131 {"^[", 2, "1;" + styleForControlChar.String()},
132 {"b", 1, "1"},
133 },
134 )},
135 // Writing text containing a newline.
136 {newBuffer(10), "a\nb", "1",
137 newBuffer(10).setLines(
138 []cell{{"a", 1, "1"}}, []cell{{"b", 1, "1"}},
139 )},
140 // Writing text containing a newline when there is indent.
141 {newBuffer(10).setIndent(2), "a\nb", "1",
142 newBuffer(10).setIndent(2).setLines(
143 []cell{{"a", 1, "1"}},
144 []cell{{" ", 1, ""}, {" ", 1, ""}, {"b", 1, "1"}},
145 )},
146 // Writing long text that triggers wrapping.
147 {newBuffer(4), "aaaab", "1",
148 newBuffer(4).setLines(
149 []cell{{"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}},
150 []cell{{"b", 1, "1"}},
151 )},
152 // Writing long text that triggers wrapping when there is indent.
153 {newBuffer(4).setIndent(2), "aaaab", "1",
154 newBuffer(4).setIndent(2).setLines(
155 []cell{{"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}},
156 []cell{{" ", 1, ""}, {" ", 1, ""}, {"b", 1, "1"}},
157 )},
158 // Writing long text that triggers eager wrapping.
159 {newBuffer(4).setIndent(2).setEagerWrap(true), "aaaa", "1",
160 newBuffer(4).setIndent(2).setEagerWrap(true).setLines(
161 []cell{{"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}, {"a", 1, "1"}},
162 []cell{{" ", 1, ""}, {" ", 1, ""}},
163 )},
164 }
165
166 // TestBufferWrites tests buffer.writes by calling writes on a buffer and see if
167 // the buffer matches what is expected.
168 func TestBufferWrites(t *testing.T) {
169 for _, test := range bufferWritesTests {
170 b := test.buf
171 b.writes(test.text, test.style)
172 if !reflect.DeepEqual(b, test.want) {
173 t.Errorf("buf.writes(%q, %q) makes it %v, want %v",
174 test.text, test.style, b, test.want)
175 }
176 }
177 }
178
179 var bufferTrimToLinesTests = []struct {
180 buf *buffer
181 low int
182 high int
183 want *buffer
184 }{
185 {
186 newBuffer(10).setLines(
187 []cell{{"a", 1, ""}}, []cell{{"b", 1, ""}},
188 []cell{{"c", 1, ""}}, []cell{{"d", 1, ""}},
189 ), 0, 2,
190 newBuffer(10).setLines(
191 []cell{{"a", 1, ""}}, []cell{{"b", 1, ""}},
192 ),
193 },
194 // With dot.
195 {
196 newBuffer(10).setLines(
197 []cell{{"a", 1, ""}}, []cell{{"b", 1, ""}},
198 []cell{{"c", 1, ""}}, []cell{{"d", 1, ""}},
199 ).setDot(Pos{1, 1}), 1, 3,
200 newBuffer(10).setLines(
201 []cell{{"b", 1, ""}}, []cell{{"c", 1, ""}},
202 ).setDot(Pos{0, 1}),
203 },
204 // With dot that is going to be trimmed away.
205 {
206 newBuffer(10).setLines(
207 []cell{{"a", 1, ""}}, []cell{{"b", 1, ""}},
208 []cell{{"c", 1, ""}}, []cell{{"d", 1, ""}},
209 ).setDot(Pos{0, 1}), 1, 3,
210 newBuffer(10).setLines(
211 []cell{{"b", 1, ""}}, []cell{{"c", 1, ""}},
212 ).setDot(Pos{0, 1}),
213 },
214 }
215
216 func TestBufferTrimToLines(t *testing.T) {
217 for _, test := range bufferTrimToLinesTests {
218 b := test.buf
219 b.trimToLines(test.low, test.high)
220 if !reflect.DeepEqual(b, test.want) {
221 t.Errorf("buf.trimToLines(%v, %v) makes it %v, want %v",
222 test.low, test.high, b, test.want)
223 }
224 }
225 }
226
227 var bufferExtendTests = []struct {
228 buf *buffer
229 buf2 *buffer
230 moveDot bool
231 want *buffer
232 }{
233 {
234 newBuffer(10).setLines([]cell{{"a", 1, ""}}, []cell{{"b", 1, ""}}),
235 newBuffer(11).setLines([]cell{{"c", 1, ""}}, []cell{{"d", 1, ""}}),
236 false,
237 newBuffer(10).setLines(
238 []cell{{"a", 1, ""}}, []cell{{"b", 1, ""}},
239 []cell{{"c", 1, ""}}, []cell{{"d", 1, ""}},
240 ),
241 },
242 // Moving dot.
243 {
244 newBuffer(10).setLines([]cell{{"a", 1, ""}}, []cell{{"b", 1, ""}}),
245 newBuffer(11).setLines(
246 []cell{{"c", 1, ""}}, []cell{{"d", 1, ""}},
247 ).setDot(Pos{1, 1}),
248 true,
249 newBuffer(10).setLines(
250 []cell{{"a", 1, ""}}, []cell{{"b", 1, ""}},
251 []cell{{"c", 1, ""}}, []cell{{"d", 1, ""}},
252 ).setDot(Pos{3, 1}),
253 },
254 }
255
256 func TestExtend(t *testing.T) {
257 for _, test := range bufferExtendTests {
258 b := test.buf
259 b.extend(test.buf2, test.moveDot)
260 if !reflect.DeepEqual(b, test.want) {
261 t.Errorf("buf.extend(%v, %v) makes it %v, want %v",
262 test.buf2, test.moveDot, b, test.want)
263 }
264 }
265 }
266
267 var bufferExtendRightTests = []struct {
268 buf *buffer
269 buf2 *buffer
270 w int
271 want *buffer
272 }{
273 // No padding, equal height.
274 {
275 newBuffer(10).setLines([]cell{{"a", 1, ""}}, []cell{}),
276 newBuffer(11).setLines([]cell{{"c", 1, ""}}, []cell{{"d", 1, ""}}),
277 0,
278 newBuffer(10).setLines(
279 []cell{{"a", 1, ""}, {"c", 1, ""}},
280 []cell{{"d", 1, ""}},
281 ),
282 },
283 // With padding.
284 {
285 newBuffer(10).setLines([]cell{{"a", 1, ""}}, []cell{{"b", 1, ""}}),
286 newBuffer(11).setLines([]cell{{"c", 1, ""}}, []cell{{"d", 1, ""}}),
287 2,
288 newBuffer(10).setLines(
289 []cell{{"a", 1, ""}, {" ", 1, ""}, {"c", 1, ""}},
290 []cell{{"b", 1, ""}, {" ", 1, ""}, {"d", 1, ""}},
291 ),
292 },
293 // buf is higher.
294 {
295 newBuffer(10).setLines(
296 []cell{{"a", 1, ""}},
297 []cell{{"b", 1, ""}},
298 []cell{{"x", 1, ""}},
299 ),
300 newBuffer(11).setLines([]cell{{"c", 1, ""}}, []cell{{"d", 1, ""}}),
301 1,
302 newBuffer(10).setLines(
303 []cell{{"a", 1, ""}, {"c", 1, ""}},
304 []cell{{"b", 1, ""}, {"d", 1, ""}},
305 []cell{{"x", 1, ""}},
306 ),
307 },
308 // buf2 is higher.
309 {
310 newBuffer(10).setLines([]cell{{"a", 1, ""}}, []cell{{"b", 1, ""}}),
311 newBuffer(11).setLines(
312 []cell{{"c", 1, ""}}, []cell{{"d", 1, ""}}, []cell{{"e", 1, ""}},
313 ),
314 1,
315 newBuffer(10).setLines(
316 []cell{{"a", 1, ""}, {"c", 1, ""}},
317 []cell{{"b", 1, ""}, {"d", 1, ""}},
318 []cell{{" ", 1, ""}, {"e", 1, ""}},
319 ),
320 },
321 }
322
323 func TestExtendRight(t *testing.T) {
324 for _, test := range bufferExtendRightTests {
325 b := test.buf
326 b.extendRight(test.buf2, test.w)
327 if !reflect.DeepEqual(b, test.want) {
328 t.Errorf("buf.extendRight(%v, %v) makes it %v, want %v",
329 test.buf2, test.w, b, test.want)
330 }
331 }
332 }
0 package edit
1
2 import (
3 "github.com/elves/elvish/edit/ui"
4 "github.com/elves/elvish/eval"
5 "github.com/elves/elvish/parse"
6 )
7
8 type candidate struct {
9 code string // This is what will be substitued on the command line.
10 menu ui.Styled // This is what is displayed in the completion menu.
11 }
12
13 // rawCandidate is what can be converted to a candidate.
14 type rawCandidate interface {
15 eval.Value
16 text() string
17 cook(q parse.PrimaryType) *candidate
18 }
19
20 type plainCandidate string
21
22 func (plainCandidate) Kind() string { return "string" }
23 func (p plainCandidate) Repr(l int) string { return eval.String(p).Repr(l) }
24
25 func (p plainCandidate) text() string { return string(p) }
26
27 func (p plainCandidate) cook(q parse.PrimaryType) *candidate {
28 s := string(p)
29 quoted, _ := parse.QuoteAs(s, q)
30 return &candidate{code: quoted, menu: ui.Unstyled(s)}
31 }
32
33 type complexCandidate struct {
34 stem string // Used in the code and the menu.
35 codeSuffix string // Appended to the code.
36 displaySuffix string // Appended to the display.
37 style ui.Styles // Used in the menu.
38 }
39
40 func (c *complexCandidate) Kind() string { return "map" }
41 func (c *complexCandidate) Repr(int) string { return "<complex candidate>" }
42
43 func (c *complexCandidate) text() string { return c.stem }
44
45 func (c *complexCandidate) cook(q parse.PrimaryType) *candidate {
46 quoted, _ := parse.QuoteAs(c.stem, q)
47 return &candidate{
48 code: quoted + c.codeSuffix,
49 menu: ui.Styled{c.stem + c.displaySuffix, c.style},
50 }
51 }
52
53 func cookCandidates(raws []rawCandidate, pattern string,
54 match func(string, string) bool, q parse.PrimaryType) []*candidate {
55
56 var cooked []*candidate
57 for _, raw := range raws {
58 if match(raw.text(), pattern) {
59 cooked = append(cooked, raw.cook(q))
60 }
61 }
62 return cooked
63 }
0 package edit
1
2 import (
3 "strings"
4 "unicode/utf8"
5
6 "github.com/elves/elvish/eval"
7 "github.com/elves/elvish/getopt"
8 "github.com/elves/elvish/parse"
9 )
10
11 func complGetopt(ec *eval.EvalCtx, a []eval.Value, o map[string]eval.Value) {
12 var elemsv, optsv, argsv eval.IterableValue
13 eval.ScanArgs(a, &elemsv, &optsv, &argsv)
14 eval.TakeNoOpt(o)
15
16 var (
17 elems []string
18 opts []*getopt.Option
19 args []eval.CallableValue
20 variadic bool
21 )
22 desc := make(map[*getopt.Option]string)
23 // Convert arguments.
24 elemsv.Iterate(func(v eval.Value) bool {
25 elem, ok := v.(eval.String)
26 if !ok {
27 throwf("arg should be string, got %s", v.Kind())
28 }
29 elems = append(elems, string(elem))
30 return true
31 })
32 optsv.Iterate(func(v eval.Value) bool {
33 m, ok := v.(eval.MapLike)
34 if !ok {
35 throwf("opt should be map-like, got %s", v.Kind())
36 }
37 get := func(ks string) (string, bool) {
38 kv := eval.String(ks)
39 if !m.HasKey(kv) {
40 return "", false
41 }
42 vv := m.IndexOne(kv)
43 if vs, ok := vv.(eval.String); ok {
44 return string(vs), true
45 } else {
46 throwf("%s should be string, got %s", ks, vs.Kind())
47 panic("unreachable")
48 }
49 }
50
51 opt := &getopt.Option{}
52 if s, ok := get("short"); ok {
53 r, size := utf8.DecodeRuneInString(s)
54 if r == utf8.RuneError || size != len(s) {
55 throwf("short option should be exactly one rune, got %v", parse.Quote(s))
56 }
57 opt.Short = r
58 }
59 if s, ok := get("long"); ok {
60 opt.Long = s
61 }
62 if opt.Short == 0 && opt.Long == "" {
63 throwf("opt should have at least one of short and long forms")
64 }
65 if s, ok := get("desc"); ok {
66 desc[opt] = s
67 }
68 opts = append(opts, opt)
69 return true
70 })
71 argsv.Iterate(func(v eval.Value) bool {
72 sv, ok := v.(eval.String)
73 if ok {
74 if string(sv) == "..." {
75 variadic = true
76 return true
77 }
78 throwf("string except for ... not allowed as argument handler, got %s", parse.Quote(string(sv)))
79 }
80 arg, ok := v.(eval.CallableValue)
81 if !ok {
82 throwf("argument handler should be fn, got %s", v.Kind())
83 }
84 args = append(args, arg)
85 return true
86 })
87 // TODO Configurable config
88 g := getopt.Getopt{opts, getopt.GNUGetoptLong}
89 _, parsedArgs, ctx := g.Parse(elems)
90 out := ec.OutputChan()
91
92 putShortOpt := func(opt *getopt.Option) {
93 c := &complexCandidate{stem: "-" + string(opt.Short)}
94 if d, ok := desc[opt]; ok {
95 c.displaySuffix = " (" + d + ")"
96 }
97 out <- c
98 }
99 putLongOpt := func(opt *getopt.Option) {
100 c := &complexCandidate{stem: "--" + string(opt.Long)}
101 if d, ok := desc[opt]; ok {
102 c.displaySuffix = " (" + d + ")"
103 }
104 out <- c
105 }
106
107 switch ctx.Type {
108 case getopt.NewOptionOrArgument, getopt.Argument:
109 // Find argument completer
110 var argCompl eval.CallableValue
111 if len(parsedArgs) < len(args) {
112 argCompl = args[len(parsedArgs)]
113 } else if variadic {
114 argCompl = args[len(args)-1]
115 }
116 if argCompl != nil {
117 cands, err := callArgCompleter(argCompl, ec.Evaler, []string{ctx.Text})
118 maybeThrow(err)
119 for _, cand := range cands {
120 out <- cand
121 }
122 }
123 // TODO Notify that there is no suitable argument completer
124 case getopt.NewOption:
125 for _, opt := range opts {
126 if opt.Short != 0 {
127 putShortOpt(opt)
128 }
129 if opt.Long != "" {
130 putLongOpt(opt)
131 }
132 }
133 case getopt.NewLongOption:
134 for _, opt := range opts {
135 if opt.Long != "" {
136 putLongOpt(opt)
137 }
138 }
139 case getopt.LongOption:
140 for _, opt := range opts {
141 if strings.HasPrefix(opt.Long, ctx.Text) {
142 putLongOpt(opt)
143 }
144 }
145 case getopt.ChainShortOption:
146 for _, opt := range opts {
147 if opt.Short != 0 {
148 // XXX loses chained options
149 putShortOpt(opt)
150 }
151 }
152 case getopt.OptionArgument:
153 }
154 }
0 package edit
1
2 import (
3 "errors"
4 "fmt"
5 "io/ioutil"
6 "os"
7 "path"
8 "sort"
9 "strings"
10
11 "github.com/elves/elvish/edit/ui"
12 "github.com/elves/elvish/eval"
13 "github.com/elves/elvish/parse"
14 "github.com/elves/elvish/util"
15 )
16
17 var (
18 errCompletionUnapplicable = errors.New("completion unapplicable")
19 errCannotEvalIndexee = errors.New("cannot evaluate indexee")
20 errCannotIterateKey = errors.New("indexee does not support iterating keys")
21 )
22
23 // completer takes the current Node (always a leaf in the AST) and an Editor and
24 // returns a compl. If the completer does not apply to the type of the current
25 // Node, it should return an error of ErrCompletionUnapplicable.
26 type completer func(parse.Node, *eval.Evaler) (*compl, error)
27
28 // compl is the result of a completer, meaning that any of the candidates can
29 // replace the text in the interval [begin, end).
30 type compl struct {
31 begin int
32 end int
33 candidates []*candidate
34 }
35
36 // completers is the list of all completers.
37 // TODO(xiaq): Make this list programmable.
38 var completers = []struct {
39 name string
40 completer
41 }{
42 {"variable", complVariable},
43 {"index", complIndex},
44 {"command name", complFormHead},
45 {"redir", complRedir},
46 {"argument", complArg},
47 }
48
49 // complete takes a Node and Evaler and tries all completers. It returns the
50 // name of the completer, and the result and error it gave. If no completer is
51 // available, it returns an empty completer name.
52 func complete(n parse.Node, ev *eval.Evaler) (string, *compl, error) {
53 for _, item := range completers {
54 compl, err := item.completer(n, ev)
55 if compl != nil {
56 return item.name, compl, nil
57 } else if err != nil && err != errCompletionUnapplicable {
58 return item.name, nil, err
59 }
60 }
61 return "", nil, nil
62 }
63
64 // TODO(xiaq): Rewrite this to use cookCandidates
65 func complVariable(n parse.Node, ev *eval.Evaler) (*compl, error) {
66 primary := parse.GetPrimary(n)
67 if primary == nil || primary.Type != parse.Variable {
68 return nil, errCompletionUnapplicable
69 }
70
71 // The starting position of "what we are completing". First move past "$".
72 begin := n.Begin() + 1
73
74 // XXX Repeats eval.ParseVariable.
75 explode, qname := eval.ParseVariableSplice(primary.Value)
76 nsPart, nameHead := eval.ParseVariableQName(qname)
77 begin += len(explode) + len(nsPart) // Move past "@" and "ns:".
78 ns := nsPart
79 if len(ns) > 0 {
80 ns = ns[:len(ns)-1]
81 }
82
83 // Collect matching variables.
84 var entries []string
85 iterateVariables(ev, ns, func(varname string) {
86 entries = append(entries, varname)
87 })
88 // Collect namespace prefixes.
89 // TODO Support non-module namespaces.
90 for mod := range ev.Modules {
91 modNsPart := mod + ":"
92 // This is to match namespaces that are "nested" under the current
93 // namespace.
94 if hasProperPrefix(modNsPart, nsPart) {
95 entries = append(entries, modNsPart[len(nsPart):])
96 }
97 }
98 sort.Strings(entries)
99
100 var cands []*candidate
101 // XXX(xiaq): Fragile. Perhaps the signature of this function should be
102 // changed.
103 match := ev.Editor.(*Editor).matcher()
104 // Build candidates.
105 for _, varname := range entries {
106 if match(varname, nameHead) {
107 cand := &candidate{code: varname, menu: ui.Unstyled(varname)}
108 cands = append(cands, cand)
109 }
110 }
111
112 return &compl{begin, n.End(), cands}, nil
113 }
114
115 func hasProperPrefix(s, p string) bool {
116 return len(s) > len(p) && strings.HasPrefix(s, p)
117 }
118
119 func iterateVariables(ev *eval.Evaler, ns string, f func(string)) {
120 switch ns {
121 case "":
122 for varname := range ev.Builtin {
123 f(varname)
124 }
125 for varname := range ev.Global {
126 f(varname)
127 }
128 // TODO Include local names as well.
129 case "E":
130 for _, s := range os.Environ() {
131 f(s[:strings.IndexByte(s, '=')])
132 }
133 default:
134 // TODO Support non-module namespaces.
135 for varname := range ev.Modules[ns] {
136 f(varname)
137 }
138 }
139 }
140
141 func complIndex(n parse.Node, ev *eval.Evaler) (*compl, error) {
142 begin, end, current, q, indexee := findIndexContext(n)
143
144 if begin == -1 {
145 return nil, errCompletionUnapplicable
146 }
147
148 indexeeValue := purelyEvalPrimary(indexee, ev)
149 if indexeeValue == nil {
150 return nil, errCannotEvalIndexee
151 }
152 m, ok := indexeeValue.(eval.IterateKeyer)
153 if !ok {
154 return nil, errCannotIterateKey
155 }
156
157 cands := complIndexInner(m)
158 match := ev.Editor.(*Editor).matcher()
159 return &compl{begin, end, cookCandidates(cands, current, match, q)}, nil
160 }
161
162 // Find context information for complIndex. It returns the begin and end for
163 // compl, the current text of this index and its type, and the indexee node.
164 //
165 // Right now we only support cases where there is only one level of indexing,
166 // e.g. $a[<Tab> is supported but $a[x][<Tab> is not.
167 func findIndexContext(n parse.Node) (int, int, string, parse.PrimaryType, *parse.Primary) {
168 if parse.IsSep(n) {
169 if parse.IsIndexing(n.Parent()) {
170 // We are just after an opening bracket.
171 indexing := parse.GetIndexing(n.Parent())
172 if len(indexing.Indicies) == 1 {
173 return n.End(), n.End(), "", parse.Bareword, indexing.Head
174 }
175 }
176 if parse.IsArray(n.Parent()) {
177 array := n.Parent()
178 if parse.IsIndexing(array.Parent()) {
179 // We are after an existing index and spaces.
180 indexing := parse.GetIndexing(array.Parent())
181 if len(indexing.Indicies) == 1 {
182 return n.End(), n.End(), "", parse.Bareword, indexing.Head
183 }
184 }
185 }
186 }
187
188 if parse.IsPrimary(n) {
189 primary := parse.GetPrimary(n)
190 compound, current := primaryInSimpleCompound(primary)
191 if compound != nil {
192 if parse.IsArray(compound.Parent()) {
193 array := compound.Parent()
194 if parse.IsIndexing(array.Parent()) {
195 // We are just after an incomplete index.
196 indexing := parse.GetIndexing(array.Parent())
197 if len(indexing.Indicies) == 1 {
198 return compound.Begin(), compound.End(), current, primary.Type, indexing.Head
199 }
200 }
201 }
202 }
203 }
204
205 return -1, -1, "", 0, nil
206 }
207
208 func complIndexInner(m eval.IterateKeyer) []rawCandidate {
209 var keys []rawCandidate
210 m.IterateKey(func(v eval.Value) bool {
211 if keyv, ok := v.(eval.String); ok {
212 keys = append(keys, plainCandidate(keyv))
213 }
214 return true
215 })
216 sort.Sort(plainCandidates(keys))
217 return keys
218 }
219
220 func complFormHead(n parse.Node, ev *eval.Evaler) (*compl, error) {
221 begin, end, head, q := findFormHeadContext(n)
222 if begin == -1 {
223 return nil, errCompletionUnapplicable
224 }
225 cands, err := complFormHeadInner(head, ev)
226 if err != nil {
227 return nil, err
228 }
229
230 match := ev.Editor.(*Editor).matcher()
231 return &compl{begin, end, cookCandidates(cands, head, match, q)}, nil
232 }
233
234 func findFormHeadContext(n parse.Node) (int, int, string, parse.PrimaryType) {
235 // Determine if we are starting a new command. There are 3 cases:
236 // 1. The whole chunk is empty (nothing entered at all): the leaf is a
237 // Chunk.
238 // 2. Just after a newline or semicolon: the leaf is a Sep and its parent is
239 // a Chunk.
240 // 3. Just after a pipe: the leaf is a Sep and its parent is a Pipeline.
241 if parse.IsChunk(n) {
242 return n.End(), n.End(), "", parse.Bareword
243 }
244 if parse.IsSep(n) {
245 parent := n.Parent()
246 if parse.IsChunk(parent) || parse.IsPipeline(parent) {
247 return n.End(), n.End(), "", parse.Bareword
248 }
249 }
250
251 if primary, ok := n.(*parse.Primary); ok {
252 if compound, head := primaryInSimpleCompound(primary); compound != nil {
253 if form, ok := compound.Parent().(*parse.Form); ok {
254 if form.Head == compound {
255 return compound.Begin(), compound.End(), head, primary.Type
256 }
257 }
258 }
259 }
260 return -1, -1, "", 0
261 }
262
263 func complFormHeadInner(head string, ev *eval.Evaler) ([]rawCandidate, error) {
264 if util.DontSearch(head) {
265 return complFilenameInner(head, true)
266 }
267
268 var commands []rawCandidate
269 got := func(s string) {
270 commands = append(commands, plainCandidate(s))
271 }
272 for special := range eval.IsBuiltinSpecial {
273 got(special)
274 }
275 explode, ns, _ := eval.ParseVariable(head)
276 if !explode {
277 iterateVariables(ev, ns, func(varname string) {
278 if strings.HasPrefix(varname, eval.FnPrefix) {
279 got(eval.MakeVariableName(false, ns, varname[len(eval.FnPrefix):]))
280 } else {
281 got(eval.MakeVariableName(false, ns, varname) + "=")
282 }
283 })
284 }
285 ev.EachExternal(func(command string) {
286 got(command)
287 if strings.HasPrefix(head, "e:") {
288 got("e:" + command)
289 }
290 })
291 // TODO Support non-module namespaces.
292 for ns := range ev.Modules {
293 if head != ns+":" {
294 got(ns + ":")
295 }
296 }
297 sort.Sort(plainCandidates(commands))
298
299 return commands, nil
300 }
301
302 type plainCandidates []rawCandidate
303
304 func (pc plainCandidates) Len() int { return len(pc) }
305 func (pc plainCandidates) Less(i, j int) bool {
306 return pc[i].(plainCandidate) < pc[j].(plainCandidate)
307 }
308 func (pc plainCandidates) Swap(i, j int) { pc[i], pc[j] = pc[j], pc[i] }
309
310 // complRedir completes redirection RHS.
311 func complRedir(n parse.Node, ev *eval.Evaler) (*compl, error) {
312 begin, end, current, q := findRedirContext(n)
313 if begin == -1 {
314 return nil, errCompletionUnapplicable
315 }
316 cands, err := complFilenameInner(current, false)
317 if err != nil {
318 return nil, err
319 }
320 match := ev.Editor.(*Editor).matcher()
321 return &compl{begin, end, cookCandidates(cands, current, match, q)}, nil
322 }
323
324 func findRedirContext(n parse.Node) (int, int, string, parse.PrimaryType) {
325 if parse.IsSep(n) {
326 if parse.IsRedir(n.Parent()) {
327 return n.End(), n.End(), "", parse.Bareword
328 }
329 }
330 if primary, ok := n.(*parse.Primary); ok {
331 if compound, head := primaryInSimpleCompound(primary); compound != nil {
332 if parse.IsRedir(compound.Parent()) {
333 return compound.Begin(), compound.End(), head, primary.Type
334 }
335 }
336 }
337 return -1, -1, "", 0
338 }
339
340 // complArg completes arguments. It identifies the context and then delegates
341 // the actual completion work to a suitable completer.
342 func complArg(n parse.Node, ev *eval.Evaler) (*compl, error) {
343 begin, end, current, q, form := findArgContext(n)
344 if begin == -1 {
345 return nil, errCompletionUnapplicable
346 }
347
348 // Find out head of the form and preceding arguments.
349 // If Form.Head is not a simple compound, head will be "", just what we want.
350 _, head, _ := simpleCompound(form.Head, nil)
351 var args []string
352 for _, compound := range form.Args {
353 if compound.Begin() >= begin {
354 break
355 }
356 ok, arg, _ := simpleCompound(compound, nil)
357 if ok {
358 // XXX Arguments that are not simple compounds are simply ignored.
359 args = append(args, arg)
360 }
361 }
362
363 words := make([]string, len(args)+2)
364 words[0] = head
365 words[len(words)-1] = current
366 copy(words[1:len(words)-1], args[:])
367
368 cands, err := completeArg(words, ev)
369 if err != nil {
370 return nil, err
371 }
372 match := ev.Editor.(*Editor).matcher()
373 return &compl{begin, end, cookCandidates(cands, current, match, q)}, nil
374 }
375
376 func findArgContext(n parse.Node) (int, int, string, parse.PrimaryType, *parse.Form) {
377 if sep, ok := n.(*parse.Sep); ok {
378 if form, ok := sep.Parent().(*parse.Form); ok {
379 return n.End(), n.End(), "", parse.Bareword, form
380 }
381 }
382 if primary, ok := n.(*parse.Primary); ok {
383 if compound, head := primaryInSimpleCompound(primary); compound != nil {
384 if form, ok := compound.Parent().(*parse.Form); ok {
385 if form.Head != compound {
386 return compound.Begin(), compound.End(), head, primary.Type, form
387 }
388 }
389 }
390 }
391 return -1, -1, "", 0, nil
392 }
393
394 // TODO: getStyle does redundant stats.
395 func complFilenameInner(head string, executableOnly bool) (
396 []rawCandidate, error) {
397
398 dir, fileprefix := path.Split(head)
399 dirToRead := dir
400 if dirToRead == "" {
401 dirToRead = "."
402 }
403
404 infos, err := ioutil.ReadDir(dirToRead)
405 if err != nil {
406 return nil, fmt.Errorf("cannot list directory %s: %v", dirToRead, err)
407 }
408
409 cands := []rawCandidate{}
410 lsColor := getLsColor()
411 // Make candidates out of elements that match the file component.
412 for _, info := range infos {
413 name := info.Name()
414 // Show dot files iff file part of pattern starts with dot, and vice
415 // versa.
416 if dotfile(fileprefix) != dotfile(name) {
417 continue
418 }
419 // Only accept searchable directories and executable files if
420 // executableOnly is true.
421 if executableOnly && !(info.IsDir() || (info.Mode()&0111) != 0) {
422 continue
423 }
424
425 // Full filename for source and getStyle.
426 full := dir + name
427
428 suffix := " "
429 if info.IsDir() {
430 suffix = "/"
431 } else if info.Mode()&os.ModeSymlink != 0 {
432 stat, err := os.Stat(full)
433 if err == nil && stat.IsDir() {
434 // Symlink to directory.
435 suffix = "/"
436 }
437 }
438
439 cands = append(cands, &complexCandidate{
440 stem: full, codeSuffix: suffix,
441 style: ui.StylesFromString(lsColor.getStyle(full)),
442 })
443 }
444
445 return cands, nil
446 }
447
448 func dotfile(fname string) bool {
449 return strings.HasPrefix(fname, ".")
450 }
0 package edit
1
2 import (
3 "os"
4 "reflect"
5 "testing"
6
7 "github.com/elves/elvish/edit/ui"
8 "github.com/elves/elvish/eval"
9 "github.com/elves/elvish/util"
10 )
11
12 func TestComplIndexInner(t *testing.T) {
13 m := eval.NewMap(map[eval.Value]eval.Value{
14 eval.String("foo"): eval.String("bar"),
15 eval.String("lorem"): eval.String("ipsum"),
16 })
17 wantCandidates := []rawCandidate{
18 plainCandidate("foo"), plainCandidate("lorem"),
19 }
20 candidates := complIndexInner(m)
21 if !reflect.DeepEqual(candidates, wantCandidates) {
22 t.Errorf("complIndexInner(%v) = %v, want %v",
23 m, candidates, wantCandidates)
24 }
25 }
26
27 var (
28 fileStyle = ui.StylesFromString("1")
29 exeStyle = ui.StylesFromString("2")
30 dirStyle = ui.StylesFromString("4")
31 )
32
33 var complFilenameInnerTests = []struct {
34 head string
35 executableOnly bool
36 wantCandidates []rawCandidate
37 }{
38 // Match all non-hidden files and dirs, in alphabetical order.
39 // Files have suffix " " and directories "/". Styles are set according to
40 // the LS_COLORS variable, which are set in the beginning of the test.
41 {"haha", false, []rawCandidate{
42 &complexCandidate{stem: "Documents", codeSuffix: "/", style: dirStyle},
43 &complexCandidate{stem: "bar", codeSuffix: " ", style: fileStyle},
44 &complexCandidate{stem: "elvish", codeSuffix: " ", style: exeStyle},
45 &complexCandidate{stem: "foo", codeSuffix: " ", style: fileStyle},
46 }},
47 // Only match executables and directories.
48 {"haha", true, []rawCandidate{
49 &complexCandidate{stem: "Documents", codeSuffix: "/", style: dirStyle},
50 &complexCandidate{stem: "elvish", codeSuffix: " ", style: exeStyle},
51 }},
52 // Match hidden files and directories.
53 {".haha", false, []rawCandidate{
54 &complexCandidate{stem: ".elvish", codeSuffix: "/", style: dirStyle},
55 &complexCandidate{stem: ".vimrc", codeSuffix: " ", style: fileStyle},
56 }},
57 }
58
59 func TestComplFilenameInner(t *testing.T) {
60 os.Setenv("LS_COLORS", "rs=1:ex=2:di=4")
61 util.InTempDir(func(string) {
62 create("foo", 0600)
63 create(".vimrc", 0600)
64 create("bar", 0600)
65
66 create("elvish", 0700)
67
68 mkdir("Documents", 0700)
69 mkdir(".elvish", 0700)
70
71 for _, test := range complFilenameInnerTests {
72 cands, err := complFilenameInner(test.head, test.executableOnly)
73 if err != nil {
74 t.Errorf("complFilenameInner(%v, %v) returns error %v, want nil",
75 test.head, test.executableOnly, err)
76 }
77 if !reflect.DeepEqual(cands, test.wantCandidates) {
78 t.Errorf("complFilenameInner(%v, %v) returns %v, want %v",
79 test.head, test.executableOnly, cands, test.wantCandidates)
80 t.Log("returned candidates are:")
81 for _, cand := range cands {
82 t.Logf("%#v", cand)
83 }
84 }
85 }
86 })
87 }
88
89 func mkdir(dirname string, perm os.FileMode) {
90 err := os.Mkdir(dirname, perm)
91 if err != nil {
92 panic(err)
93 }
94 }
0 package edit
1
2 import (
3 "fmt"
4 "strings"
5 "unicode/utf8"
6
7 "github.com/elves/elvish/edit/ui"
8 "github.com/elves/elvish/eval"
9 "github.com/elves/elvish/util"
10 )
11
12 // Completion subsystem.
13
14 // Interface.
15
16 var _ = registerBuiltins("compl", map[string]func(*Editor){
17 "smart-start": complSmartStart,
18 "start": complStart,
19 "up": complUp,
20 "up-cycle": complUpCycle,
21 "down": complDown,
22 "down-cycle": complDownCycle,
23 "left": complLeft,
24 "right": complRight,
25 "accept": complAccept,
26 "trigger-filter": complTriggerFilter,
27 "default": complDefault,
28 })
29
30 func init() {
31 registerBindings(modeCompletion, "compl", map[ui.Key]string{
32 {ui.Up, 0}: "up",
33 {ui.Down, 0}: "down",
34 {ui.Tab, 0}: "down-cycle",
35 {ui.Tab, ui.Shift}: "up-cycle",
36 {ui.Left, 0}: "left",
37 {ui.Right, 0}: "right",
38 {ui.Enter, 0}: "accept",
39 {'F', ui.Ctrl}: "trigger-filter",
40 {'[', ui.Ctrl}: "insert:start",
41 ui.Default: "default",
42 })
43 }
44
45 type completion struct {
46 compl
47 completer string
48
49 filtering bool
50 filter string
51 filtered []*candidate
52 selected int
53 firstShown int
54 lastShownInFull int
55 height int
56 }
57
58 func (*completion) Binding(k ui.Key) eval.CallableValue {
59 return getBinding(modeCompletion, k)
60 }
61
62 func (c *completion) needScrollbar() bool {
63 return c.firstShown > 0 || c.lastShownInFull < len(c.filtered)-1
64 }
65
66 func (c *completion) ModeLine() renderer {
67 ml := modeLineRenderer{fmt.Sprintf(" COMPLETING %s ", c.completer), c.filter}
68 if !c.needScrollbar() {
69 return ml
70 }
71 return modeLineWithScrollBarRenderer{ml,
72 len(c.filtered), c.firstShown, c.lastShownInFull + 1}
73 }
74
75 func (c *completion) CursorOnModeLine() bool {
76 return c.filtering
77 }
78
79 func complStart(ed *Editor) {
80 startCompletionInner(ed, false)
81 }
82
83 func complSmartStart(ed *Editor) {
84 startCompletionInner(ed, true)
85 }
86
87 func complUp(ed *Editor) {
88 ed.completion.prev(false)
89 }
90
91 func complDown(ed *Editor) {
92 ed.completion.next(false)
93 }
94
95 func complLeft(ed *Editor) {
96 if c := ed.completion.selected - ed.completion.height; c >= 0 {
97 ed.completion.selected = c
98 }
99 }
100
101 func complRight(ed *Editor) {
102 if c := ed.completion.selected + ed.completion.height; c < len(ed.completion.filtered) {
103 ed.completion.selected = c
104 }
105 }
106
107 func complUpCycle(ed *Editor) {
108 ed.completion.prev(true)
109 }
110
111 func complDownCycle(ed *Editor) {
112 ed.completion.next(true)
113 }
114
115 // acceptCompletion accepts currently selected completion candidate.
116 func complAccept(ed *Editor) {
117 c := ed.completion
118 if 0 <= c.selected && c.selected < len(c.filtered) {
119 ed.line, ed.dot = c.apply(ed.line, ed.dot)
120 }
121 ed.mode = &ed.insert
122 }
123
124 func complDefault(ed *Editor) {
125 k := ed.lastKey
126 c := &ed.completion
127 if c.filtering && likeChar(k) {
128 c.changeFilter(c.filter + string(k.Rune))
129 } else if c.filtering && k == (ui.Key{ui.Backspace, 0}) {
130 _, size := utf8.DecodeLastRuneInString(c.filter)
131 if size > 0 {
132 c.changeFilter(c.filter[:len(c.filter)-size])
133 }
134 } else {
135 complAccept(ed)
136 ed.nextAction = action{typ: reprocessKey}
137 }
138 }
139
140 func complTriggerFilter(ed *Editor) {
141 c := &ed.completion
142 if c.filtering {
143 c.filtering = false
144 c.changeFilter("")
145 } else {
146 c.filtering = true
147 }
148 }
149
150 func (c *completion) selectedCandidate() *candidate {
151 if c.selected == -1 {
152 return &candidate{}
153 }
154 return c.filtered[c.selected]
155 }
156
157 // apply returns the line and dot after applying a candidate.
158 func (c *completion) apply(line string, dot int) (string, int) {
159 text := c.selectedCandidate().code
160 return line[:c.begin] + text + line[c.end:], c.begin + len(text)
161 }
162
163 func (c *completion) prev(cycle bool) {
164 c.selected--
165 if c.selected == -1 {
166 if cycle {
167 c.selected = len(c.filtered) - 1
168 } else {
169 c.selected++
170 }
171 }
172 }
173
174 func (c *completion) next(cycle bool) {
175 c.selected++
176 if c.selected == len(c.filtered) {
177 if cycle {
178 c.selected = 0
179 } else {
180 c.selected--
181 }
182 }
183 }
184
185 func startCompletionInner(ed *Editor, acceptPrefix bool) {
186 node := findLeafNode(ed.chunk, ed.dot)
187 if node == nil {
188 return
189 }
190
191 completer, compl, err := complete(node, ed.evaler)
192
193 if err != nil {
194 ed.addTip("%v", err)
195 } else if completer == "" {
196 ed.addTip("unsupported completion :(")
197 logger.Println("path to current leaf, leaf first")
198 for n := node; n != nil; n = n.Parent() {
199 logger.Printf("%T (%d-%d)", n, n.Begin(), n.End())
200 }
201 } else if len(compl.candidates) == 0 {
202 ed.addTip("no candidate for %s", completer)
203 } else {
204 if acceptPrefix {
205 // If there is a non-empty longest common prefix, insert it and
206 // don't start completion mode.
207 //
208 // As a special case, when there is exactly one candidate, it is
209 // immeidately accepted.
210 prefix := compl.candidates[0].code
211 for _, cand := range compl.candidates[1:] {
212 prefix = commonPrefix(prefix, cand.code)
213 if prefix == "" {
214 break
215 }
216 }
217 if prefix != "" && prefix != ed.line[compl.begin:compl.end] {
218 ed.line = ed.line[:compl.begin] + prefix + ed.line[compl.end:]
219 ed.dot = compl.begin + len(prefix)
220 return
221 }
222 }
223 ed.completion = completion{
224 completer: completer,
225 compl: *compl,
226 filtered: compl.candidates,
227 }
228 ed.mode = &ed.completion
229 }
230 }
231
232 // commonPrefix returns the longest common prefix of two strings.
233 func commonPrefix(s, t string) string {
234 for i, r := range s {
235 if i >= len(t) {
236 return s[:i]
237 }
238 r2, _ := utf8.DecodeRuneInString(t[i:])
239 if r2 != r {
240 return s[:i]
241 }
242 }
243 return s
244 }
245
246 const (
247 completionColMarginLeft = 1
248 completionColMarginRight = 1
249 completionColMarginTotal = completionColMarginLeft + completionColMarginRight
250 )
251
252 // maxWidth finds the maximum wcwidth of display texts of candidates [lo, hi).
253 // hi may be larger than the number of candidates, in which case it is truncated
254 // to the number of candidates.
255 func (c *completion) maxWidth(lo, hi int) int {
256 if hi > len(c.filtered) {
257 hi = len(c.filtered)
258 }
259 width := 0
260 for i := lo; i < hi; i++ {
261 w := util.Wcswidth(c.filtered[i].menu.Text)
262 if width < w {
263 width = w
264 }
265 }
266 return width
267 }
268
269 func (c *completion) ListRender(width, maxHeight int) *buffer {
270 b := newBuffer(width)
271 cands := c.filtered
272 if len(cands) == 0 {
273 b.writes(util.TrimWcwidth("(no result)", width), "")
274 return b
275 }
276 if maxHeight <= 1 || width <= 2 {
277 b.writes(util.TrimWcwidth("(terminal too small)", width), "")
278 return b
279 }
280
281 // Reserve the the rightmost row as margins.
282 width--
283
284 // Determine comp.height and comp.firstShown.
285 // First determine whether all candidates can be fit in the screen,
286 // assuming that they are all of maximum width. If that is the case, we use
287 // the computed height as the height for the listing, and the first
288 // candidate to show is 0. Otherwise, we use min(height, len(cands)) as the
289 // height and find the first candidate to show.
290 perLine := max(1, width/(c.maxWidth(0, len(cands))+completionColMarginTotal))
291 heightBound := util.CeilDiv(len(cands), perLine)
292 first := 0
293 height := 0
294 if heightBound < maxHeight {
295 height = heightBound
296 } else {
297 height = min(maxHeight, len(cands))
298 // Determine the first column to show. We start with the column in which the
299 // selected one is found, moving to the left until either the width is
300 // exhausted, or the old value of firstShown has been hit.
301 first = c.selected / height * height
302 w := c.maxWidth(first, first+height) + completionColMarginTotal
303 for ; first > c.firstShown; first -= height {
304 dw := c.maxWidth(first-height, first) + completionColMarginTotal
305 if w+dw > width {
306 break
307 }
308 w += dw
309 }
310 }
311 c.height = height
312 c.firstShown = first
313
314 var i, j int
315 remainedWidth := width
316 trimmed := false
317 // Show the results in columns, until width is exceeded.
318 for i = first; i < len(cands); i += height {
319 // Determine the width of the column (without the margin)
320 colWidth := c.maxWidth(i, min(i+height, len(cands)))
321 totalColWidth := colWidth + completionColMarginTotal
322 if totalColWidth > remainedWidth {
323 totalColWidth = remainedWidth
324 colWidth = totalColWidth - completionColMarginTotal
325 trimmed = true
326 }
327
328 col := newBuffer(totalColWidth)
329 for j = i; j < i+height; j++ {
330 if j > i {
331 col.newline()
332 }
333 if j >= len(cands) {
334 // Write padding to make the listing a rectangle.
335 col.writePadding(totalColWidth, styleForCompletion.String())
336 } else {
337 col.writePadding(completionColMarginLeft, styleForCompletion.String())
338 s := ui.JoinStyles(styleForCompletion, cands[j].menu.Styles)
339 if j == c.selected {
340 s = append(s, styleForSelectedCompletion.String())
341 }
342 col.writes(util.ForceWcwidth(cands[j].menu.Text, colWidth), s.String())
343 col.writePadding(completionColMarginRight, styleForCompletion.String())
344 if !trimmed {
345 c.lastShownInFull = j
346 }
347 }
348 }
349
350 b.extendRight(col, 0)
351 remainedWidth -= totalColWidth
352 if remainedWidth <= completionColMarginTotal {
353 break
354 }
355 }
356 // When the listing is incomplete, always use up the entire width.
357 if remainedWidth > 0 && c.needScrollbar() {
358 col := newBuffer(remainedWidth)
359 for i := 0; i < height; i++ {
360 if i > 0 {
361 col.newline()
362 }
363 col.writePadding(remainedWidth, styleForCompletion.String())
364 }
365 b.extendRight(col, 0)
366 remainedWidth = 0
367 }
368 return b
369 }
370
371 func (c *completion) changeFilter(f string) {
372 c.filter = f
373 if f == "" {
374 c.filtered = c.candidates
375 return
376 }
377 c.filtered = nil
378 for _, cand := range c.candidates {
379 if strings.Contains(cand.menu.Text, f) {
380 c.filtered = append(c.filtered, cand)
381 }
382 }
383 if len(c.filtered) > 0 {
384 c.selected = 0
385 } else {
386 c.selected = -1
387 }
388 }
0 package edit
1
2 func distributeWidths(w int, weights []float64, actual []int) []int {
3 n := len(weights)
4 widths := make([]int, n)
5 done := make([]bool, n)
6
7 for {
8 wsum := 0.0
9 for i := 0; i < n; i++ {
10 if done[i] {
11 continue
12 }
13 wsum += weights[i]
14 }
15 // Widths allocated away
16 allocated := 0
17 for i := 0; i < n; i++ {
18 if done[i] {
19 continue
20 }
21 allowed := int(float64(w) * weights[i] / wsum)
22 if actual[i] <= allowed {
23 // Actual width fit in allowed width; allocate
24 widths[i] = actual[i]
25 allocated += actual[i]
26 done[i] = true
27 }
28 }
29 if allocated == 0 {
30 // Use allowed width for all remaining columns
31 for i := 0; i < n; i++ {
32 if done[i] {
33 continue
34 }
35 allowed := int(float64(w) * weights[i] / wsum)
36 widths[i] = allowed
37 w -= allowed
38 done[i] = true
39 }
40 break
41 }
42 }
43 logger.Printf("distribute(%d, %v, %v) -> %v", w, weights, actual, widths)
44 return widths
45 }
0 package edit
1
2 import (
3 "fmt"
4 "html"
5 "strings"
6
7 "github.com/elves/elvish/eval"
8 )
9
10 func _dumpBuf(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
11 out := ec.OutputFile()
12 buf := ec.Editor.(*Editor).writer.oldBuf
13 for _, line := range buf.lines {
14 style := ""
15 openedSpan := false
16 for _, c := range line {
17 if c.style != style {
18 if openedSpan {
19 fmt.Fprint(out, "</span>")
20 }
21 var classes []string
22 for _, c := range strings.Split(c.style, ";") {
23 classes = append(classes, "sgr-"+c)
24 }
25 fmt.Fprintf(out,
26 `<span class="%s">`, strings.Join(classes, " "))
27 style = c.style
28 openedSpan = true
29 }
30 fmt.Fprintf(out, "%s", html.EscapeString(c.string))
31 }
32 if openedSpan {
33 fmt.Fprint(out, "</span>")
34 }
35 fmt.Fprint(out, "\n")
36 }
37 }
0 // Package edit implements a command line editor.
1 package edit
2
3 import (
4 "bytes"
5 "fmt"
6 "os"
7 "sync"
8 "syscall"
9 "time"
10
11 "github.com/elves/elvish/daemon/api"
12 "github.com/elves/elvish/edit/highlight"
13 "github.com/elves/elvish/edit/history"
14 "github.com/elves/elvish/edit/tty"
15 "github.com/elves/elvish/edit/ui"
16 "github.com/elves/elvish/eval"
17 "github.com/elves/elvish/parse"
18 "github.com/elves/elvish/sys"
19 "github.com/elves/elvish/util"
20 )
21
22 var logger = util.GetLogger("[edit] ")
23
24 const (
25 lackEOLRune = '\u23ce'
26 lackEOL = "\033[7m" + string(lackEOLRune) + "\033[m"
27 )
28
29 // Editor keeps the status of the line editor.
30 type Editor struct {
31 in *os.File
32 out *os.File
33 writer *Writer
34 reader *tty.Reader
35 sigs chan os.Signal
36 daemon *api.Client
37 evaler *eval.Evaler
38
39 variables map[string]eval.Variable
40
41 active bool
42 activeMutex sync.Mutex
43
44 historyFuser *history.Fuser
45 historyMutex sync.RWMutex
46
47 editorState
48 }
49
50 type editorState struct {
51 // States used during ReadLine. Reset at the beginning of ReadLine.
52 savedTermios *sys.Termios
53
54 notificationMutex sync.Mutex
55
56 notifications []string
57 tips []string
58
59 line string
60 lexedLine *string
61 chunk *parse.Chunk
62 styling *highlight.Styling
63 promptContent []*ui.Styled
64 rpromptContent []*ui.Styled
65 dot int
66
67 mode Mode
68
69 insert insert
70 command command
71 completion completion
72 navigation navigation
73 hist hist
74
75 // A cache of external commands, used in stylist.
76 isExternal map[string]bool
77 parseErrorAtEnd bool
78
79 // Used for builtins.
80 lastKey ui.Key
81 nextAction action
82 }
83
84 // NewEditor creates an Editor.
85 func NewEditor(in *os.File, out *os.File, sigs chan os.Signal, ev *eval.Evaler, daemon *api.Client) *Editor {
86 ed := &Editor{
87 in: in,
88 out: out,
89 writer: newWriter(out),
90 reader: tty.NewReader(in),
91 sigs: sigs,
92 daemon: daemon,
93 evaler: ev,
94
95 variables: makeVariables(),
96 }
97 if daemon != nil {
98 f, err := history.NewFuser(daemon)
99 if err != nil {
100 fmt.Fprintln(os.Stderr, "Failed to initialize command history. Disabled.")
101 } else {
102 ed.historyFuser = f
103 }
104 }
105 ev.Editor = ed
106
107 installModules(ev.Modules, ed)
108
109 return ed
110 }
111
112 // Active returns the activeness of the Editor.
113 func (ed *Editor) Active() bool {
114 return ed.active
115 }
116
117 // ActiveMutex returns a mutex that must be used when changing the activeness of
118 // the Editor.
119 func (ed *Editor) ActiveMutex() *sync.Mutex {
120 return &ed.activeMutex
121 }
122
123 func (ed *Editor) flash() {
124 // TODO implement fish-like flash effect
125 }
126
127 func (ed *Editor) addTip(format string, args ...interface{}) {
128 ed.tips = append(ed.tips, fmt.Sprintf(format, args...))
129 }
130
131 // Notify adds one notification entry. It is concurrency-safe.
132 func (ed *Editor) Notify(format string, args ...interface{}) {
133 ed.notificationMutex.Lock()
134 defer ed.notificationMutex.Unlock()
135 ed.notifications = append(ed.notifications, fmt.Sprintf(format, args...))
136 }
137
138 func (ed *Editor) refresh(fullRefresh bool, addErrorsToTips bool) error {
139 src := ed.line
140 // Re-lex the line if needed
141 if ed.lexedLine == nil || *ed.lexedLine != src {
142 ed.lexedLine = &src
143 n, err := parse.Parse("[interactive]", src)
144 ed.chunk = n
145
146 ed.parseErrorAtEnd = err != nil && atEnd(err, len(src))
147 // If all parse errors are at the end, it is likely caused by incomplete
148 // input. In that case, do not complain about parse errors.
149 // TODO(xiaq): Find a more reliable way to determine incomplete input.
150 // Ideally the parser should report it.
151 if err != nil && addErrorsToTips && !ed.parseErrorAtEnd {
152 ed.addTip("%s", err)
153 }
154
155 ed.styling = &highlight.Styling{}
156 doHighlight(n, ed)
157
158 _, err = ed.evaler.Compile(n, "[interactive]", src)
159 if err != nil && !atEnd(err, len(src)) {
160 if addErrorsToTips {
161 ed.addTip("%s", err)
162 }
163 // Highlight errors in the input buffer.
164 // TODO(xiaq): There might be multiple tokens involved in the
165 // compiler error; they should all be highlighted as erroneous.
166 p := err.(*eval.CompilationError).Context.Begin
167 badn := findLeafNode(n, p)
168 ed.styling.Add(badn.Begin(), badn.End(), styleForCompilerError.String())
169 }
170 }
171 return ed.writer.refresh(&ed.editorState, fullRefresh)
172 }
173
174 func atEnd(e error, n int) bool {
175 switch e := e.(type) {
176 case *eval.CompilationError:
177 return e.Context.Begin == n
178 case *parse.Error:
179 for _, entry := range e.Entries {
180 if entry.Context.Begin != n {
181 return false
182 }
183 }
184 return true
185 default:
186 logger.Printf("atEnd called with error type %T", e)
187 return false
188 }
189 }
190
191 // insertAtDot inserts text at the dot and moves the dot after it.
192 func (ed *Editor) insertAtDot(text string) {
193 ed.line = ed.line[:ed.dot] + text + ed.line[ed.dot:]
194 ed.dot += len(text)
195 }
196
197 const flushInputDuringSetup = false
198
199 func setupTerminal(file *os.File) (*sys.Termios, error) {
200 fd := int(file.Fd())
201 term, err := sys.NewTermiosFromFd(fd)
202 if err != nil {
203 return nil, fmt.Errorf("can't get terminal attribute: %s", err)
204 }
205
206 savedTermios := term.Copy()
207
208 term.SetICanon(false)
209 term.SetEcho(false)
210 term.SetVMin(1)
211 term.SetVTime(0)
212
213 err = term.ApplyToFd(fd)
214 if err != nil {
215 return nil, fmt.Errorf("can't set up terminal attribute: %s", err)
216 }
217
218 if flushInputDuringSetup {
219 err = sys.FlushInput(fd)
220 if err != nil {
221 return nil, fmt.Errorf("can't flush input: %s", err)
222 }
223 }
224
225 return savedTermios, nil
226 }
227
228 // startReadLine prepares the terminal for the editor.
229 func (ed *Editor) startReadLine() error {
230 ed.activeMutex.Lock()
231 defer ed.activeMutex.Unlock()
232 ed.active = true
233
234 savedTermios, err := setupTerminal(ed.in)
235 if err != nil {
236 return err
237 }
238 ed.savedTermios = savedTermios
239
240 _, width := sys.GetWinsize(int(ed.in.Fd()))
241 /*
242 Write a lackEOLRune if the cursor is not in the leftmost column. This is
243 done as follows:
244
245 1. Turn on autowrap;
246
247 2. Write lackEOL along with enough padding, so that the total width is
248 equal to the width of the screen.
249
250 If the cursor was in the first column, we are still in the same line,
251 just off the line boundary. Otherwise, we are now in the next line.
252
253 3. Rewind to the first column, write one space and rewind again. If the
254 cursor was in the first column to start with, we have just erased the
255 LackEOL character. Otherwise, we are now in the next line and this is
256 a no-op. The LackEOL character remains.
257 */
258 fmt.Fprintf(ed.out, "\033[?7h%s%*s\r \r", lackEOL, width-util.Wcwidth(lackEOLRune), "")
259
260 /*
261 Turn off autowrap.
262
263 The terminals sometimes has different opinions about how wide some
264 characters are (notably emojis and some dingbats) with elvish. When that
265 happens, elvish becomes wrong about where the cursor is when it writes
266 its output, and the effect can be disastrous.
267
268 If we turn off autowrap, the terminal won't insert any newlines behind
269 the scene, so elvish is always right about which line the cursor is.
270 With a bit more caution, this can restrict the consequence of the
271 mismatch within one line.
272 */
273 ed.out.WriteString("\033[?7l")
274 // Turn on SGR-style mouse tracking.
275 //ed.out.WriteString("\033[?1000;1006h")
276
277 // Enable bracketed paste.
278 ed.out.WriteString("\033[?2004h")
279
280 return nil
281 }
282
283 // finishReadLine puts the terminal in a state suitable for other programs to
284 // use.
285 func (ed *Editor) finishReadLine(addError func(error)) {
286 ed.activeMutex.Lock()
287 defer ed.activeMutex.Unlock()
288 ed.active = false
289
290 // Refresh the terminal for the last time in a clean-ish state.
291 ed.mode = &ed.insert
292 ed.tips = nil
293 ed.dot = len(ed.line)
294 if !ed.rpromptPersistent() {
295 ed.rpromptContent = nil
296 }
297 addError(ed.refresh(false, false))
298 ed.out.WriteString("\n")
299 ed.writer.resetOldBuf()
300
301 ed.reader.Quit()
302
303 // Turn on autowrap.
304 ed.out.WriteString("\033[?7h")
305 // Turn off mouse tracking.
306 //ed.out.WriteString("\033[?1000;1006l")
307
308 // Disable bracketed paste.
309 ed.out.WriteString("\033[?2004l")
310
311 // Restore termios.
312 err := ed.savedTermios.ApplyToFd(int(ed.in.Fd()))
313 if err != nil {
314 addError(fmt.Errorf("can't restore terminal attribute: %s", err))
315 }
316
317 // Save the line before resetting all of editorState.
318 line := ed.line
319
320 ed.editorState = editorState{}
321
322 callHooks(ed.evaler, ed.afterReadLine(), eval.String(line))
323 }
324
325 // ReadLine reads a line interactively.
326 func (ed *Editor) ReadLine() (line string, err error) {
327 e := ed.startReadLine()
328 if e != nil {
329 return "", e
330 }
331 defer ed.finishReadLine(func(e error) {
332 if e != nil {
333 err = util.CatError(err, e)
334 }
335 })
336
337 ed.mode = &ed.insert
338
339 // Find external commands asynchronously, so that slow I/O won't block the
340 // editor.
341 isExternalCh := make(chan map[string]bool, 1)
342 go getIsExternal(ed.evaler, isExternalCh)
343
344 go ed.reader.Run()
345
346 fullRefresh := false
347
348 callHooks(ed.evaler, ed.beforeReadLine())
349
350 MainLoop:
351 for {
352 ed.promptContent = callPrompt(ed, ed.prompt())
353 ed.rpromptContent = callPrompt(ed, ed.rprompt())
354
355 err := ed.refresh(fullRefresh, true)
356 fullRefresh = false
357 if err != nil {
358 return "", err
359 }
360
361 ed.tips = nil
362
363 select {
364 case m := <-isExternalCh:
365 ed.isExternal = m
366 case sig := <-ed.sigs:
367 // TODO(xiaq): Maybe support customizable handling of signals
368 switch sig {
369 case syscall.SIGINT:
370 // Start over
371 ed.editorState = editorState{
372 savedTermios: ed.savedTermios,
373 isExternal: ed.isExternal,
374 }
375 ed.mode = &ed.insert
376 continue MainLoop
377 case syscall.SIGWINCH:
378 fullRefresh = true
379 continue MainLoop
380 case syscall.SIGCHLD:
381 // ignore
382 default:
383 ed.addTip("ignored signal %s", sig)
384 }
385 case err := <-ed.reader.ErrorChan():
386 ed.Notify("reader error: %s", err.Error())
387 case unit := <-ed.reader.UnitChan():
388 switch unit := unit.(type) {
389 case tty.MouseEvent:
390 ed.addTip("mouse: %+v", unit)
391 case tty.CursorPosition:
392 // Ignore CPR
393 case tty.PasteSetting:
394 if !unit {
395 continue
396 }
397 var buf bytes.Buffer
398 timer := time.NewTimer(tty.EscSequenceTimeout)
399 paste:
400 for {
401 // XXX Should also select on other chans. However those chans
402 // will be unified (again) into one later so we don't do
403 // busywork here.
404 select {
405 case unit := <-ed.reader.UnitChan():
406 switch unit := unit.(type) {
407 case tty.Key:
408 k := ui.Key(unit)
409 if k.Mod != 0 {
410 ed.Notify("function key within paste, aborting")
411 break paste
412 }
413 buf.WriteRune(k.Rune)
414 timer.Reset(tty.EscSequenceTimeout)
415 case tty.PasteSetting:
416 if !unit {
417 break paste
418 }
419 default: // Ignore other things.
420 }
421 case <-timer.C:
422 ed.Notify("bracketed paste timeout")
423 break paste
424 }
425 }
426 topaste := buf.String()
427 if ed.insert.quotePaste {
428 topaste = parse.Quote(topaste)
429 }
430 ed.insertAtDot(topaste)
431 case tty.RawRune:
432 insertRaw(ed, rune(unit))
433 case tty.Key:
434 k := ui.Key(unit)
435 lookupKey:
436 fn := ed.mode.Binding(k)
437 if fn == nil {
438 ed.addTip("Unbound and no default binding: %s", k)
439 continue MainLoop
440 }
441
442 ed.insert.insertedLiteral = false
443 ed.lastKey = k
444 ed.CallFn(fn)
445 if ed.insert.insertedLiteral {
446 ed.insert.literalInserts++
447 } else {
448 ed.insert.literalInserts = 0
449 }
450 act := ed.nextAction
451 ed.nextAction = action{}
452
453 switch act.typ {
454 case noAction:
455 continue
456 case reprocessKey:
457 err := ed.refresh(false, true)
458 if err != nil {
459 return "", err
460 }
461 goto lookupKey
462 case exitReadLine:
463 if act.returnErr == nil && act.returnLine != "" {
464 ed.appendHistory(act.returnLine)
465 }
466 return act.returnLine, act.returnErr
467 }
468 }
469 }
470 }
471 }
472
473 // getIsExternal finds a set of all external commands and puts it on the result
474 // channel.
475 func getIsExternal(ev *eval.Evaler, result chan<- map[string]bool) {
476 isExternal := make(map[string]bool)
477 ev.EachExternal(func(name string) {
478 isExternal[name] = true
479 })
480 result <- isExternal
481 }
0 package edit
1
2 import (
3 "testing"
4
5 "github.com/kr/pty"
6 )
7
8 func TestSetupTerminal(t *testing.T) {
9 pty, tty, err := pty.Open()
10 if err != nil {
11 t.Errorf("cannot open pty for testing setupTerminal")
12 }
13 defer pty.Close()
14 defer tty.Close()
15
16 _, err = setupTerminal(tty)
17 if err != nil {
18 t.Errorf("setupTerminal returns an error")
19 }
20 // TODO(xiaq): Test whether the interesting flags in the termios were indeed
21 // set.
22 // termios, err := sys.NewTermiosFromFd(int(tty.Fd()))
23 }
0 package highlight
1
2 import (
3 "strings"
4
5 "github.com/elves/elvish/edit/nodeutil"
6 "github.com/elves/elvish/edit/ui"
7 "github.com/elves/elvish/parse"
8 )
9
10 type Emitter struct {
11 GoodFormHead func(string) bool
12 AddStyling func(begin, end int, style string)
13 }
14
15 func (e *Emitter) EmitAll(n parse.Node) {
16 switch n := n.(type) {
17 case *parse.Form:
18 e.form(n)
19 case *parse.Primary:
20 e.primary(n)
21 case *parse.Sep:
22 e.sep(n)
23 }
24 for _, child := range n.Children() {
25 e.EmitAll(child)
26 }
27 }
28
29 func (e *Emitter) form(n *parse.Form) {
30 for _, an := range n.Assignments {
31 if an.Left != nil && an.Left.Head != nil {
32 v := an.Left.Head
33 e.AddStyling(v.Begin(), v.End(), styleForGoodVariable.String())
34 }
35 }
36 for _, cn := range n.Vars {
37 if len(cn.Indexings) > 0 && cn.Indexings[0].Head != nil {
38 v := cn.Indexings[0].Head
39 e.AddStyling(v.Begin(), v.End(), styleForGoodVariable.String())
40 }
41 }
42 if n.Head != nil {
43 e.formHead(n.Head)
44 // Special forms
45 switch n.Head.SourceText() {
46 case "for":
47 if len(n.Args) >= 1 && len(n.Args[0].Indexings) > 0 {
48 v := n.Args[0].Indexings[0].Head
49 e.AddStyling(v.Begin(), v.End(), styleForGoodVariable.String())
50 }
51 if len(n.Args) >= 4 && n.Args[3].SourceText() == "else" {
52 a := n.Args[3]
53 e.AddStyling(a.Begin(), a.End(), styleForSep["else"])
54 }
55 case "try":
56 i := 1
57 highlightKeyword := func(name string) bool {
58 if i >= len(n.Args) {
59 return false
60 }
61 a := n.Args[i]
62 if a.SourceText() != name {
63 return false
64 }
65 e.AddStyling(a.Begin(), a.End(), styleForSep[name])
66 return true
67 }
68 if highlightKeyword("except") {
69 if i+1 < len(n.Args) && len(n.Args[i+1].Indexings) > 0 {
70 v := n.Args[i+1].Indexings[0]
71 e.AddStyling(v.Begin(), v.End(), styleForGoodVariable.String())
72 }
73 i += 3
74 }
75 if highlightKeyword("else") {
76 i += 2
77 }
78 highlightKeyword("finally")
79 }
80 // TODO(xiaq): Handle other special forms.
81 }
82 }
83
84 func (e *Emitter) formHead(n *parse.Compound) {
85 simple, head, err := nodeutil.SimpleCompound(n, nil)
86 st := ui.Styles{}
87 if simple {
88 if e.GoodFormHead(head) {
89 st = styleForGoodCommand
90 } else {
91 st = styleForBadCommand
92 }
93 } else if err != nil {
94 st = styleForBadCommand
95 }
96 if len(st) > 0 {
97 e.AddStyling(n.Begin(), n.End(), st.String())
98 }
99 }
100
101 func (e *Emitter) primary(n *parse.Primary) {
102 e.AddStyling(n.Begin(), n.End(), styleForPrimary[n.Type].String())
103 }
104
105 func (e *Emitter) sep(n *parse.Sep) {
106 septext := n.SourceText()
107 switch {
108 case strings.TrimSpace(septext) == "":
109 // Don't do anything. Whitespaces don't get any styling.
110 case strings.HasPrefix(septext, "#"):
111 // Comment.
112 e.AddStyling(n.Begin(), n.End(), styleForComment.String())
113 default:
114 e.AddStyling(n.Begin(), n.End(), styleForSep[septext])
115 }
116 }
0 package highlight
1
2 import (
3 "reflect"
4 "strings"
5 "testing"
6
7 "github.com/elves/elvish/parse"
8 )
9
10 type styling struct {
11 begin int
12 end int
13 style string
14 }
15
16 type emitTests struct {
17 source string
18 wantStylings []styling
19 }
20
21 // In the test cases, commands that start with x are bad, everything else is
22 // good.
23 func goodFormHead(head string) bool { return !strings.HasPrefix(head, "x") }
24
25 // This just tests the Highlight method itself, its dependencies are tested
26 // below.
27 var emitAllTests = []emitTests{
28 //01234
29 {"x 'y'", []styling{
30 {0, 1, styleForBadCommand.String()},
31 {0, 1, styleForPrimary[parse.Bareword].String()},
32 {2, 5, styleForPrimary[parse.SingleQuoted].String()},
33 }},
34 }
35
36 func TestEmitAll(t *testing.T) {
37 test(t, "form", emitAllTests,
38 func(e *Emitter, ps *parse.Parser) {
39 e.EmitAll(parse.ParseChunk(ps))
40 })
41 }
42
43 var formTests = []emitTests{
44 // Temporary assignments.
45 {"a=1 b=2", []styling{
46 {0, 1, styleForGoodVariable.String()},
47 {4, 5, styleForGoodVariable.String()}}},
48 // Normal assignments,
49 {"a b = 1 2", []styling{
50 {0, 1, styleForGoodVariable.String()},
51 {2, 3, styleForGoodVariable.String()}}},
52 // Good commands.
53 {"a", []styling{{0, 1, styleForGoodCommand.String()}}},
54 // Bad commands.
55 {"xabc", []styling{{0, 4, styleForBadCommand.String()}}},
56 {"'xa'", []styling{{0, 4, styleForBadCommand.String()}}},
57
58 // "for".
59 // Highlighting variable.
60 //012345678901
61 {"for x [] { }", []styling{
62 {0, 3, styleForGoodCommand.String()},
63 {4, 5, styleForGoodVariable.String()}}},
64 // Highlighting variable, incomplete form.
65 //01234
66 {"for x", []styling{
67 {0, 3, styleForGoodCommand.String()},
68 {4, 5, styleForGoodVariable.String()}}},
69 // Highlighting variable and "else".
70 //012345678901234567890
71 {"for x [] { } else { }", []styling{
72 {0, 3, styleForGoodCommand.String()},
73 {4, 5, styleForGoodVariable.String()},
74 {13, 17, styleForSep["else"]}}},
75
76 // "try".
77 // Highlighting except-variable.
78 //01234567890123456789
79 {"try { } except x { }", []styling{
80 {0, 3, styleForGoodCommand.String()},
81 {8, 14, styleForSep["except"]},
82 {15, 16, styleForGoodVariable.String()},
83 }},
84 // Highlighting except-variable, incomplete form.
85 //0123456789012345
86 {"try { } except x", []styling{
87 {0, 3, styleForGoodCommand.String()},
88 {8, 14, styleForSep["except"]},
89 {15, 16, styleForGoodVariable.String()},
90 }},
91 // Highlighting "else" and "finally".
92 //0123456789012345678901234567
93 {"try { } else { } finally { }", []styling{
94 {0, 3, styleForGoodCommand.String()},
95 {8, 12, styleForSep["else"]},
96 {17, 24, styleForSep["finally"]},
97 }},
98 }
99
100 func TestForm(t *testing.T) {
101 test(t, "form", formTests,
102 func(e *Emitter, ps *parse.Parser) {
103 e.form(parse.ParseForm(ps))
104 })
105 }
106
107 var primaryTests = []emitTests{
108 {"what", []styling{{0, 4, styleForPrimary[parse.Bareword].String()}}},
109 {"$var", []styling{{0, 4, styleForPrimary[parse.Variable].String()}}},
110 {"'a'", []styling{{0, 3, styleForPrimary[parse.SingleQuoted].String()}}},
111 {`"x"`, []styling{{0, 3, styleForPrimary[parse.DoubleQuoted].String()}}},
112 }
113
114 func TestPrimary(t *testing.T) {
115 test(t, "primary", primaryTests,
116 func(e *Emitter, ps *parse.Parser) {
117 e.primary(parse.ParsePrimary(ps, false))
118 })
119 }
120
121 var sepTests = []emitTests{
122 {">", []styling{{0, 1, styleForSep[">"]}}},
123 {"# comment", []styling{{0, 9, styleForComment.String()}}},
124 }
125
126 func TestSep(t *testing.T) {
127 test(t, "sep", sepTests,
128 func(e *Emitter, ps *parse.Parser) {
129 src := ps.Source()
130 e.sep(parse.NewSep(src, 0, len(src)))
131 })
132 }
133
134 func test(t *testing.T, what string,
135 tests []emitTests, f func(*Emitter, *parse.Parser)) {
136
137 for _, test := range tests {
138 var stylings []styling
139 e := &Emitter{goodFormHead, func(b, e int, s string) {
140 stylings = append(stylings, styling{b, e, s})
141 }}
142 ps := parse.NewParser("<test>", test.source)
143
144 f(e, ps)
145
146 if !reflect.DeepEqual(stylings, test.wantStylings) {
147 t.Errorf("%s %q gets stylings %v, want %v", what, test.source,
148 stylings, test.wantStylings)
149 }
150 }
151 }
0 package highlight
1
2 import (
3 "github.com/elves/elvish/edit/ui"
4 "github.com/elves/elvish/parse"
5 )
6
7 // Semantically applied styles.
8 var (
9 styleForGoodCommand = ui.Styles{"green"}
10 styleForBadCommand = ui.Styles{"red"}
11 styleForGoodVariable = ui.Styles{"magenta"}
12 styleForBadVariable = ui.Styles{"white", "bg-red"}
13 )
14
15 // Lexically applied styles.
16
17 // ui.Styles for Primary nodes.
18 var styleForPrimary = map[parse.PrimaryType]ui.Styles{
19 parse.Bareword: {},
20 parse.SingleQuoted: {"yellow"},
21 parse.DoubleQuoted: {"yellow"},
22 parse.Variable: styleForGoodVariable,
23 parse.Wildcard: {},
24 parse.Tilde: {},
25 }
26
27 var styleForComment = ui.Styles{"cyan"}
28
29 // ui.Styles for Sep nodes.
30 var styleForSep = map[string]string{
31 ">": "green",
32 ">>": "green",
33 "<": "green",
34 "?>": "green",
35 "|": "green",
36
37 "?(": "bold",
38 "(": "bold",
39 ")": "bold",
40 "[": "bold",
41 "]": "bold",
42 "{": "bold",
43 "}": "bold",
44
45 "&": "bold",
46
47 "if": "yellow",
48 "then": "yellow",
49 "elif": "yellow",
50 "else": "yellow",
51 "fi": "yellow",
52
53 "while": "yellow",
54 "do": "yellow",
55 "done": "yellow",
56
57 "for": "yellow",
58 "in": "yellow",
59
60 "try": "yellow",
61 "except": "yellow",
62 "finally": "yellow",
63 "tried": "yellow",
64
65 "begin": "yellow",
66 "end": "yellow",
67 }
0 package highlight
1
2 import (
3 "bytes"
4 "sort"
5
6 "github.com/elves/elvish/edit/ui"
7 )
8
9 // Preparing and applying styling.
10
11 type Styling struct {
12 begins []stylingEvent
13 ends []stylingEvent
14 }
15
16 func (s *Styling) Add(begin, end int, style string) {
17 if style == "" {
18 return
19 }
20 s.begins = append(s.begins, stylingEvent{begin, style})
21 s.ends = append(s.ends, stylingEvent{end, style})
22 }
23
24 func (s *Styling) Apply() *StylingApplier {
25 sort.Sort(stylingEvents(s.begins))
26 sort.Sort(stylingEvents(s.ends))
27 return &StylingApplier{s, make(map[string]int), 0, 0, ""}
28 }
29
30 type StylingApplier struct {
31 *Styling
32 occurrence map[string]int
33 ibegin int
34 iend int
35 result string
36 }
37
38 func (a *StylingApplier) At(i int) {
39 changed := false
40 for a.iend < len(a.ends) && a.ends[a.iend].pos == i {
41 a.occurrence[a.ends[a.iend].style]--
42 a.iend++
43 changed = true
44 }
45 for a.ibegin < len(a.begins) && a.begins[a.ibegin].pos == i {
46 a.occurrence[a.begins[a.ibegin].style]++
47 a.ibegin++
48 changed = true
49 }
50
51 if changed {
52 b := new(bytes.Buffer)
53 for style, occ := range a.occurrence {
54 if occ == 0 {
55 continue
56 }
57 if b.Len() > 0 {
58 b.WriteString(";")
59 }
60 b.WriteString(ui.TranslateStyle(style))
61 }
62 a.result = b.String()
63 }
64 }
65
66 func (a *StylingApplier) Get() string {
67 return a.result
68 }
69
70 type stylingEvent struct {
71 pos int
72 style string
73 }
74
75 type stylingEvents []stylingEvent
76
77 func (s stylingEvents) Len() int { return len(s) }
78 func (s stylingEvents) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
79 func (s stylingEvents) Less(i, j int) bool { return s[i].pos < s[j].pos }
0 package edit
1
2 import (
3 "os"
4
5 "github.com/elves/elvish/edit/highlight"
6 "github.com/elves/elvish/eval"
7 "github.com/elves/elvish/parse"
8 "github.com/elves/elvish/util"
9 )
10
11 func doHighlight(n parse.Node, ed *Editor) {
12 s := &highlight.Emitter{
13 func(s string) bool { return goodFormHead(s, ed) },
14 ed.styling.Add,
15 }
16 s.EmitAll(n)
17 }
18
19 func goodFormHead(head string, ed *Editor) bool {
20 if eval.IsBuiltinSpecial[head] {
21 return true
22 } else if util.DontSearch(head) {
23 // XXX don't stat twice
24 return util.IsExecutable(head) || isDir(head)
25 } else {
26 ev := ed.evaler
27 explode, ns, name := eval.ParseVariable(head)
28 if !explode {
29 switch ns {
30 case "":
31 if ev.Builtin[eval.FnPrefix+name] != nil || ev.Global[eval.FnPrefix+name] != nil {
32 return true
33 }
34 case "e":
35 if ed.isExternal[name] {
36 return true
37 }
38 default:
39 if ev.Modules[ns] != nil && ev.Modules[ns][eval.FnPrefix+name] != nil {
40 return true
41 }
42 }
43 }
44 return ed.isExternal[head]
45 }
46 }
47
48 func isDir(fname string) bool {
49 stat, err := os.Stat(fname)
50 return err == nil && stat.IsDir()
51 }
0 package edit
1
2 import (
3 "errors"
4 "fmt"
5 "strconv"
6 "strings"
7
8 "github.com/elves/elvish/edit/ui"
9 )
10
11 // Command history listing mode.
12
13 var _ = registerBuiltins(modeHistoryListing, map[string]func(*Editor){
14 "start": histlistStart,
15 "toggle-dedup": histlistToggleDedup,
16 "toggle-case-sensitivity": histlistToggleCaseSensitivity,
17 })
18
19 func init() {
20 registerBindings(modeHistoryListing, modeHistoryListing,
21 map[ui.Key]string{
22 {'G', ui.Ctrl}: "toggle-case-sensitivity",
23 {'D', ui.Ctrl}: "toggle-dedup",
24 })
25 }
26
27 // ErrStoreOffline is thrown when an operation requires the storage backend, but
28 // it is offline.
29 var ErrStoreOffline = errors.New("store offline")
30
31 type histlist struct {
32 *listing
33 all []string
34 dedup bool
35 caseInsensitive bool
36 last map[string]int
37 shown []string
38 index []int
39 indexWidth int
40 }
41
42 func newHistlist(cmds []string) *listing {
43 last := make(map[string]int)
44 for i, entry := range cmds {
45 last[entry] = i
46 }
47 hl := &histlist{
48 // This has to be here for the initializatio to work :(
49 listing: &listing{},
50 all: cmds, last: last, indexWidth: len(strconv.Itoa(len(cmds) - 1))}
51 l := newListing(modeHistoryListing, hl)
52 hl.listing = &l
53 return &l
54 }
55
56 func (hl *histlist) ModeTitle(i int) string {
57 s := " HISTORY "
58 if hl.dedup {
59 s += "(dedup on) "
60 }
61 if hl.caseInsensitive {
62 s += "(case-insensitive) "
63 }
64 return s
65 }
66
67 func (*histlist) CursorOnModeLine() bool {
68 return true
69 }
70
71 func (hl *histlist) Len() int {
72 return len(hl.shown)
73 }
74
75 func (hl *histlist) Show(i int) (string, ui.Styled) {
76 return fmt.Sprintf("%d", hl.index[i]), ui.Unstyled(hl.shown[i])
77 }
78
79 func (hl *histlist) Filter(filter string) int {
80 hl.updateShown()
81 return len(hl.shown) - 1
82 }
83
84 func (hl *histlist) toggleDedup() {
85 hl.dedup = !hl.dedup
86 hl.updateShown()
87 }
88
89 func (hl *histlist) toggleCaseSensitivity() {
90 hl.caseInsensitive = !hl.caseInsensitive
91 hl.updateShown()
92 }
93
94 func (hl *histlist) updateShown() {
95 hl.shown = nil
96 hl.index = nil
97 dedup := hl.dedup
98 filter := hl.filter
99 if hl.caseInsensitive {
100 filter = strings.ToLower(filter)
101 }
102 for i, entry := range hl.all {
103 fentry := entry
104 if hl.caseInsensitive {
105 fentry = strings.ToLower(entry)
106 }
107 if (!dedup || hl.last[entry] == i) && strings.Contains(fentry, filter) {
108 hl.index = append(hl.index, i)
109 hl.shown = append(hl.shown, entry)
110 }
111 }
112 hl.selected = len(hl.shown) - 1
113 }
114
115 // Editor interface.
116
117 func (hl *histlist) Accept(i int, ed *Editor) {
118 line := hl.shown[i]
119 if len(ed.line) > 0 {
120 line = "\n" + line
121 }
122 ed.insertAtDot(line)
123 }
124
125 func histlistStart(ed *Editor) {
126 cmds, err := getCmds(ed)
127 if err != nil {
128 ed.Notify("%v", err)
129 return
130 }
131
132 ed.mode = newHistlist(cmds)
133 }
134
135 func getCmds(ed *Editor) ([]string, error) {
136 if ed.daemon == nil {
137 return nil, ErrStoreOffline
138 }
139 return ed.historyFuser.AllCmds()
140 }
141
142 func histlistToggleDedup(ed *Editor) {
143 if hl := getHistlist(ed); hl != nil {
144 hl.toggleDedup()
145 }
146 }
147
148 func histlistToggleCaseSensitivity(ed *Editor) {
149 if hl := getHistlist(ed); hl != nil {
150 hl.toggleCaseSensitivity()
151 }
152 }
153
154 func getHistlist(ed *Editor) *histlist {
155 if l, ok := ed.mode.(*listing); ok {
156 if hl, ok := l.provider.(*histlist); ok {
157 return hl
158 }
159 }
160 return nil
161 }
0 package edit
1
2 import (
3 "testing"
4
5 "github.com/elves/elvish/edit/ui"
6 )
7
8 var (
9 theHistList = newHistlist([]string{"ls", "echo lalala", "ls"})
10
11 histlistFilterTests = []listingFilterTestCases{
12 {"", []shown{
13 {"0", ui.Unstyled("ls")},
14 {"1", ui.Unstyled("echo lalala")},
15 {"2", ui.Unstyled("ls")}}},
16 {"l", []shown{
17 {"0", ui.Unstyled("ls")},
18 {"1", ui.Unstyled("echo lalala")},
19 {"2", ui.Unstyled("ls")}}},
20 // {"ch", []styled{unstyled("1 echo lalala")}},
21 }
22 )
23
24 func TestHistlist(t *testing.T) {
25 testListingFilter(t, "theHistList", theHistList, histlistFilterTests)
26 }
0 package history
1
2 // Fuser provides a unified view into a shared storage-backed command history
3 // and per-session history.
4 type Fuser struct {
5 store Store
6 storeUpper int
7
8 // Per-session history.
9 cmds []string
10 seqs []int
11 }
12
13 func NewFuser(store Store) (*Fuser, error) {
14 upper, err := store.NextCmdSeq()
15 if err != nil {
16 return nil, err
17 }
18 return &Fuser{store, upper, nil, nil}, nil
19 }
20
21 func (f *Fuser) AddCmd(cmd string) error {
22 seq, err := f.store.AddCmd(cmd)
23 if err != nil {
24 return err
25 }
26 f.cmds = append(f.cmds, cmd)
27 f.seqs = append(f.seqs, seq)
28 return nil
29 }
30
31 func (f *Fuser) AllCmds() ([]string, error) {
32 cmds, err := f.store.Cmds(0, f.storeUpper)
33 if err != nil {
34 return nil, err
35 }
36 return append(cmds, f.cmds...), nil
37 }
38
39 func (f *Fuser) SessionCmds() []string {
40 return f.cmds
41 }
42
43 func (f *Fuser) Walker(prefix string) *Walker {
44 return NewWalker(f.store, f.storeUpper, f.cmds, f.seqs, prefix)
45 }
0 package history
1
2 import (
3 "errors"
4 "reflect"
5 "testing"
6 )
7
8 func TestNewFuser(t *testing.T) {
9 mockError := errors.New("mock error")
10 _, err := NewFuser(&mockStore{oneOffError: mockError})
11 if err != mockError {
12 t.Errorf("NewFuser -> error %v, want %v", err, mockError)
13 }
14 }
15
16 var fuserStore = &mockStore{cmds: []string{"store 1"}}
17
18 func TestFuser(t *testing.T) {
19 f, err := NewFuser(fuserStore)
20 if err != nil {
21 t.Errorf("NewFuser -> error %v, want nil", err)
22 }
23
24 // AddCmd should not add command to session history if backend has an error
25 // adding the command.
26 mockError := errors.New("mock error")
27 fuserStore.oneOffError = mockError
28 err = f.AddCmd("haha")
29 if err != mockError {
30 t.Errorf("AddCmd doesn't forward backend error")
31 }
32 if len(f.cmds) != 0 {
33 t.Errorf("AddCmd adds command to session history when backend errors")
34 }
35
36 // AddCmd should add command to both storage and session
37 f.AddCmd("session 1")
38 if !reflect.DeepEqual(fuserStore.cmds, []string{"store 1", "session 1"}) {
39 t.Errorf("AddCmd doesn't add command to backend storage")
40 }
41 if !reflect.DeepEqual(f.SessionCmds(), []string{"session 1"}) {
42 t.Errorf("AddCmd doesn't add command to session history")
43 }
44
45 // AllCmds should return all commands from the storage when the Fuser was
46 // created followed by session commands
47 fuserStore.AddCmd("other session 1")
48 fuserStore.AddCmd("other session 2")
49 f.AddCmd("session 2")
50 cmds, err := f.AllCmds()
51 if err != nil {
52 t.Errorf("AllCmds returns error")
53 }
54 if !reflect.DeepEqual(cmds, []string{"store 1", "session 1", "session 2"}) {
55 t.Errorf("AllCmds doesn't return all commands")
56 }
57
58 // AllCmds should forward backend storage error
59 mockError = errors.New("another mock error")
60 fuserStore.oneOffError = mockError
61 _, err = f.AllCmds()
62 if err != mockError {
63 t.Errorf("AllCmds doesn't forward backend error")
64 }
65
66 // Walker should return a walker that walks through all commands
67 w := f.Walker("")
68 wantCmd(t, w.Prev, 4, "session 2")
69 wantCmd(t, w.Prev, 1, "session 1")
70 wantCmd(t, w.Prev, 0, "store 1")
71 wantErr(t, w.Prev, ErrEndOfHistory)
72 }
0 // Package history provides utilities for the command history.
1 package history
0 package history
1
2 import (
3 "strings"
4 )
5
6 // Store is the interface of the storage backend.
7 type Store interface {
8 NextCmdSeq() (int, error)
9 AddCmd(cmd string) (int, error)
10 Cmds(from, upto int) ([]string, error)
11 PrevCmd(upto int, prefix string) (int, string, error)
12 }
13
14 // mockStore is an implementation of the Store interface that can be used for
15 // testing.
16 type mockStore struct {
17 cmds []string
18
19 oneOffError error
20 }
21
22 func (s *mockStore) error() error {
23 err := s.oneOffError
24 s.oneOffError = nil
25 return err
26 }
27
28 func (s *mockStore) NextCmdSeq() (int, error) {
29 return len(s.cmds), s.error()
30 }
31
32 func (s *mockStore) AddCmd(cmd string) (int, error) {
33 if s.oneOffError != nil {
34 return -1, s.error()
35 }
36 s.cmds = append(s.cmds, cmd)
37 return len(s.cmds) - 1, nil
38 }
39
40 func (s *mockStore) Cmds(from, upto int) ([]string, error) {
41 return s.cmds[from:upto], s.error()
42 }
43
44 func (s *mockStore) PrevCmd(upto int, prefix string) (int, string, error) {
45 if s.oneOffError != nil {
46 return -1, "", s.error()
47 }
48 if upto < 0 || upto > len(s.cmds) {
49 upto = len(s.cmds)
50 }
51 for i := upto - 1; i >= 0; i-- {
52 if strings.HasPrefix(s.cmds[i], prefix) {
53 return i, s.cmds[i], nil
54 }
55 }
56 return -1, "", ErrEndOfHistory
57 }
0 package history
1
2 import (
3 "errors"
4 "strings"
5
6 "github.com/elves/elvish/store/storedefs"
7 )
8
9 var ErrEndOfHistory = errors.New("end of history")
10
11 // Walker is used for walking through history entries with a given (possibly
12 // empty) prefix, skipping duplicates entries.
13 type Walker struct {
14 store Store
15 storeUpper int
16 sessionCmds []string
17 sessionSeqs []int
18 prefix string
19
20 // The next element to fetch from the session history. If equal to -1, the
21 // next element comes from the storage backend.
22 sessionIdx int
23 // Index of the next element in the stack that Prev will return on next
24 // call. If equal to len(stack), the next element needs to be fetched,
25 // either from the session history or the storage backend.
26 top int
27 stack []string
28 seq []int
29 inStack map[string]bool
30 }
31
32 func NewWalker(store Store, upper int, cmds []string, seqs []int, prefix string) *Walker {
33 return &Walker{store, upper, cmds, seqs, prefix,
34 len(cmds) - 1, 0, nil, nil, map[string]bool{}}
35 }
36
37 // Prefix returns the prefix of the commands that the walker walks through.
38 func (w *Walker) Prefix() string {
39 return w.prefix
40 }
41
42 // CurrentSeq returns the sequence number of the current entry.
43 func (w *Walker) CurrentSeq() int {
44 if len(w.seq) > 0 && w.top <= len(w.seq) && w.top > 0 {
45 return w.seq[w.top-1]
46 }
47 return -1
48 }
49
50 // CurrentSeq returns the content of the current entry.
51 func (w *Walker) CurrentCmd() string {
52 if len(w.stack) > 0 && w.top <= len(w.stack) && w.top > 0 {
53 return w.stack[w.top-1]
54 }
55 return ""
56 }
57
58 // Prev walks to the previous matching history entry, skipping all duplicates.
59 func (w *Walker) Prev() (int, string, error) {
60 // Entry comes from the stack.
61 if w.top < len(w.stack) {
62 i := w.top
63 w.top++
64 return w.seq[i], w.stack[i], nil
65 }
66
67 // Find the entry in the session part.
68 for i := w.sessionIdx; i >= 0; i-- {
69 seq := w.sessionSeqs[i]
70 cmd := w.sessionCmds[i]
71 if strings.HasPrefix(cmd, w.prefix) && !w.inStack[cmd] {
72 w.push(cmd, seq)
73 w.sessionIdx = i - 1
74 return seq, cmd, nil
75 }
76 }
77 // Not found in the session part.
78 w.sessionIdx = -1
79
80 seq := w.storeUpper
81 if len(w.seq) > 0 && seq > w.seq[len(w.seq)-1] {
82 seq = w.seq[len(w.seq)-1]
83 }
84 for {
85 var (
86 cmd string
87 err error
88 )
89 seq, cmd, err = w.store.PrevCmd(seq, w.prefix)
90 if err != nil {
91 if err.Error() == storedefs.ErrNoMatchingCmd.Error() {
92 err = ErrEndOfHistory
93 }
94 return -1, "", err
95 }
96 if !w.inStack[cmd] {
97 w.push(cmd, seq)
98 return seq, cmd, nil
99 }
100 }
101 }
102
103 func (w *Walker) push(cmd string, seq int) {
104 w.inStack[cmd] = true
105 w.stack = append(w.stack, cmd)
106 w.seq = append(w.seq, seq)
107 w.top++
108 }
109
110 // Next reverses Prev.
111 func (w *Walker) Next() (int, string, error) {
112 if w.top <= 0 {
113 return -1, "", ErrEndOfHistory
114 }
115 w.top--
116 if w.top == 0 {
117 return -1, "", ErrEndOfHistory
118 }
119 return w.seq[w.top-1], w.stack[w.top-1], nil
120 }
0 package history
1
2 import (
3 "errors"
4 "testing"
5
6 "github.com/elves/elvish/store/storedefs"
7 )
8
9 func TestWalker(t *testing.T) {
10 mockError := errors.New("mock error")
11 walkerStore := &mockStore{
12 // 0 1 2 3 4 5
13 cmds: []string{"echo", "ls -l", "echo a", "ls -a", "echo a", "ls a"},
14 }
15
16 var w *Walker
17
18 // Going back and forth.
19 w = NewWalker(walkerStore, -1, nil, nil, "")
20 wantCurrent(t, w, -1, "")
21 wantCmd(t, w.Prev, 5, "ls a")
22 wantCurrent(t, w, 5, "ls a")
23 wantErr(t, w.Next, ErrEndOfHistory)
24 wantErr(t, w.Next, ErrEndOfHistory)
25 wantCmd(t, w.Prev, 5, "ls a")
26
27 wantCmd(t, w.Prev, 4, "echo a")
28 wantCmd(t, w.Next, 5, "ls a")
29 wantCmd(t, w.Prev, 4, "echo a")
30
31 wantCmd(t, w.Prev, 3, "ls -a")
32 // "echo a" should be skipped
33 wantCmd(t, w.Prev, 1, "ls -l")
34 wantCmd(t, w.Prev, 0, "echo")
35 wantErr(t, w.Prev, ErrEndOfHistory)
36
37 // With an upper bound on the storage.
38 w = NewWalker(walkerStore, 2, nil, nil, "")
39 wantCmd(t, w.Prev, 1, "ls -l")
40 wantCmd(t, w.Prev, 0, "echo")
41 wantErr(t, w.Prev, ErrEndOfHistory)
42
43 // Prefix matching 1.
44 w = NewWalker(walkerStore, -1, nil, nil, "echo")
45 if w.Prefix() != "echo" {
46 t.Errorf("got prefix %q, want %q", w.Prefix(), "echo")
47 }
48 wantCmd(t, w.Prev, 4, "echo a")
49 wantCmd(t, w.Prev, 0, "echo")
50 wantErr(t, w.Prev, ErrEndOfHistory)
51
52 // Prefix matching 2.
53 w = NewWalker(walkerStore, -1, nil, nil, "ls")
54 wantCmd(t, w.Prev, 5, "ls a")
55 wantCmd(t, w.Prev, 3, "ls -a")
56 wantCmd(t, w.Prev, 1, "ls -l")
57 wantErr(t, w.Prev, ErrEndOfHistory)
58
59 // Walker with session history.
60 w = NewWalker(walkerStore, -1,
61 []string{"ls -l", "ls -v", "echo haha"}, []int{7, 10, 12}, "ls")
62 wantCmd(t, w.Prev, 10, "ls -v")
63
64 wantCmd(t, w.Prev, 7, "ls -l")
65 wantCmd(t, w.Next, 10, "ls -v")
66 wantCmd(t, w.Prev, 7, "ls -l")
67
68 wantCmd(t, w.Prev, 5, "ls a")
69 wantCmd(t, w.Next, 7, "ls -l")
70 wantCmd(t, w.Prev, 5, "ls a")
71
72 wantCmd(t, w.Prev, 3, "ls -a")
73 wantErr(t, w.Prev, ErrEndOfHistory)
74
75 // Backend error.
76 w = NewWalker(walkerStore, -1, nil, nil, "")
77 wantCmd(t, w.Prev, 5, "ls a")
78 wantCmd(t, w.Prev, 4, "echo a")
79 walkerStore.oneOffError = mockError
80 wantErr(t, w.Prev, mockError)
81
82 // storedefs.ErrNoMatchingCmd is turned into ErrEndOfHistory.
83 w = NewWalker(walkerStore, -1, nil, nil, "")
84 walkerStore.oneOffError = storedefs.ErrNoMatchingCmd
85 wantErr(t, w.Prev, ErrEndOfHistory)
86 }
87
88 func wantCurrent(t *testing.T, w *Walker, wantSeq int, wantCmd string) {
89 seq, cmd := w.CurrentSeq(), w.CurrentCmd()
90 if seq != wantSeq {
91 t.Errorf("got seq %d, want %d", seq, wantSeq)
92 }
93 if cmd != wantCmd {
94 t.Errorf("got cmd %q, want %q", cmd, wantCmd)
95 }
96 }
97
98 func wantCmd(t *testing.T, f func() (int, string, error), wantSeq int, wantCmd string) {
99 seq, cmd, err := f()
100 if seq != wantSeq {
101 t.Errorf("got seq %d, want %d", seq, wantSeq)
102 }
103 if cmd != wantCmd {
104 t.Errorf("got cmd %q, want %q", cmd, wantCmd)
105 }
106 if err != nil {
107 t.Errorf("got err %v, want nil", err)
108 }
109 }
110
111 func wantErr(t *testing.T, f func() (int, string, error), want error) {
112 _, _, err := f()
113 if err != want {
114 t.Errorf("got err %v, want %v", err, want)
115 }
116 }
0 package edit
1
2 import (
3 "fmt"
4 "strings"
5
6 "github.com/elves/elvish/edit/history"
7 "github.com/elves/elvish/edit/ui"
8 "github.com/elves/elvish/eval"
9 )
10
11 // Command history subsystem.
12
13 // Interface.
14
15 var _ = registerBuiltins("history", map[string]func(*Editor){
16 "start": historyStart,
17 "up": historyUp,
18 "down": historyDown,
19 "down-or-quit": historyDownOrQuit,
20 "switch-to-histlist": historySwitchToHistlist,
21 "default": historyDefault,
22 })
23
24 func init() {
25 registerBindings(modeHistory, "history", map[ui.Key]string{
26 {ui.Up, 0}: "up",
27 {ui.Down, 0}: "down-or-quit",
28 {'[', ui.Ctrl}: "insert:start",
29 {'R', ui.Ctrl}: "switch-to-histlist",
30 ui.Default: "default",
31 })
32 }
33
34 type hist struct {
35 *history.Walker
36 }
37
38 func (*hist) Binding(k ui.Key) eval.CallableValue {
39 return getBinding(modeHistory, k)
40 }
41
42 func (h *hist) ModeLine() renderer {
43 return modeLineRenderer{fmt.Sprintf(" HISTORY #%d ", h.CurrentSeq()), ""}
44 }
45
46 func historyStart(ed *Editor) {
47 if ed.historyFuser == nil {
48 ed.Notify("history offline")
49 return
50 }
51 prefix := ed.line[:ed.dot]
52 walker := ed.historyFuser.Walker(prefix)
53 ed.hist = hist{walker}
54 if ed.prevHistory() {
55 ed.mode = &ed.hist
56 } else {
57 ed.addTip("no matching history item")
58 }
59 }
60
61 func historyUp(ed *Editor) {
62 ed.prevHistory()
63 }
64
65 func historyDown(ed *Editor) {
66 ed.nextHistory()
67 }
68
69 func historyDownOrQuit(ed *Editor) {
70 if !ed.nextHistory() {
71 ed.mode = &ed.insert
72 }
73 }
74
75 func historySwitchToHistlist(ed *Editor) {
76 histlistStart(ed)
77 if hl := getHistlist(ed); hl != nil {
78 ed.line = ""
79 ed.dot = 0
80 hl.changeFilter(ed.hist.Prefix())
81 }
82 }
83
84 func historyDefault(ed *Editor) {
85 ed.acceptHistory()
86 ed.mode = &ed.insert
87 ed.nextAction = action{typ: reprocessKey}
88 }
89
90 // Implementation.
91
92 func (ed *Editor) appendHistory(line string) {
93 // TODO: should have a user variable to control the behavior
94 // Do not add command leading by space into history. This is
95 // useful for confidential operations.
96 if strings.HasPrefix(line, " ") {
97 return
98 }
99
100 if ed.daemon != nil && ed.historyFuser != nil {
101 ed.historyMutex.Lock()
102 ed.daemon.Waits().Add(1)
103 go func() {
104 // TODO(xiaq): Report possible error
105 err := ed.historyFuser.AddCmd(line)
106 ed.daemon.Waits().Done()
107 ed.historyMutex.Unlock()
108 if err != nil {
109 logger.Println("failed to add cmd to store:", err)
110 } else {
111 logger.Println("added cmd to store:", line)
112 }
113 }()
114 }
115 }
116
117 func (ed *Editor) prevHistory() bool {
118 _, _, err := ed.hist.Prev()
119 return err == nil
120 }
121
122 func (ed *Editor) nextHistory() bool {
123 _, _, err := ed.hist.Next()
124 return err == nil
125 }
126
127 // acceptHistory accepts the currently selected history.
128 func (ed *Editor) acceptHistory() {
129 ed.line = ed.hist.CurrentCmd()
130 ed.dot = len(ed.line)
131 }
0 package edit
1
2 import (
3 "sync"
4
5 "github.com/elves/elvish/daemon/api"
6 "github.com/elves/elvish/eval"
7 )
8
9 // History implements the $le:history variable. It is list-like.
10 type History struct {
11 mutex *sync.RWMutex
12 st *api.Client
13 }
14
15 var _ eval.ListLike = History{}
16
17 func (hv History) Kind() string {
18 return "list"
19 }
20
21 func (hv History) Repr(int) string {
22 return "$le:history"
23 }
24
25 func (hv History) Len() int {
26 hv.mutex.RLock()
27 defer hv.mutex.RUnlock()
28
29 nextseq, err := hv.st.NextCmdSeq()
30 maybeThrow(err)
31 return nextseq - 1
32 }
33
34 func (hv History) Iterate(f func(eval.Value) bool) {
35 hv.mutex.RLock()
36 defer hv.mutex.RUnlock()
37
38 n := hv.Len()
39 cmds, err := hv.st.Cmds(1, n+1)
40 maybeThrow(err)
41
42 for _, cmd := range cmds {
43 if !f(eval.String(cmd)) {
44 break
45 }
46 }
47 }
48
49 func (hv History) IndexOne(idx eval.Value) eval.Value {
50 hv.mutex.RLock()
51 defer hv.mutex.RUnlock()
52
53 slice, i, j := eval.ParseAndFixListIndex(eval.ToString(idx), hv.Len())
54 if slice {
55 cmds, err := hv.st.Cmds(i+1, j+1)
56 maybeThrow(err)
57 vs := make([]eval.Value, len(cmds))
58 for i := range cmds {
59 vs[i] = eval.String(cmds[i])
60 }
61 return eval.NewList(vs...)
62 }
63 s, err := hv.st.Cmd(i + 1)
64 maybeThrow(err)
65 return eval.String(s)
66 }
0 package edit
1
2 import (
3 "fmt"
4 "os"
5
6 "github.com/elves/elvish/eval"
7 )
8
9 // The $le:{before,after}-readline lists that contain hooks. We might have more
10 // hooks in future.
11
12 var _ = registerVariable("before-readline", makeListVariable)
13
14 func (ed *Editor) beforeReadLine() eval.List {
15 return ed.variables["before-readline"].Get().(eval.List)
16 }
17
18 var _ = registerVariable("after-readline", makeListVariable)
19
20 func (ed *Editor) afterReadLine() eval.List {
21 return ed.variables["after-readline"].Get().(eval.List)
22 }
23
24 func makeListVariable() eval.Variable {
25 return eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList)
26 }
27
28 func callHooks(ev *eval.Evaler, li eval.List, args ...eval.Value) {
29 if li.Len() == 0 {
30 return
31 }
32
33 opfunc := func(ec *eval.EvalCtx) {
34 li.Iterate(func(v eval.Value) bool {
35 fn, ok := v.(eval.CallableValue)
36 if !ok {
37 fmt.Fprintf(os.Stderr, "not a function: %s\n", v.Repr(eval.NoPretty))
38 return true
39 }
40 err := ec.PCall(fn, args, eval.NoOpts)
41 if err != nil {
42 // TODO Print stack trace.
43 fmt.Fprintf(os.Stderr, "function error: %s\n", err.Error())
44 }
45 return true
46 })
47 }
48 ev.Eval(eval.Op{opfunc, -1, -1}, "[hooks]", "no source")
49 }
0 package edit
1
2 import (
3 "io"
4 "strings"
5 "unicode"
6 "unicode/utf8"
7
8 "github.com/elves/elvish/edit/ui"
9 "github.com/elves/elvish/eval"
10 "github.com/elves/elvish/util"
11 )
12
13 // Builtins related to insert and command mode.
14
15 var (
16 _ = registerBuiltins("", map[string]func(*Editor){
17 "kill-line-left": killLineLeft,
18 "kill-line-right": killLineRight,
19 "kill-word-left": killWordLeft,
20 "kill-small-word-left": killSmallWordLeft,
21 "kill-rune-left": killRuneLeft,
22 "kill-rune-right": killRuneRight,
23
24 "move-dot-left": moveDotLeft,
25 "move-dot-right": moveDotRight,
26 "move-dot-left-word": moveDotLeftWord,
27 "move-dot-right-word": moveDotRightWord,
28 "move-dot-sol": moveDotSOL,
29 "move-dot-eol": moveDotEOL,
30 "move-dot-up": moveDotUp,
31 "move-dot-down": moveDotDown,
32
33 "insert-last-word": insertLastWord,
34 "insert-key": insertKey,
35
36 "return-line": returnLine,
37 "smart-enter": smartEnter,
38 "return-eof": returnEOF,
39
40 "toggle-quote-paste": toggleQuotePaste,
41 "insert-raw": startInsertRaw,
42
43 "end-of-history": endOfHistory,
44 "redraw": redraw,
45 })
46 _ = registerBuiltins("insert", map[string]func(*Editor){
47 "start": insertStart,
48 "default": insertDefault,
49 })
50 _ = registerBuiltins("command", map[string]func(*Editor){
51 "start": commandStart,
52 "default": commandDefault,
53 })
54 )
55
56 func init() {
57 registerBindings(modeInsert, "", map[ui.Key]string{
58 // Moving.
59 {ui.Left, 0}: "move-dot-left",
60 {ui.Right, 0}: "move-dot-right",
61 {ui.Up, ui.Alt}: "move-dot-up",
62 {ui.Down, ui.Alt}: "move-dot-down",
63 {ui.Left, ui.Ctrl}: "move-dot-left-word",
64 {ui.Right, ui.Ctrl}: "move-dot-right-word",
65 {ui.Home, 0}: "move-dot-sol",
66 {ui.End, 0}: "move-dot-eol",
67 // Killing.
68 {'U', ui.Ctrl}: "kill-line-left",
69 {'K', ui.Ctrl}: "kill-line-right",
70 {'W', ui.Ctrl}: "kill-word-left",
71 {ui.Backspace, 0}: "kill-rune-left",
72 // Some terminal send ^H on backspace
73 // ui.Key{'H', ui.Ctrl}: "kill-rune-left",
74 {ui.Delete, 0}: "kill-rune-right",
75 // Inserting.
76 {'.', ui.Alt}: "insert-last-word",
77 {ui.Enter, ui.Alt}: "insert-key",
78 // Controls.
79 {ui.Enter, 0}: "smart-enter",
80 {'D', ui.Ctrl}: "return-eof",
81 {ui.F2, 0}: "toggle-quote-paste",
82
83 // Other modes.
84 // ui.Key{'[', ui.Ctrl}: "command-start",
85 {ui.Tab, 0}: "compl:smart-start",
86 {ui.Up, 0}: "history:start",
87 {ui.Down, 0}: "end-of-history",
88 {'N', ui.Ctrl}: "nav:start",
89 {'R', ui.Ctrl}: "histlist:start",
90 {'1', ui.Alt}: "lastcmd:start",
91 {'L', ui.Ctrl}: "loc:start",
92 {'V', ui.Ctrl}: "insert-raw",
93
94 ui.Default: "insert:default",
95 })
96 registerBindings(modeCommand, "", map[ui.Key]string{
97 // Moving.
98 {'h', 0}: "move-dot-left",
99 {'l', 0}: "move-dot-right",
100 {'k', 0}: "move-dot-up",
101 {'j', 0}: "move-dot-down",
102 {'b', 0}: "move-dot-left-word",
103 {'w', 0}: "move-dot-right-word",
104 {'0', 0}: "move-dot-sol",
105 {'$', 0}: "move-dot-eol",
106 // Killing.
107 {'x', 0}: "kill-rune-right",
108 {'D', 0}: "kill-line-right",
109 // Controls.
110 {'i', 0}: "insert:start",
111 ui.Default: "command:default",
112 })
113 }
114
115 type insert struct {
116 quotePaste bool
117 // The number of consecutive key inserts. Used for abbreviation expansion.
118 literalInserts int
119 // Indicates whether a key was inserted (via insert-default). A hack for
120 // maintaining the inserts field.
121 insertedLiteral bool
122 }
123
124 // ui.Insert mode is the default mode and has an empty mode.
125 func (ins *insert) ModeLine() renderer {
126 if ins.quotePaste {
127 return modeLineRenderer{" INSERT (quote paste) ", ""}
128 }
129 return nil
130 }
131
132 func (*insert) Binding(k ui.Key) eval.CallableValue {
133 return getBinding(modeInsert, k)
134 }
135
136 type command struct{}
137
138 func (*command) ModeLine() renderer {
139 return modeLineRenderer{" COMMAND ", ""}
140 }
141
142 func (*command) Binding(k ui.Key) eval.CallableValue {
143 return getBinding(modeCommand, k)
144 }
145
146 func insertStart(ed *Editor) {
147 ed.mode = &ed.insert
148 }
149
150 func commandStart(ed *Editor) {
151 ed.mode = &ed.command
152 }
153
154 func killLineLeft(ed *Editor) {
155 sol := util.FindLastSOL(ed.line[:ed.dot])
156 ed.line = ed.line[:sol] + ed.line[ed.dot:]
157 ed.dot = sol
158 }
159
160 func killLineRight(ed *Editor) {
161 eol := util.FindFirstEOL(ed.line[ed.dot:]) + ed.dot
162 ed.line = ed.line[:ed.dot] + ed.line[eol:]
163 }
164
165 // NOTE(xiaq): A word is a run of non-space runes. When killing a word,
166 // trimming spaces are removed as well. Examples:
167 // "abc xyz" -> "abc ", "abc xyz " -> "abc ".
168
169 func killWordLeft(ed *Editor) {
170 if ed.dot == 0 {
171 return
172 }
173 space := strings.LastIndexFunc(
174 strings.TrimRightFunc(ed.line[:ed.dot], unicode.IsSpace),
175 unicode.IsSpace) + 1
176 ed.line = ed.line[:space] + ed.line[ed.dot:]
177 ed.dot = space
178 }
179
180 // NOTE(xiaq): A small word is either a run of alphanumeric (Unicode category L
181 // or N) runes or a run of non-alphanumeric runes. This is consistent with vi's
182 // definition of word, except that "_" is not considered alphanumeric. When
183 // killing a small word, trimming spaces are removed as well. Examples:
184 // "abc/~" -> "abc", "~/abc" -> "~/", "abc* " -> "abc"
185
186 func killSmallWordLeft(ed *Editor) {
187 left := strings.TrimRightFunc(ed.line[:ed.dot], unicode.IsSpace)
188 // The case of left == "" is handled as well.
189 r, _ := utf8.DecodeLastRuneInString(left)
190 if isAlnum(r) {
191 left = strings.TrimRightFunc(left, isAlnum)
192 } else {
193 left = strings.TrimRightFunc(
194 left, func(r rune) bool { return !isAlnum(r) })
195 }
196 ed.line = left + ed.line[ed.dot:]
197 ed.dot = len(left)
198 }
199
200 func isAlnum(r rune) bool {
201 return unicode.IsLetter(r) || unicode.IsNumber(r)
202 }
203
204 func killRuneLeft(ed *Editor) {
205 if ed.dot > 0 {
206 _, w := utf8.DecodeLastRuneInString(ed.line[:ed.dot])
207 ed.line = ed.line[:ed.dot-w] + ed.line[ed.dot:]
208 ed.dot -= w
209 } else {
210 ed.flash()
211 }
212 }
213
214 func killRuneRight(ed *Editor) {
215 if ed.dot < len(ed.line) {
216 _, w := utf8.DecodeRuneInString(ed.line[ed.dot:])
217 ed.line = ed.line[:ed.dot] + ed.line[ed.dot+w:]
218 } else {
219 ed.flash()
220 }
221 }
222
223 func moveDotLeft(ed *Editor) {
224 _, w := utf8.DecodeLastRuneInString(ed.line[:ed.dot])
225 ed.dot -= w
226 }
227
228 func moveDotRight(ed *Editor) {
229 _, w := utf8.DecodeRuneInString(ed.line[ed.dot:])
230 ed.dot += w
231 }
232
233 func moveDotLeftWord(ed *Editor) {
234 if ed.dot == 0 {
235 return
236 }
237 space := strings.LastIndexFunc(
238 strings.TrimRightFunc(ed.line[:ed.dot], unicode.IsSpace),
239 unicode.IsSpace) + 1
240 ed.dot = space
241 }
242
243 func moveDotRightWord(ed *Editor) {
244 // Move to first space
245 p := strings.IndexFunc(ed.line[ed.dot:], unicode.IsSpace)
246 if p == -1 {
247 ed.dot = len(ed.line)
248 return
249 }
250 ed.dot += p
251 // Move to first nonspace
252 p = strings.IndexFunc(ed.line[ed.dot:], notSpace)
253 if p == -1 {
254 ed.dot = len(ed.line)
255 return
256 }
257 ed.dot += p
258 }
259
260 func notSpace(r rune) bool {
261 return !unicode.IsSpace(r)
262 }
263
264 func moveDotSOL(ed *Editor) {
265 sol := util.FindLastSOL(ed.line[:ed.dot])
266 ed.dot = sol
267 }
268
269 func moveDotEOL(ed *Editor) {
270 eol := util.FindFirstEOL(ed.line[ed.dot:]) + ed.dot
271 ed.dot = eol
272 }
273
274 func moveDotUp(ed *Editor) {
275 sol := util.FindLastSOL(ed.line[:ed.dot])
276 if sol == 0 {
277 ed.flash()
278 return
279 }
280 prevEOL := sol - 1
281 prevSOL := util.FindLastSOL(ed.line[:prevEOL])
282 width := util.Wcswidth(ed.line[sol:ed.dot])
283 ed.dot = prevSOL + len(util.TrimWcwidth(ed.line[prevSOL:prevEOL], width))
284 }
285
286 func moveDotDown(ed *Editor) {
287 eol := util.FindFirstEOL(ed.line[ed.dot:]) + ed.dot
288 if eol == len(ed.line) {
289 ed.flash()
290 return
291 }
292 nextSOL := eol + 1
293 nextEOL := util.FindFirstEOL(ed.line[nextSOL:]) + nextSOL
294 sol := util.FindLastSOL(ed.line[:ed.dot])
295 width := util.Wcswidth(ed.line[sol:ed.dot])
296 ed.dot = nextSOL + len(util.TrimWcwidth(ed.line[nextSOL:nextEOL], width))
297 }
298
299 func insertLastWord(ed *Editor) {
300 if ed.daemon == nil {
301 ed.addTip("daemon offline")
302 return
303 }
304 _, cmd, err := ed.daemon.PrevCmd(-1, "")
305 if err == nil {
306 ed.insertAtDot(lastWord(cmd))
307 } else {
308 ed.addTip("db error: %s", err.Error())
309 }
310 }
311
312 func lastWord(s string) string {
313 words := wordify(s)
314 if len(words) == 0 {
315 return ""
316 }
317 return words[len(words)-1]
318 }
319
320 func insertKey(ed *Editor) {
321 k := ed.lastKey
322 ed.insertAtDot(string(k.Rune))
323 }
324
325 func returnLine(ed *Editor) {
326 ed.nextAction = action{typ: exitReadLine, returnLine: ed.line}
327 }
328
329 func smartEnter(ed *Editor) {
330 if ed.parseErrorAtEnd {
331 // There is a parsing error at the end. ui.Insert a newline and copy
332 // indents from previous line.
333 indent := findLastIndent(ed.line[:ed.dot])
334 ed.insertAtDot("\n" + indent)
335 } else {
336 returnLine(ed)
337 }
338 }
339
340 func findLastIndent(s string) string {
341 line := s[util.FindLastSOL(s):]
342 trimmed := strings.TrimLeft(line, " \t")
343 return line[:len(line)-len(trimmed)]
344 }
345
346 func returnEOF(ed *Editor) {
347 if len(ed.line) == 0 {
348 ed.nextAction = action{typ: exitReadLine, returnErr: io.EOF}
349 }
350 }
351
352 func toggleQuotePaste(ed *Editor) {
353 ed.insert.quotePaste = !ed.insert.quotePaste
354 }
355
356 func endOfHistory(ed *Editor) {
357 ed.Notify("End of history")
358 }
359
360 func redraw(ed *Editor) {
361 ed.refresh(true, true)
362 }
363
364 func insertDefault(ed *Editor) {
365 k := ed.lastKey
366 if likeChar(k) {
367 insertKey(ed)
368 // Match abbreviations.
369 expanded := false
370 literals := ed.line[ed.dot-ed.insert.literalInserts-1 : ed.dot]
371 ed.abbrIterate(func(abbr, full string) bool {
372 if strings.HasSuffix(literals, abbr) {
373 ed.line = ed.line[:ed.dot-len(abbr)] + full + ed.line[ed.dot:]
374 ed.dot += len(full) - len(abbr)
375 expanded = true
376 return false
377 }
378 return true
379 })
380 // No match.
381 if !expanded {
382 ed.insert.insertedLiteral = true
383 }
384 } else {
385 ed.Notify("Unbound: %s", k)
386 }
387 }
388
389 // likeChar returns if a key looks like a character meant to be input (as
390 // opposed to a function key).
391 func likeChar(k ui.Key) bool {
392 return k.Mod == 0 && k.Rune > 0 && unicode.IsGraphic(k.Rune)
393 }
394
395 func commandDefault(ed *Editor) {
396 k := ed.lastKey
397 ed.Notify("Unbound: %s", k)
398 }
0 package edit
1
2 import (
3 "fmt"
4 "strconv"
5 "strings"
6
7 "github.com/elves/elvish/edit/ui"
8 )
9
10 // LastCmd mode.
11
12 var _ = registerBuiltins(modeLastCmd, map[string]func(*Editor){
13 "start": lastcmdStart,
14 "alt-default": lastcmdAltDefault,
15 })
16
17 func init() {
18 registerBindings(modeLastCmd, modeLastCmd, map[ui.Key]string{
19 ui.Default: "alt-default",
20 })
21 }
22
23 type lastcmdEntry struct {
24 i int
25 s string
26 }
27
28 type lastcmd struct {
29 line string
30 words []string
31 filtered []lastcmdEntry
32 minus bool
33 }
34
35 func newLastCmd(line string) *listing {
36 b := &lastcmd{line, wordify(line), nil, false}
37 l := newListing(modeLastCmd, b)
38 return &l
39 }
40
41 func (b *lastcmd) ModeTitle(int) string {
42 return " LASTCMD "
43 }
44
45 func (b *lastcmd) Len() int {
46 return len(b.filtered)
47 }
48
49 func (b *lastcmd) Show(i int) (string, ui.Styled) {
50 entry := b.filtered[i]
51 var head string
52 if entry.i == -1 {
53 head = "M-1"
54 } else if b.minus {
55 head = fmt.Sprintf("%d", entry.i-len(b.words))
56 } else {
57 head = fmt.Sprintf("%d", entry.i)
58 }
59 return head, ui.Unstyled(entry.s)
60 }
61
62 func (b *lastcmd) Filter(filter string) int {
63 b.filtered = nil
64 b.minus = len(filter) > 0 && filter[0] == '-'
65 if filter == "" || filter == "-" {
66 b.filtered = append(b.filtered, lastcmdEntry{-1, b.line})
67 } else if _, err := strconv.Atoi(filter); err != nil {
68 return -1
69 }
70 // Quite inefficient way to filter by prefix of stringified index.
71 n := len(b.words)
72 for i, word := range b.words {
73 if filter == "" ||
74 (!b.minus && strings.HasPrefix(strconv.Itoa(i), filter)) ||
75 (b.minus && strings.HasPrefix(strconv.Itoa(i-n), filter)) {
76 b.filtered = append(b.filtered, lastcmdEntry{i, word})
77 }
78 }
79 if len(b.filtered) == 0 {
80 return -1
81 }
82 return 0
83 }
84
85 // Editor interface.
86
87 func (b *lastcmd) Accept(i int, ed *Editor) {
88 ed.insertAtDot(b.filtered[i].s)
89 insertStart(ed)
90 }
91
92 func lastcmdStart(ed *Editor) {
93 logger.Println("lastcmd-alt-start")
94 _, cmd, err := ed.daemon.PrevCmd(-1, "")
95 if err != nil {
96 ed.Notify("db error: %s", err.Error())
97 return
98 }
99 ed.mode = newLastCmd(cmd)
100 }
101
102 func lastcmdAltDefault(ed *Editor) {
103 l, lc := getLastcmd(ed)
104 if l == nil {
105 return
106 }
107 logger.Println("lastcmd-alt-default")
108 if ed.lastKey == (ui.Key{'1', ui.Alt}) {
109 lc.Accept(0, ed)
110 logger.Println("accepting")
111 } else if l.handleFilterKey(ed.lastKey) {
112 if lc.Len() == 1 {
113 lc.Accept(l.selected, ed)
114 logger.Println("accepting")
115 }
116 } else {
117 insertStart(ed)
118 ed.nextAction = action{typ: reprocessKey}
119 }
120 }
121
122 func getLastcmd(ed *Editor) (*listing, *lastcmd) {
123 if l, ok := ed.mode.(*listing); ok {
124 if lc, ok := l.provider.(*lastcmd); ok {
125 return l, lc
126 }
127 }
128 return nil, nil
129 }
0 package edit
1
2 import (
3 "testing"
4
5 "github.com/elves/elvish/edit/ui"
6 )
7
8 var (
9 theLine = "qw search 'foo bar ~y'"
10 theLastCmd = newLastCmd(theLine)
11
12 lastcmdFilterTests = []listingFilterTestCases{
13 {"", []shown{
14 {"M-1", ui.Unstyled(theLine)},
15 {"0", ui.Unstyled("qw")},
16 {"1", ui.Unstyled("search")},
17 {"2", ui.Unstyled("'foo bar ~y'")}}},
18 {"1", []shown{{"1", ui.Unstyled("search")}}},
19 {"-", []shown{
20 {"M-1", ui.Unstyled(theLine)},
21 {"-3", ui.Unstyled("qw")},
22 {"-2", ui.Unstyled("search")},
23 {"-1", ui.Unstyled("'foo bar ~y'")}}},
24 {"-1", []shown{{"-1", ui.Unstyled("'foo bar ~y'")}}},
25 }
26 )
27
28 func TestLastCmd(t *testing.T) {
29 testListingFilter(t, "theLastCmd", theLastCmd, lastcmdFilterTests)
30 }
0 package edit
1
2 import (
3 "container/list"
4 "errors"
5 "fmt"
6 "strings"
7 "unicode/utf8"
8
9 "github.com/elves/elvish/edit/ui"
10 "github.com/elves/elvish/eval"
11 "github.com/elves/elvish/util"
12 )
13
14 var _ = registerBuiltins(modeListing, map[string]func(*Editor){
15 "up": func(ed *Editor) { getListing(ed).up(false) },
16 "up-cycle": func(ed *Editor) { getListing(ed).up(true) },
17 "page-up": func(ed *Editor) { getListing(ed).pageUp() },
18 "down": func(ed *Editor) { getListing(ed).down(false) },
19 "down-cycle": func(ed *Editor) { getListing(ed).down(true) },
20 "page-down": func(ed *Editor) { getListing(ed).pageDown() },
21 "backspace": func(ed *Editor) { getListing(ed).backspace() },
22 "accept": func(ed *Editor) { getListing(ed).accept(ed) },
23 "accept-close": func(ed *Editor) {
24 getListing(ed).accept(ed)
25 insertStart(ed)
26 },
27 "default": func(ed *Editor) { getListing(ed).defaultBinding(ed) },
28 })
29
30 func init() {
31 registerBindings(modeListing, modeListing, map[ui.Key]string{
32 ui.Key{ui.Up, 0}: "up",
33 ui.Key{ui.PageUp, 0}: "page-up",
34 ui.Key{ui.Down, 0}: "down",
35 ui.Key{ui.PageDown, 0}: "page-down",
36 ui.Key{ui.Tab, 0}: "down-cycle",
37 ui.Key{ui.Tab, ui.Shift}: "up-cycle",
38 ui.Key{ui.Backspace, 0}: "backspace",
39 ui.Key{ui.Enter, 0}: "accept-close",
40 ui.Key{ui.Enter, ui.Alt}: "accept",
41 ui.Default: "default",
42 ui.Key{'[', ui.Ctrl}: "insert:start",
43 })
44 }
45
46 // listing implements a listing mode that supports the notion of selecting an
47 // entry and filtering entries.
48 type listing struct {
49 name string
50 provider listingProvider
51 selected int
52 filter string
53 pagesize int
54 headerWidth int
55 }
56
57 type listingProvider interface {
58 Len() int
59 Show(i int) (string, ui.Styled)
60 Filter(filter string) int
61 Accept(i int, ed *Editor)
62 ModeTitle(int) string
63 }
64
65 type Placeholderer interface {
66 Placeholder() string
67 }
68
69 func newListing(t string, p listingProvider) listing {
70 l := listing{t, p, 0, "", 0, 0}
71 l.changeFilter("")
72 for i := 0; i < p.Len(); i++ {
73 header, _ := p.Show(i)
74 width := util.Wcswidth(header)
75 if l.headerWidth < width {
76 l.headerWidth = width
77 }
78 }
79 return l
80 }
81
82 func (l *listing) Binding(k ui.Key) eval.CallableValue {
83 specificBindings := keyBindings[l.name]
84 if specificBindings == nil {
85 return getBinding(modeListing, k)
86 }
87 listingBindings := keyBindings[modeListing]
88 // mode-specific binding -> listing binding ->
89 // mode-specific default -> listing default
90 if v, ok := specificBindings[k]; ok {
91 return v
92 }
93 if v, ok := listingBindings[k]; ok {
94 return v
95 }
96 if v, ok := specificBindings[ui.Default]; ok {
97 return v
98 }
99 return listingBindings[ui.Default]
100 }
101
102 func (l *listing) ModeLine() renderer {
103 return modeLineRenderer{l.provider.ModeTitle(l.selected), l.filter}
104 }
105
106 func (l *listing) CursorOnModeLine() bool {
107 if c, ok := l.provider.(CursorOnModeLiner); ok {
108 return c.CursorOnModeLine()
109 }
110 return false
111 }
112
113 func (l *listing) List(maxHeight int) renderer {
114 n := l.provider.Len()
115 if n == 0 {
116 var ph string
117 if pher, ok := l.provider.(Placeholderer); ok {
118 ph = pher.Placeholder()
119 } else {
120 ph = "(no result)"
121 }
122 return placeholderRenderer(ph)
123 }
124
125 // Collect the entries to show. We start from the selected entry and extend
126 // in both directions alternatingly. The entries are split into lines and
127 // then collected in a list.
128 low := l.selected
129 if low == -1 {
130 low = 0
131 }
132 high := low
133 height := 0
134 var listOfLines list.List
135 getEntry := func(i int) []ui.Styled {
136 header, content := l.provider.Show(i)
137 lines := strings.Split(content.Text, "\n")
138 styles := content.Styles
139 if i == l.selected {
140 styles = append(styles, styleForSelected...)
141 }
142 styleds := make([]ui.Styled, len(lines))
143 for i, line := range lines {
144 if l.headerWidth > 0 {
145 if i == 0 {
146 line = fmt.Sprintf("%*s %s", l.headerWidth, header, line)
147 } else {
148 line = fmt.Sprintf("%*s %s", l.headerWidth, "", line)
149 }
150 }
151 styleds[i] = ui.Styled{line, styles}
152 }
153 return styleds
154 }
155 // We start by extending high, so that the first entry to include is
156 // l.selected.
157 extendLow := false
158 lastShownIncomplete := false
159 for height < maxHeight && !(low == 0 && high == n) {
160 var i int
161 if (extendLow && low > 0) || high == n {
162 low--
163
164 entry := getEntry(low)
165 // Prepend at most the last (height - maxHeight) lines.
166 for i = len(entry) - 1; i >= 0 && height < maxHeight; i-- {
167 listOfLines.PushFront(entry[i])
168 height++
169 }
170 if i >= 0 {
171 lastShownIncomplete = true
172 }
173 } else {
174 entry := getEntry(high)
175 // Append at most the first (height - maxHeight) lines.
176 for i = 0; i < len(entry) && height < maxHeight; i++ {
177 listOfLines.PushBack(entry[i])
178 height++
179 }
180 if i < len(entry) {
181 lastShownIncomplete = true
182 }
183
184 high++
185 }
186 extendLow = !extendLow
187 }
188
189 l.pagesize = high - low
190
191 // Convert the List to a slice.
192 lines := make([]ui.Styled, 0, listOfLines.Len())
193 for p := listOfLines.Front(); p != nil; p = p.Next() {
194 lines = append(lines, p.Value.(ui.Styled))
195 }
196
197 ls := listingRenderer{lines}
198 if low > 0 || high < n || lastShownIncomplete {
199 // Need scrollbar
200 return listingWithScrollBarRenderer{ls, n, low, high, height}
201 }
202 return ls
203 }
204
205 func writeHorizontalScrollbar(b *buffer, n, low, high, width int) {
206 slow, shigh := findScrollInterval(n, low, high, width)
207 for i := 0; i < width; i++ {
208 if slow <= i && i < shigh {
209 b.write(' ', styleForScrollBarThumb.String())
210 } else {
211 b.write('━', styleForScrollBarArea.String())
212 }
213 }
214 }
215
216 func renderScrollbar(n, low, high, height int) *buffer {
217 slow, shigh := findScrollInterval(n, low, high, height)
218 // Logger.Printf("low = %d, high = %d, n = %d, slow = %d, shigh = %d", low, high, n, slow, shigh)
219 b := newBuffer(1)
220 for i := 0; i < height; i++ {
221 if i > 0 {
222 b.newline()
223 }
224 if slow <= i && i < shigh {
225 b.write(' ', styleForScrollBarThumb.String())
226 } else {
227 b.write('│', styleForScrollBarArea.String())
228 }
229 }
230 return b
231 }
232
233 func findScrollInterval(n, low, high, height int) (int, int) {
234 f := func(i int) int {
235 return int(float64(i)/float64(n)*float64(height) + 0.5)
236 }
237 scrollLow, scrollHigh := f(low), f(high)
238 if scrollLow == scrollHigh {
239 if scrollHigh == high {
240 scrollLow--
241 } else {
242 scrollHigh++
243 }
244 }
245 return scrollLow, scrollHigh
246 }
247
248 func (l *listing) changeFilter(newfilter string) {
249 l.filter = newfilter
250 l.selected = l.provider.Filter(newfilter)
251 }
252
253 func (l *listing) backspace() bool {
254 _, size := utf8.DecodeLastRuneInString(l.filter)
255 if size > 0 {
256 l.changeFilter(l.filter[:len(l.filter)-size])
257 return true
258 }
259 return false
260 }
261
262 func (l *listing) up(cycle bool) {
263 n := l.provider.Len()
264 if n == 0 {
265 return
266 }
267 l.selected--
268 if l.selected == -1 {
269 if cycle {
270 l.selected += n
271 } else {
272 l.selected++
273 }
274 }
275 }
276
277 func (l *listing) pageUp() {
278 n := l.provider.Len()
279 if n == 0 {
280 return
281 }
282 l.selected -= l.pagesize
283 if l.selected < 0 {
284 l.selected = 0
285 }
286 }
287
288 func (l *listing) down(cycle bool) {
289 n := l.provider.Len()
290 if n == 0 {
291 return
292 }
293 l.selected++
294 if l.selected == n {
295 if cycle {
296 l.selected -= n
297 } else {
298 l.selected--
299 }
300 }
301 }
302
303 func (l *listing) pageDown() {
304 n := l.provider.Len()
305 if n == 0 {
306 return
307 }
308 l.selected += l.pagesize
309 if l.selected >= n {
310 l.selected = n - 1
311 }
312 }
313
314 func (l *listing) accept(ed *Editor) {
315 if l.selected >= 0 {
316 l.provider.Accept(l.selected, ed)
317 }
318 }
319
320 func (l *listing) handleFilterKey(k ui.Key) bool {
321 if likeChar(k) {
322 l.changeFilter(l.filter + string(k.Rune))
323 return true
324 }
325 return false
326 }
327
328 func (l *listing) defaultBinding(ed *Editor) {
329 if !l.handleFilterKey(ed.lastKey) {
330 insertStart(ed)
331 ed.nextAction = action{typ: reprocessKey}
332 }
333 }
334
335 var errNotListing = errors.New("not in a listing mode")
336
337 func getListing(ed *Editor) *listing {
338 if l, ok := ed.mode.(*listing); ok {
339 return l
340 } else {
341 throw(errNotListing)
342 panic("unreachable")
343 }
344 }
0 package edit
1
2 import (
3 "reflect"
4 "testing"
5
6 "github.com/elves/elvish/edit/ui"
7 )
8
9 type shown struct {
10 header string
11 content ui.Styled
12 }
13
14 type listingFilterTestCases struct {
15 filter string
16 wantShowns []shown
17 }
18
19 func testListingFilter(t *testing.T, name string, l *listing, testcases []listingFilterTestCases) {
20 ls := l.provider
21 for _, testcase := range testcases {
22 ls.Filter(testcase.filter)
23
24 l := ls.Len()
25 if l != len(testcase.wantShowns) {
26 t.Errorf("%s.Len() -> %d, want %d (filter was %q)",
27 name, l, len(testcase.wantShowns), testcase.filter)
28 }
29 for i, want := range testcase.wantShowns {
30 header, content := ls.Show(i)
31 if header != want.header || !reflect.DeepEqual(content, want.content) {
32 t.Errorf("%s.Show(%d) => (%v, %v), want (%v, %v) (filter was %q)",
33 name, i, header, content, want.header, want.content, testcase.filter)
34 }
35 }
36 }
37 }
0 package edit
1
2 import (
3 "fmt"
4 "reflect"
5 "strconv"
6 "testing"
7
8 "github.com/elves/elvish/edit/ui"
9 )
10
11 type provider struct {
12 elems []string
13 accepted int
14 }
15
16 func (p provider) Len() int { return len(p.elems) }
17 func (p provider) Filter(string) int { return 0 }
18 func (p provider) Accept(i int, ed *Editor) { p.accepted = i }
19 func (p provider) ModeTitle(i int) string { return fmt.Sprintf("test %d", i) }
20
21 func (p provider) Show(i int) (string, ui.Styled) {
22 return strconv.Itoa(i), ui.Unstyled(p.elems[i])
23 }
24
25 var (
26 mode = "test233"
27 p = provider{[]string{"foo", "bar", "foobar", "lorem", "ipsum"}, -1}
28 ls = newListing(mode, p)
29 )
30
31 func TestListing(t *testing.T) {
32 wantedModeLine := modeLineRenderer{"test 0", ""}
33 if modeLine := ls.ModeLine(); modeLine != wantedModeLine {
34 t.Errorf("ls.ModeLine() = %v, want %v", modeLine, wantedModeLine)
35 }
36
37 // Selecting the first element and rendering with height=2. We expect to see
38 // the first 2 elements, with the first being shown as selected.
39 testListingList(t, 0, 2, listingWithScrollBarRenderer{
40 listingRenderer: listingRenderer{[]ui.Styled{
41 ui.Styled{"0 foo", styleForSelected},
42 ui.Styled{"1 bar", ui.Styles{}},
43 }},
44 n: 5, low: 0, high: 2, height: 2,
45 })
46 // Selecting the last element and rendering with height=2. We expect to see
47 // the last 2 elements, with the last being shown as selected.
48 testListingList(t, 4, 2, listingWithScrollBarRenderer{
49 listingRenderer: listingRenderer{[]ui.Styled{
50 ui.Styled{"3 lorem", ui.Styles{}},
51 ui.Styled{"4 ipsum", styleForSelected},
52 }},
53 n: 5, low: 3, high: 5, height: 2,
54 })
55 // Selecting the middle element and rendering with height=3. We expect to
56 // see the middle element and two elements around it, with the middle being
57 // shown as selected.
58 testListingList(t, 2, 3, listingWithScrollBarRenderer{
59 listingRenderer: listingRenderer{[]ui.Styled{
60 ui.Styled{"1 bar", ui.Styles{}},
61 ui.Styled{"2 foobar", styleForSelected},
62 ui.Styled{"3 lorem", ui.Styles{}},
63 }},
64 n: 5, low: 1, high: 4, height: 3,
65 })
66 }
67
68 func testListingList(t *testing.T, i, h int, want renderer) {
69 ls.selected = i
70 if r := ls.List(h); !reflect.DeepEqual(r, want) {
71 t.Errorf("selecting %d, ls.List(%d) = %v, want %v", i, h, r, want)
72 }
73 }
0 package edit
1
2 import (
3 "bytes"
4 "fmt"
5 "math"
6 "os"
7 "path/filepath"
8 "regexp"
9 "strings"
10
11 "github.com/elves/elvish/edit/ui"
12 "github.com/elves/elvish/eval"
13 "github.com/elves/elvish/parse"
14 "github.com/elves/elvish/store/storedefs"
15 "github.com/elves/elvish/util"
16 )
17
18 // Location mode.
19
20 var _ = registerBuiltins(modeLocation, map[string]func(*Editor){
21 "start": locStart,
22 })
23
24 func init() {
25 registerBindings(modeLocation, modeLocation, map[ui.Key]string{})
26 }
27
28 // PinnedScore is a special value of Score in storedefs.Dir to represent that the
29 // directory is pinned.
30 var PinnedScore = math.Inf(1)
31
32 type location struct {
33 home string // The home directory; leave empty if unknown.
34 all []storedefs.Dir
35 filtered []storedefs.Dir
36 }
37
38 func newLocation(dirs []storedefs.Dir, home string) *listing {
39 l := newListing(modeLocation, &location{all: dirs, home: home})
40 return &l
41 }
42
43 func (loc *location) ModeTitle(i int) string {
44 return " LOCATION "
45 }
46
47 func (*location) CursorOnModeLine() bool {
48 return true
49 }
50
51 func (loc *location) Len() int {
52 return len(loc.filtered)
53 }
54
55 func (loc *location) Show(i int) (string, ui.Styled) {
56 var header string
57 score := loc.filtered[i].Score
58 if score == PinnedScore {
59 header = "*"
60 } else {
61 header = fmt.Sprintf("%.0f", score)
62 }
63 return header, ui.Unstyled(showPath(loc.filtered[i].Path, loc.home))
64 }
65
66 func (loc *location) Filter(filter string) int {
67 loc.filtered = nil
68 pattern := makeLocationFilterPattern(filter)
69 for _, item := range loc.all {
70 if pattern.MatchString(showPath(item.Path, loc.home)) {
71 loc.filtered = append(loc.filtered, item)
72 }
73 }
74
75 if len(loc.filtered) == 0 {
76 return -1
77 }
78 return 0
79 }
80
81 func showPath(path, home string) string {
82 if home != "" && path == home {
83 return "~"
84 } else if home != "" && strings.HasPrefix(path, home+"/") {
85 return "~/" + parse.Quote(path[len(home)+1:])
86 } else {
87 return parse.Quote(path)
88 }
89 }
90
91 var emptyRegexp = regexp.MustCompile("")
92
93 func makeLocationFilterPattern(s string) *regexp.Regexp {
94 var b bytes.Buffer
95 b.WriteString(".*")
96 segs := strings.Split(s, "/")
97 for i, seg := range segs {
98 if i > 0 {
99 b.WriteString(".*/.*")
100 }
101 b.WriteString(regexp.QuoteMeta(seg))
102 }
103 b.WriteString(".*")
104 p, err := regexp.Compile(b.String())
105 if err != nil {
106 logger.Printf("failed to compile regexp %q: %v", b.String(), err)
107 return emptyRegexp
108 }
109 return p
110 }
111
112 func (ed *Editor) chdir(dir string) error {
113 dir, err := filepath.Abs(dir)
114 if err != nil {
115 return err
116 }
117 err = os.Chdir(dir)
118 if err == nil {
119 store := ed.daemon
120 store.Waits().Add(1)
121 go func() {
122 // XXX Error ignored.
123 store.AddDir(dir, 1)
124 store.Waits().Done()
125 logger.Println("added dir to store:", dir)
126 }()
127 }
128 return err
129 }
130
131 // Editor interface.
132
133 func (loc *location) Accept(i int, ed *Editor) {
134 err := ed.chdir(loc.filtered[i].Path)
135 if err != nil {
136 ed.Notify("%v", err)
137 }
138 ed.mode = &ed.insert
139 }
140
141 func locStart(ed *Editor) {
142 if ed.daemon == nil {
143 ed.Notify("%v", ErrStoreOffline)
144 return
145 }
146 black := convertListToSet(ed.locHidden())
147 dirs, err := ed.daemon.Dirs(black)
148 if err != nil {
149 ed.Notify("store error: %v", err)
150 return
151 }
152
153 pinnedValue := ed.locPinned()
154 pinned := convertListToDirs(pinnedValue)
155 pinnedSet := convertListToSet(pinnedValue)
156
157 // TODO(xiaq): Optimize this by changing GetDirs to a callback API, and
158 // build dirs by first putting pinned directories and then appending those
159 // from store.
160 for _, d := range dirs {
161 _, inPinned := pinnedSet[d.Path]
162 if !inPinned {
163 pinned = append(pinned, d)
164 }
165 }
166 dirs = pinned
167
168 // Drop the error. When there is an error, home is "", which is used to
169 // signify "no home known" in location.
170 home, _ := util.GetHome("")
171 ed.mode = newLocation(dirs, home)
172 }
173
174 // convertListToDirs converts a list of strings to []storedefs.Dir. It uses the
175 // special score of PinnedScore to signify that the directory is pinned.
176 func convertListToDirs(li eval.List) []storedefs.Dir {
177 pinned := make([]storedefs.Dir, 0, li.Len())
178 // XXX(xiaq): silently drops non-string items.
179 li.Iterate(func(v eval.Value) bool {
180 if s, ok := v.(eval.String); ok {
181 pinned = append(pinned, storedefs.Dir{string(s), PinnedScore})
182 }
183 return true
184 })
185 return pinned
186 }
187
188 func convertListToSet(li eval.List) map[string]struct{} {
189 set := make(map[string]struct{})
190 // XXX(xiaq): silently drops non-string items.
191 li.Iterate(func(v eval.Value) bool {
192 if s, ok := v.(eval.String); ok {
193 set[string(s)] = struct{}{}
194 }
195 return true
196 })
197 return set
198 }
199
200 // Variables.
201
202 var _ = registerVariable("loc-hidden", func() eval.Variable {
203 return eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList)
204 })
205
206 func (ed *Editor) locHidden() eval.List {
207 return ed.variables["loc-hidden"].Get().(eval.List)
208 }
209
210 var _ = registerVariable("loc-pinned", func() eval.Variable {
211 return eval.NewPtrVariableWithValidator(eval.NewList(), eval.ShouldBeList)
212 })
213
214 func (ed *Editor) locPinned() eval.List {
215 return ed.variables["loc-pinned"].Get().(eval.List)
216 }
0 package edit
1
2 import (
3 "testing"
4
5 "github.com/elves/elvish/edit/ui"
6 "github.com/elves/elvish/store/storedefs"
7 )
8
9 var (
10 theLocation = newLocation([]storedefs.Dir{
11 {"/pinned", PinnedScore},
12 {"/src/github.com/elves/elvish", 300},
13 {"/src/home/xyz", 233},
14 {"/home/dir", 100},
15 {"/foo/\nbar", 77},
16 {"/usr/elves/elvish", 6},
17 }, "/home")
18
19 locationFilterTests = []listingFilterTestCases{
20 {"", []shown{
21 {"*", ui.Unstyled("/pinned")},
22 {"300", ui.Unstyled("/src/github.com/elves/elvish")},
23 {"233", ui.Unstyled("/src/home/xyz")},
24 {"100", ui.Unstyled("~/dir")}, // home is abbreviated
25 {"77", ui.Unstyled(`"/foo/\nbar"`)}, // special char is quoted
26 {"6", ui.Unstyled("/usr/elves/elvish")}}},
27 {"/s", []shown{
28 {"300", ui.Unstyled("/src/github.com/elves/elvish")},
29 {"233", ui.Unstyled("/src/home/xyz")},
30 {"6", ui.Unstyled("/usr/elves/elvish")}}},
31 {"/e/e", []shown{
32 {"300", ui.Unstyled("/src/github.com/elves/elvish")},
33 {"6", ui.Unstyled("/usr/elves/elvish")}}},
34 {"x", []shown{{"233", ui.Unstyled("/src/home/xyz")}}},
35 // Matchers operate on the displayed text, not the actual path.
36 // 1. Home directory is abbreviated to ~, and is matched by ~, but not by
37 // the actual path.
38 {"~", []shown{{"100", ui.Unstyled("~/dir")}}},
39 {"home", []shown{{"233", ui.Unstyled("/src/home/xyz")}}},
40 // 2. Special characters are quoted, and are matched by the quoted form,
41 // not by the actual form.
42 {"\n", []shown{}},
43 {"\\n", []shown{{"77", ui.Unstyled(`"/foo/\nbar"`)}}},
44 }
45 )
46
47 func TestLocation(t *testing.T) {
48 testListingFilter(t, "theLocation", theLocation, locationFilterTests)
49 }
0 package edit
1
2 //go:generate stringer -type=fileFeature -output=ls-colors_string.go
3
4 import (
5 "os"
6 "path"
7 "strings"
8 "sync"
9 "syscall"
10 )
11
12 // Color files based on their various features.
13 //
14 // This is a reverse-engineered implementation of the parsing and
15 // interpretation of the LS_COLORS environmental variable used by GNU
16 // coreutils.
17
18 type fileFeature int
19
20 const (
21 featureInvalid fileFeature = iota
22
23 featureOrphanedSymlink
24 featureSymlink
25
26 featureMultiHardLink
27
28 featureNamedPipe
29 featureSocket
30 featureDoor
31 featureBlockDevice
32 featureCharDevice
33
34 featureWorldWritableStickyDirectory
35 featureWorldWritableDirectory
36 featureStickyDirectory
37 featureDirectory
38
39 featureCapability
40
41 featureSetuid
42 featureSetgid
43 featureExecutable
44
45 featureRegular
46 )
47
48 var featureForName = map[string]fileFeature{
49 "rs": featureRegular,
50 "di": featureDirectory,
51 "ln": featureSymlink,
52 "mh": featureMultiHardLink,
53 "pi": featureNamedPipe,
54 "so": featureSocket,
55 "do": featureDoor,
56 "bd": featureBlockDevice,
57 "cd": featureCharDevice,
58 "or": featureOrphanedSymlink,
59 "su": featureSetuid,
60 "sg": featureSetgid,
61 "ca": featureCapability,
62 "tw": featureWorldWritableStickyDirectory,
63 "ow": featureWorldWritableDirectory,
64 "st": featureStickyDirectory,
65 "ex": featureExecutable,
66 }
67
68 type lsColor struct {
69 styleForFeature map[fileFeature]string
70 styleForExt map[string]string
71 }
72
73 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:`
74
75 func getLsColorString() string {
76 lsColorString := os.Getenv("LS_COLORS")
77 if len(lsColorString) == 0 {
78 return defaultLsColorString
79 }
80 return lsColorString
81 }
82
83 var (
84 lastLsColor *lsColor
85 lastLsColorString string
86 lastLsColorMutex sync.Mutex
87 )
88
89 func init() {
90 lastLsColor = parseLsColor(defaultLsColorString)
91 }
92
93 func getLsColor() *lsColor {
94 lastLsColorMutex.Lock()
95 defer lastLsColorMutex.Unlock()
96
97 s := getLsColorString()
98 if lastLsColorString != s {
99 lastLsColorString = s
100 lastLsColor = parseLsColor(s)
101 }
102 return lastLsColor
103 }
104
105 // parseLsColor parses a string in the LS_COLORS format into lsColor. Erroneous
106 // fields are silently ignored.
107 func parseLsColor(s string) *lsColor {
108 lc := &lsColor{make(map[fileFeature]string), make(map[string]string)}
109 for _, spec := range strings.Split(s, ":") {
110 words := strings.Split(spec, "=")
111 if len(words) != 2 {
112 continue
113 }
114 key, value := words[0], words[1]
115 filterValues := []string{}
116 for _, splitValue := range strings.Split(value, ";") {
117 if strings.Count(splitValue, "0") == len(splitValue) {
118 continue
119 }
120 filterValues = append(filterValues, splitValue)
121 }
122 if len(filterValues) == 0 {
123 continue
124 }
125 value = strings.Join(filterValues, ";")
126 if strings.HasPrefix(key, "*.") {
127 lc.styleForExt[key[2:]] = value
128 } else {
129 feature, ok := featureForName[key]
130 if !ok {
131 continue
132 }
133 lc.styleForFeature[feature] = value
134 }
135 }
136 return lc
137 }
138
139 func is(u, p uint32) bool {
140 return u&p == p
141 }
142
143 // Weirdly, permission masks for group and other are missing on platforms other
144 // than linux, darwin and netbsd. So we replicate some of them here.
145 const (
146 S_IWOTH = 0x2 // Writable by other
147 S_IXGRP = 0x8 // Executable by group
148 S_IXOTH = 0x1 // Executable by other
149 )
150
151 func determineFeature(fname string, mh bool) (fileFeature, error) {
152 var stat syscall.Stat_t
153 err := syscall.Lstat(fname, &stat)
154 if err != nil {
155 return 0, err
156 }
157
158 // The type of syscall.Stat_t.Mode is uint32 on Linux and uint16 on Mac
159 m := (uint32)(stat.Mode)
160
161 // Symlink and OrphanedSymlink has highest precedence
162 if is(m, syscall.S_IFLNK) {
163 _, err := os.Stat(fname)
164 if err != nil {
165 return featureOrphanedSymlink, nil
166 }
167 return featureSymlink, nil
168 }
169
170 // featureMultiHardLink
171 if mh && stat.Nlink > 1 {
172 return featureMultiHardLink, nil
173 }
174
175 // type bits features
176 switch {
177 case is(m, syscall.S_IFIFO):
178 return featureNamedPipe, nil
179 case is(m, syscall.S_IFSOCK):
180 return featureSocket, nil
181 /*
182 case m | syscall.S_IFDOOR != 0:
183 return featureDoor, nil
184 */
185 case is(m, syscall.S_IFBLK):
186 return featureBlockDevice, nil
187 case is(m, syscall.S_IFCHR):
188 return featureCharDevice, nil
189 case is(m, syscall.S_IFDIR):
190 // Perm bits features for directory
191 switch {
192 case is(m, S_IWOTH|syscall.S_ISVTX):
193 return featureWorldWritableStickyDirectory, nil
194 case is(m, S_IWOTH):
195 return featureWorldWritableDirectory, nil
196 case is(m, syscall.S_ISVTX):
197 return featureStickyDirectory, nil
198 default:
199 return featureDirectory, nil
200 }
201 }
202
203 // TODO(xiaq): Support featureCapacity
204
205 // Perm bits features for regular files
206 switch {
207 case is(m, syscall.S_ISUID):
208 return featureSetuid, nil
209 case is(m, syscall.S_ISGID):
210 return featureSetgid, nil
211 case m&(syscall.S_IXUSR|S_IXGRP|S_IXOTH) != 0:
212 return featureExecutable, nil
213 }
214
215 // Check extension
216 return featureRegular, nil
217 }
218
219 func (lc *lsColor) getStyle(fname string) string {
220 mh := strings.Trim(lc.styleForFeature[featureMultiHardLink], "0") != ""
221 // TODO Handle error from determineFeature
222 feature, _ := determineFeature(fname, mh)
223 if feature == featureRegular {
224 if ext := path.Ext(fname); ext != "" {
225 if style, ok := lc.styleForExt[ext]; ok {
226 return style
227 }
228 }
229 }
230 return lc.styleForFeature[feature]
231 }
0 // Code generated by "stringer -type=fileFeature -output=ls-colors_string.go"; DO NOT EDIT.
1
2 package edit
3
4 import "fmt"
5
6 const _fileFeature_name = "featureInvalidfeatureOrphanedSymlinkfeatureSymlinkfeatureMultiHardLinkfeatureNamedPipefeatureSocketfeatureDoorfeatureBlockDevicefeatureCharDevicefeatureWorldWritableStickyDirectoryfeatureWorldWritableDirectoryfeatureStickyDirectoryfeatureDirectoryfeatureCapabilityfeatureSetuidfeatureSetgidfeatureExecutablefeatureRegular"
7
8 var _fileFeature_index = [...]uint16{0, 14, 36, 50, 70, 86, 99, 110, 128, 145, 180, 209, 231, 247, 264, 277, 290, 307, 321}
9
10 func (i fileFeature) String() string {
11 if i < 0 || i >= fileFeature(len(_fileFeature_index)-1) {
12 return fmt.Sprintf("fileFeature(%d)", i)
13 }
14 return _fileFeature_name[_fileFeature_index[i]:_fileFeature_index[i+1]]
15 }
0 package edit
1
2 import (
3 "os"
4 "testing"
5
6 "github.com/elves/elvish/util"
7 )
8
9 func create(fname string, perm os.FileMode) {
10 f, err := os.OpenFile(fname, os.O_CREATE, perm)
11 if err != nil {
12 panic(err)
13 }
14 f.Close()
15 }
16
17 func TestDetermineFeature(t *testing.T) {
18 test := func(fname string, mh bool, wantedFeature fileFeature) {
19 feature, err := determineFeature(fname, mh)
20 if err != nil {
21 t.Errorf("determineFeature(%q, %v) returns error %v, want no error",
22 fname, mh, err)
23 }
24 if feature != wantedFeature {
25 t.Errorf("determineFeature(%q, %v) returns feature %v, want %v",
26 fname, mh, feature, wantedFeature)
27 }
28 }
29
30 util.InTempDir(func(string) {
31
32 create("a", 0600)
33 // Regular file.
34 test("a", true, featureRegular)
35
36 // Symlink.
37 os.Symlink("a", "symlink")
38 test("symlink", true, featureSymlink)
39
40 // Broken symlink.
41 os.Symlink("aaaa", "bad-symlink")
42 test("bad-symlink", true, featureOrphanedSymlink)
43
44 // Multiple hard links.
45 os.Link("a", "a2")
46 test("a", true, featureMultiHardLink)
47
48 // Don't test for multiple hard links.
49 test("a", false, featureRegular)
50
51 // Setuid and Setgid.
52 // XXX(xiaq): Fails.
53 /*
54 create("su", os.ModeSetuid)
55 test("su", true, featureSetuid)
56 create("sg", os.ModeSetgid)
57 test("sg", true, featureSetgid)
58 */
59
60 // Executable.
61 create("xu", 0100)
62 create("xg", 0010)
63 create("xo", 0001)
64 test("xu", true, featureExecutable)
65 test("xg", true, featureExecutable)
66 test("xo", true, featureExecutable)
67 })
68 }
0 package edit
1
2 import (
3 "strings"
4
5 "github.com/elves/elvish/eval"
6 "github.com/elves/elvish/util"
7 )
8
9 var _ = registerVariable("-use-subseq-matcher", func() eval.Variable {
10 return eval.NewPtrVariableWithValidator(eval.Bool(false), eval.ShouldBeBool)
11 })
12
13 func (ed *Editor) useSubseqMatcher() bool {
14 return bool(ed.variables["-use-subseq-matcher"].Get().(eval.Bool).Bool())
15 }
16
17 func (ed *Editor) matcher() func(string, string) bool {
18 if ed.useSubseqMatcher() {
19 return util.HasSubseq
20 } else {
21 return strings.HasPrefix
22 }
23 }
0 package edit
1
2 func min(a, b int) int {
3 if a <= b {
4 return a
5 }
6 return b
7 }
8
9 func max(a, b int) int {
10 if a >= b {
11 return a
12 }
13 return b
14 }
0 package edit
1
2 import (
3 "github.com/elves/elvish/edit/ui"
4 "github.com/elves/elvish/eval"
5 )
6
7 // Names of modes, used for subnamespaces of le: and name of binding table in
8 // $le:binding.
9 const (
10 modeInsert = "insert"
11 modeRawInsert = "raw-insert"
12 modeCommand = "command"
13 modeCompletion = "completion"
14 modeNavigation = "navigation"
15 modeHistory = "history"
16 modeHistoryListing = "histlist"
17 modeLastCmd = "lastcmd"
18 modeLocation = "loc"
19 modeListing = "listing" // A "super mode" for histlist, lastcmd, loc
20 )
21
22 // Mode is an editor mode.
23 type Mode interface {
24 ModeLine() renderer
25 Binding(ui.Key) eval.CallableValue
26 }
27
28 // CursorOnModeLiner is an optional interface that modes can implement. If a
29 // mode does and the method returns true, the cursor is placed on the modeline
30 // when that mode is active.
31 type CursorOnModeLiner interface {
32 CursorOnModeLine() bool
33 }
34
35 // Lister is a mode with a listing.
36 type Lister interface {
37 List(maxHeight int) renderer
38 }
39
40 // ListRenderer is a mode with a listing that handles the rendering itself.
41 // NOTE(xiaq): This interface is being deprecated in favor of Lister.
42 type ListRenderer interface {
43 // ListRender renders the listing under the given constraint of width and
44 // maximum height. It returns a rendered buffer.
45 ListRender(width, maxHeight int) *buffer
46 }
0 package edit
1
2 import (
3 "errors"
4 "os"
5 "path"
6 "sort"
7 "strings"
8 "unicode/utf8"
9
10 "github.com/elves/elvish/edit/ui"
11 "github.com/elves/elvish/eval"
12 "github.com/elves/elvish/parse"
13 "github.com/elves/elvish/util"
14 )
15
16 // Navigation subsystem.
17
18 // Interface.
19
20 var _ = registerBuiltins("nav", map[string]func(*Editor){
21 "start": navStart,
22 "up": navUp,
23 "down": navDown,
24 "page-up": navPageUp,
25 "page-down": navPageDown,
26 "left": navLeft,
27 "right": navRight,
28 "trigger-shown-hidden": navTriggerShowHidden,
29 "trigger-filter": navTriggerFilter,
30 "insert-selected": navInsertSelected,
31 "insert-selected-and-quit": navInsertSelectedAndQuit,
32 "default": navDefault,
33 })
34
35 func init() {
36 registerBindings(modeNavigation, "nav", map[ui.Key]string{
37 {ui.Up, 0}: "up",
38 {ui.Down, 0}: "down",
39 {ui.PageUp, 0}: "page-up",
40 {ui.PageDown, 0}: "page-down",
41 {ui.Left, 0}: "left",
42 {ui.Right, 0}: "right",
43 {ui.Enter, ui.Alt}: "insert-selected",
44 {ui.Enter, 0}: "insert-selected-and-quit",
45 {'H', ui.Ctrl}: "trigger-shown-hidden",
46 {'F', ui.Ctrl}: "trigger-filter",
47 {'[', ui.Ctrl}: "insert:start",
48 ui.Default: "default",
49 })
50 }
51
52 type navigation struct {
53 current *navColumn
54 parent *navColumn
55 preview *navColumn
56 showHidden bool
57 filtering bool
58 filter string
59 chdir func(string) error
60 }
61
62 func (*navigation) Binding(k ui.Key) eval.CallableValue {
63 return getBinding(modeNavigation, k)
64 }
65
66 func (n *navigation) ModeLine() renderer {
67 title := " NAVIGATING "
68 if n.showHidden {
69 title += "(show hidden) "
70 }
71 return modeLineRenderer{title, n.filter}
72 }
73
74 func (n *navigation) CursorOnModeLine() bool {
75 return n.filtering
76 }
77
78 func navStart(ed *Editor) {
79 initNavigation(&ed.navigation, ed)
80 ed.mode = &ed.navigation
81 }
82
83 func navUp(ed *Editor) {
84 ed.navigation.prev()
85 }
86
87 func navDown(ed *Editor) {
88 ed.navigation.next()
89 }
90
91 func navPageUp(ed *Editor) {
92 ed.navigation.current.pageUp()
93 ed.navigation.refresh()
94 }
95
96 func navPageDown(ed *Editor) {
97 ed.navigation.current.pageDown()
98 ed.navigation.refresh()
99 }
100
101 func navLeft(ed *Editor) {
102 ed.navigation.ascend()
103 }
104
105 func navRight(ed *Editor) {
106 ed.navigation.descend()
107 }
108
109 func navTriggerShowHidden(ed *Editor) {
110 ed.navigation.showHidden = !ed.navigation.showHidden
111 ed.navigation.refresh()
112 }
113
114 func navTriggerFilter(ed *Editor) {
115 ed.navigation.filtering = !ed.navigation.filtering
116 }
117
118 func navInsertSelected(ed *Editor) {
119 ed.insertAtDot(parse.Quote(ed.navigation.current.selectedName()) + " ")
120 }
121
122 func navInsertSelectedAndQuit(ed *Editor) {
123 ed.insertAtDot(parse.Quote(ed.navigation.current.selectedName()) + " ")
124 ed.mode = &ed.insert
125 }
126
127 func navDefault(ed *Editor) {
128 // Use key binding for insert mode without exiting nigation mode.
129 k := ed.lastKey
130 n := &ed.navigation
131 if n.filtering && likeChar(k) {
132 n.filter += k.String()
133 n.refreshCurrent()
134 n.refreshDirPreview()
135 } else if n.filtering && k == (ui.Key{ui.Backspace, 0}) {
136 _, size := utf8.DecodeLastRuneInString(n.filter)
137 if size > 0 {
138 n.filter = n.filter[:len(n.filter)-size]
139 n.refreshCurrent()
140 n.refreshDirPreview()
141 }
142 } else {
143 ed.CallFn(getBinding(modeInsert, k))
144 }
145 }
146
147 // Implementation.
148 // TODO(xiaq): Remember which file was selected in each directory.
149
150 var (
151 errorEmptyCwd = errors.New("current directory is empty")
152 errorNoCwdInParent = errors.New("could not find current directory in parent")
153 )
154
155 func initNavigation(n *navigation, ed *Editor) {
156 *n = navigation{chdir: ed.chdir}
157 n.refresh()
158 }
159
160 func (n *navigation) maintainSelected(name string) {
161 n.current.selected = 0
162 for i, s := range n.current.candidates {
163 if s.Text > name {
164 break
165 }
166 n.current.selected = i
167 }
168 }
169
170 func (n *navigation) refreshCurrent() {
171 selectedName := n.current.selectedName()
172 all, err := n.loaddir(".")
173 if err != nil {
174 n.current = newErrNavColumn(err)
175 return
176 }
177 // Try to select the old selected file.
178 // XXX(xiaq): This would break when we support alternative ordering.
179 n.current = newNavColumn(all, func(i int) bool {
180 return i == 0 || all[i].Text <= selectedName
181 })
182 n.current.changeFilter(n.filter)
183 n.maintainSelected(selectedName)
184 }
185
186 func (n *navigation) refreshParent() {
187 wd, err := os.Getwd()
188 if err != nil {
189 n.parent = newErrNavColumn(err)
190 return
191 }
192 if wd == "/" {
193 n.parent = newNavColumn(nil, nil)
194 } else {
195 all, err := n.loaddir("..")
196 if err != nil {
197 n.parent = newErrNavColumn(err)
198 return
199 }
200 cwd, err := os.Stat(".")
201 if err != nil {
202 n.parent = newErrNavColumn(err)
203 return
204 }
205 n.parent = newNavColumn(all, func(i int) bool {
206 d, _ := os.Lstat("../" + all[i].Text)
207 return os.SameFile(d, cwd)
208 })
209 }
210 }
211
212 func (n *navigation) refreshDirPreview() {
213 if n.current.selected != -1 {
214 name := n.current.selectedName()
215 fi, err := os.Stat(name)
216 if err != nil {
217 n.preview = newErrNavColumn(err)
218 return
219 }
220 if fi.Mode().IsDir() {
221 all, err := n.loaddir(name)
222 if err != nil {
223 n.preview = newErrNavColumn(err)
224 return
225 }
226 n.preview = newNavColumn(all, func(int) bool { return false })
227 } else {
228 n.preview = newFilePreviewNavColumn(name)
229 }
230 } else {
231 n.preview = nil
232 }
233 }
234
235 // refresh rereads files in current and parent directories and maintains the
236 // selected file if possible.
237 func (n *navigation) refresh() {
238 n.refreshCurrent()
239 n.refreshParent()
240 n.refreshDirPreview()
241 }
242
243 // ascend changes current directory to the parent.
244 // TODO(xiaq): navigation.{ascend descend} bypasses the cd builtin. This can be
245 // problematic if cd acquires more functionality (e.g. trigger a hook).
246 func (n *navigation) ascend() error {
247 wd, err := os.Getwd()
248 if err != nil {
249 return err
250 }
251 if wd == "/" {
252 return nil
253 }
254
255 name := n.parent.selectedName()
256 err = os.Chdir("..")
257 if err != nil {
258 return err
259 }
260 n.filter = ""
261 n.refresh()
262 n.maintainSelected(name)
263 // XXX Refresh dir preview again. We should perhaps not have used refresh
264 // above.
265 n.refreshDirPreview()
266 return nil
267 }
268
269 // descend changes current directory to the selected file, if it is a
270 // directory.
271 func (n *navigation) descend() error {
272 if n.current.selected == -1 {
273 return errorEmptyCwd
274 }
275 name := n.current.selectedName()
276 err := n.chdir(name)
277 if err != nil {
278 return err
279 }
280 n.filter = ""
281 n.current.selected = -1
282 n.refresh()
283 n.refreshDirPreview()
284 return nil
285 }
286
287 // prev selects the previous file.
288 func (n *navigation) prev() {
289 if n.current.selected > 0 {
290 n.current.selected--
291 }
292 n.refresh()
293 }
294
295 // next selects the next file.
296 func (n *navigation) next() {
297 if n.current.selected != -1 && n.current.selected < len(n.current.candidates)-1 {
298 n.current.selected++
299 }
300 n.refresh()
301 }
302
303 func (n *navigation) loaddir(dir string) ([]ui.Styled, error) {
304 f, err := os.Open(dir)
305 if err != nil {
306 return nil, err
307 }
308 names, err := f.Readdirnames(-1)
309 if err != nil {
310 return nil, err
311 }
312 sort.Strings(names)
313
314 var all []ui.Styled
315 lsColor := getLsColor()
316 for _, name := range names {
317 if n.showHidden || name[0] != '.' {
318 all = append(all, ui.Styled{name,
319 ui.StylesFromString(lsColor.getStyle(path.Join(dir, name)))})
320 }
321 }
322
323 return all, nil
324 }
325
326 const (
327 navigationListingColMargin = 1
328
329 parentColumnWeight = 3.0
330 currentColumnWeight = 8.0
331 previewColumnWeight = 9.0
332 )
333
334 func (n *navigation) List(maxHeight int) renderer {
335 return makeNavRenderer(
336 maxHeight,
337 n.parent.FullWidth(maxHeight),
338 n.current.FullWidth(maxHeight),
339 n.preview.FullWidth(maxHeight),
340 n.parent.List(maxHeight),
341 n.current.List(maxHeight),
342 n.preview.List(maxHeight),
343 )
344 }
345
346 // navColumn is a column in the navigation layout.
347 type navColumn struct {
348 listing
349 all []ui.Styled
350 candidates []ui.Styled
351 // selected int
352 err error
353 }
354
355 func newNavColumn(all []ui.Styled, sel func(int) bool) *navColumn {
356 nc := &navColumn{all: all, candidates: all}
357 nc.provider = nc
358 nc.selected = -1
359 for i := range all {
360 if sel(i) {
361 nc.selected = i
362 }
363 }
364 return nc
365 }
366
367 func newErrNavColumn(err error) *navColumn {
368 nc := &navColumn{err: err}
369 nc.provider = nc
370 return nc
371 }
372
373 // PreviewBytes is the maximum number of bytes to preview a file.
374 const PreviewBytes = 64 * 1024
375
376 // Errors displayed in the preview area.
377 var (
378 ErrNotRegular = errors.New("no preview for non-regular file")
379 ErrNotValidUTF8 = errors.New("no preview for non-utf8 file")
380 )
381
382 func newFilePreviewNavColumn(fname string) *navColumn {
383 // XXX This implementation is a bit hacky, since listing is not really
384 // intended for listing file content.
385 var err error
386 file, err := os.Open(fname)
387 if err != nil {
388 return newErrNavColumn(err)
389 }
390
391 info, err := file.Stat()
392 if err != nil {
393 return newErrNavColumn(err)
394 }
395 if (info.Mode() & (os.ModeDevice | os.ModeNamedPipe | os.ModeSocket | os.ModeCharDevice)) != 0 {
396 return newErrNavColumn(ErrNotRegular)
397 }
398
399 // BUG when the file is bigger than the buffer, the scrollbar is wrong.
400 buf := make([]byte, PreviewBytes)
401 nr, err := file.Read(buf[:])
402 if err != nil {
403 return newErrNavColumn(err)
404 }
405
406 content := string(buf[:nr])
407 if !utf8.ValidString(content) {
408 return newErrNavColumn(ErrNotValidUTF8)
409 }
410
411 lines := strings.Split(content, "\n")
412 styleds := make([]ui.Styled, len(lines))
413 for i, line := range lines {
414 styleds[i] = ui.Styled{strings.Replace(line, "\t", " ", -1), ui.Styles{}}
415 }
416 return newNavColumn(styleds, func(int) bool { return false })
417 }
418
419 func (nc *navColumn) Placeholder() string {
420 if nc.err != nil {
421 return nc.err.Error()
422 }
423 return ""
424 }
425
426 func (nc *navColumn) Len() int {
427 return len(nc.candidates)
428 }
429
430 func (nc *navColumn) Show(i int) (string, ui.Styled) {
431 cand := nc.candidates[i]
432 return "", ui.Styled{" " + cand.Text + " ", cand.Styles}
433 }
434
435 func (nc *navColumn) Filter(filter string) int {
436 nc.candidates = nc.candidates[:0]
437 for _, s := range nc.all {
438 if strings.Contains(s.Text, filter) {
439 nc.candidates = append(nc.candidates, s)
440 }
441 }
442 return 0
443 }
444
445 func (nc *navColumn) FullWidth(h int) int {
446 if nc == nil {
447 return 0
448 }
449 if nc.err != nil {
450 return util.Wcswidth(nc.err.Error())
451 }
452 maxw := 0
453 for _, s := range nc.candidates {
454 maxw = max(maxw, util.Wcswidth(s.Text)+2)
455 }
456 if len(nc.candidates) > h {
457 maxw++
458 }
459 return maxw
460 }
461
462 func (nc *navColumn) Accept(i int, ed *Editor) {
463 // TODO
464 }
465
466 func (nc *navColumn) ModeTitle(i int) string {
467 // Not used
468 return ""
469 }
470
471 func (nc *navColumn) selectedName() string {
472 if nc == nil || nc.selected == -1 || nc.selected >= len(nc.candidates) {
473 return ""
474 }
475 return nc.candidates[nc.selected].Text
476 }
0 package nodeutil
1
2 import (
3 "strings"
4
5 "github.com/elves/elvish/parse"
6 "github.com/elves/elvish/util"
7 )
8
9 func SimpleCompound(cn *parse.Compound, upto *parse.Indexing) (bool, string, error) {
10 tilde := false
11 head := ""
12 for _, in := range cn.Indexings {
13 if len(in.Indicies) > 0 {
14 return false, "", nil
15 }
16 switch in.Head.Type {
17 case parse.Tilde:
18 tilde = true
19 case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted:
20 head += in.Head.Value
21 default:
22 return false, "", nil
23 }
24
25 if in == upto {
26 break
27 }
28 }
29 if tilde {
30 i := strings.Index(head, "/")
31 if i == -1 {
32 i = len(head)
33 }
34 uname := head[:i]
35 home, err := util.GetHome(uname)
36 if err != nil {
37 return false, "", err
38 }
39 head = home + head[i:]
40 }
41 return true, head, nil
42 }
0 package nodeutil
1
2 import "testing"
3
4 func TestNodeUtil(t *testing.T) {
5 // TODO(xiaq): Add tests.
6 }
0 package edit
1
2 import (
3 "strings"
4
5 "github.com/elves/elvish/edit/nodeutil"
6 "github.com/elves/elvish/eval"
7 "github.com/elves/elvish/parse"
8 )
9
10 // Utilities for insepcting the AST. Used for completers and stylists.
11
12 func primaryInSimpleCompound(pn *parse.Primary) (*parse.Compound, string) {
13 indexing := parse.GetIndexing(pn.Parent())
14 if indexing == nil {
15 return nil, ""
16 }
17 compound := parse.GetCompound(indexing.Parent())
18 if compound == nil {
19 return nil, ""
20 }
21 ok, head, _ := simpleCompound(compound, indexing)
22 if !ok {
23 return nil, ""
24 }
25 return compound, head
26 }
27
28 func simpleCompound(cn *parse.Compound, upto *parse.Indexing) (bool, string, error) {
29 return nodeutil.SimpleCompound(cn, upto)
30 }
31
32 // purelyEvalPrimary evaluates a primary node without causing any side effects.
33 // If this cannot be done, it returns nil.
34 //
35 // Currently, only string literals and variables with no @ can be evaluated.
36 func purelyEvalPrimary(pn *parse.Primary, ev *eval.Evaler) eval.Value {
37 switch pn.Type {
38 case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted:
39 return eval.String(pn.Value)
40 case parse.Variable:
41 explode, ns, name := eval.ParseVariable(pn.Value)
42 if explode {
43 return nil
44 }
45 ec := eval.NewTopEvalCtx(ev, "[pure eval]", "", nil)
46 variable := ec.ResolveVar(ns, name)
47 return variable.Get()
48 }
49 return nil
50 }
51
52 // leafNodeAtDot finds the leaf node at a specific position. It returns nil if
53 // position is out of bound.
54 func findLeafNode(n parse.Node, p int) parse.Node {
55 descend:
56 for len(n.Children()) > 0 {
57 for _, ch := range n.Children() {
58 if ch.Begin() <= p && p <= ch.End() {
59 n = ch
60 continue descend
61 }
62 }
63 return nil
64 }
65 return n
66 }
67
68 func wordify(src string) []string {
69 n, _ := parse.Parse("[wordify]", src)
70 return wordifyInner(n, nil)
71 }
72
73 func wordifyInner(n parse.Node, words []string) []string {
74 if len(n.Children()) == 0 || parse.IsCompound(n) {
75 text := n.SourceText()
76 if strings.TrimFunc(text, parse.IsSpaceOrNewline) != "" {
77 return append(words, text)
78 }
79 return words
80 }
81 for _, ch := range n.Children() {
82 words = wordifyInner(ch, words)
83 }
84 return words
85 }
0 package edit
1
2 import (
3 "os"
4 "os/user"
5
6 "github.com/elves/elvish/edit/ui"
7 "github.com/elves/elvish/eval"
8 "github.com/elves/elvish/util"
9 )
10
11 var _ = registerVariable("prompt", promptVariable)
12
13 func promptVariable() eval.Variable {
14 prompt := func(ec *eval.EvalCtx,
15 args []eval.Value, opts map[string]eval.Value) {
16
17 out := ec.OutputChan()
18 out <- &ui.Styled{util.Getwd() + "> ", ui.Styles{}}
19 }
20 return eval.NewPtrVariableWithValidator(
21 &eval.BuiltinFn{"default prompt", prompt}, eval.ShouldBeFn)
22 }
23
24 func (ed *Editor) prompt() eval.Callable {
25 return ed.variables["prompt"].Get().(eval.Callable)
26 }
27
28 var _ = registerVariable("rprompt", rpromptVariable)
29
30 func rpromptVariable() eval.Variable {
31 username := "???"
32 user, err := user.Current()
33 if err == nil {
34 username = user.Username
35 }
36 hostname, err := os.Hostname()
37 if err != nil {
38 hostname = "???"
39 }
40 rpromptStr := username + "@" + hostname
41 rprompt := func(ec *eval.EvalCtx,
42 args []eval.Value, opts map[string]eval.Value) {
43
44 out := ec.OutputChan()
45 out <- &ui.Styled{rpromptStr, ui.Styles{"7"}}
46 }
47
48 return eval.NewPtrVariableWithValidator(
49 &eval.BuiltinFn{"default rprompt", rprompt}, eval.ShouldBeFn)
50 }
51
52 func (ed *Editor) rprompt() eval.Callable {
53 return ed.variables["rprompt"].Get().(eval.Callable)
54 }
55
56 var _ = registerVariable("rprompt-persistent", func() eval.Variable {
57 return eval.NewPtrVariableWithValidator(eval.Bool(false), eval.ShouldBeBool)
58 })
59
60 func (ed *Editor) rpromptPersistent() bool {
61 return bool(ed.variables["rprompt-persistent"].Get().(eval.Bool).Bool())
62 }
0 package edit
1
2 import (
3 "github.com/elves/elvish/edit/ui"
4 "github.com/elves/elvish/eval"
5 )
6
7 // Raw insert mode is a special mode, in that it does not use the normal key
8 // binding. Rather, insertRaw is called directly from the main loop in
9 // Editor.ReadLine.
10
11 type rawInsert struct {
12 }
13
14 func startInsertRaw(ed *Editor) {
15 ed.reader.SetRaw(true)
16 ed.mode = rawInsert{}
17 }
18
19 func insertRaw(ed *Editor, r rune) {
20 ed.insertAtDot(string(r))
21 ed.reader.SetRaw(false)
22 ed.mode = &ed.insert
23 }
24
25 func (rawInsert) Binding(k ui.Key) eval.CallableValue {
26 // The raw insert mode does not handle keys.
27 return nil
28 }
29
30 func (ri rawInsert) ModeLine() renderer {
31 return modeLineRenderer{" RAW ", ""}
32 }
0 package edit
1
2 import (
3 "fmt"
4 "os"
5 "strings"
6
7 "github.com/elves/elvish/edit/ui"
8 "github.com/elves/elvish/eval"
9 )
10
11 // This file contains several "registries", data structure that are written
12 // during program initialization and later used when initializing the Editor.
13
14 var builtinMaps = map[string]map[string]*BuiltinFn{}
15
16 // registerBuiltins registers builtins under a subnamespace of le:, to be used
17 // during the initialization of the Editor. It should be called for global
18 // variable initializations to make sure every subnamespace is registered before
19 // makeBindings is ever called.
20 func registerBuiltins(module string, impls map[string]func(*Editor)) struct{} {
21 if _, ok := builtinMaps[module]; !ok {
22 builtinMaps[module] = make(map[string]*BuiltinFn)
23 }
24 for name, impl := range impls {
25 var fullName string
26 if module == "" {
27 fullName = "le:" + eval.FnPrefix + name
28 } else {
29 fullName = "le:" + module + ":" + eval.FnPrefix + name
30 }
31 builtinMaps[module][name] = &BuiltinFn{fullName, impl}
32 }
33 return struct{}{}
34 }
35
36 func makeNamespaceFromBuiltins(builtins map[string]*BuiltinFn) eval.Namespace {
37 ns := eval.Namespace{}
38 for name, builtin := range builtins {
39 ns[eval.FnPrefix+name] = eval.NewPtrVariable(builtin)
40 }
41 return ns
42 }
43
44 var keyBindings = map[string]map[ui.Key]eval.CallableValue{}
45
46 // registerBindings registers default bindings for a mode to initialize the
47 // global keyBindings map. Builtin names are resolved in the defaultMod
48 // subnamespace using information from builtinMaps. It should be called in init
49 // functions.
50 func registerBindings(
51 mt string, defaultMod string, bindingData map[ui.Key]string) struct{} {
52
53 if _, ok := keyBindings[mt]; !ok {
54 keyBindings[mt] = map[ui.Key]eval.CallableValue{}
55 }
56 for key, fullName := range bindingData {
57 // break fullName into mod and name.
58 var mod, name string
59 nameParts := strings.SplitN(fullName, ":", 2)
60 if len(nameParts) == 2 {
61 mod, name = nameParts[0], nameParts[1]
62 } else {
63 mod, name = defaultMod, nameParts[0]
64 }
65 if m, ok := builtinMaps[mod]; ok {
66 if builtin, ok := m[name]; ok {
67 keyBindings[mt][key] = builtin
68 } else {
69 fmt.Fprintln(os.Stderr, "Internal warning: no such builtin", name, "in mod", mod)
70 }
71 } else {
72 fmt.Fprintln(os.Stderr, "Internal warning: no such mod:", mod)
73 }
74 }
75 return struct{}{}
76 }
77
78 var variableMakers = map[string]func() eval.Variable{}
79
80 // registerVariables registers a variable, its name and a func used to derive
81 // its value, later to be used during Editor initialization to populate
82 // Editor.variables as well as the le: namespace.
83 func registerVariable(name string, maker func() eval.Variable) struct{} {
84 variableMakers[name] = maker
85 return struct{}{}
86 }
87
88 func makeVariables() map[string]eval.Variable {
89 m := make(map[string]eval.Variable, len(variableMakers))
90 for name, maker := range variableMakers {
91 m[name] = maker()
92 }
93 return m
94 }
0 package edit
1
2 import (
3 "strings"
4 "unicode/utf8"
5
6 "github.com/elves/elvish/edit/highlight"
7 "github.com/elves/elvish/edit/ui"
8 "github.com/elves/elvish/util"
9 )
10
11 type renderer interface {
12 render(b *buffer)
13 }
14
15 func render(r renderer, width int) *buffer {
16 if r == nil {
17 return nil
18 }
19 b := newBuffer(width)
20 r.render(b)
21 return b
22 }
23
24 type modeLineRenderer struct {
25 title string
26 filter string
27 }
28
29 func (ml modeLineRenderer) render(b *buffer) {
30 b.writes(ml.title, styleForMode.String())
31 b.writePadding(1, "")
32 b.writes(ml.filter, styleForFilter.String())
33 b.dot = b.cursor()
34 }
35
36 type modeLineWithScrollBarRenderer struct {
37 modeLineRenderer
38 n, low, high int
39 }
40
41 func (ml modeLineWithScrollBarRenderer) render(b *buffer) {
42 ml.modeLineRenderer.render(b)
43
44 scrollbarWidth := b.width - cellsWidth(b.lines[len(b.lines)-1]) - 2
45 if scrollbarWidth >= 3 {
46 b.writePadding(1, "")
47 writeHorizontalScrollbar(b, ml.n, ml.low, ml.high, scrollbarWidth)
48 }
49 }
50
51 type placeholderRenderer string
52
53 func (lp placeholderRenderer) render(b *buffer) {
54 b.writes(util.TrimWcwidth(string(lp), b.width), "")
55 }
56
57 type listingRenderer struct {
58 lines []ui.Styled
59 }
60
61 func (ls listingRenderer) render(b *buffer) {
62 for i, line := range ls.lines {
63 if i > 0 {
64 b.newline()
65 }
66 b.writes(util.ForceWcwidth(line.Text, b.width), line.Styles.String())
67 }
68 }
69
70 type listingWithScrollBarRenderer struct {
71 listingRenderer
72 n, low, high, height int
73 }
74
75 func (ls listingWithScrollBarRenderer) render(b *buffer) {
76 b1 := render(ls.listingRenderer, b.width-1)
77 b.extendRight(b1, 0)
78
79 scrollbar := renderScrollbar(ls.n, ls.low, ls.high, ls.height)
80 b.extendRight(scrollbar, b.width-1)
81 }
82
83 type navRenderer struct {
84 maxHeight int
85 fwParent, fwCurrent, fwPreview int
86 parent, current, preview renderer
87 }
88
89 func makeNavRenderer(h int, w1, w2, w3 int, r1, r2, r3 renderer) renderer {
90 return &navRenderer{h, w1, w2, w3, r1, r2, r3}
91 }
92
93 func (nr *navRenderer) render(b *buffer) {
94 margin := navigationListingColMargin
95
96 w := b.width - margin*2
97 ws := distributeWidths(w,
98 []float64{parentColumnWeight, currentColumnWeight, previewColumnWeight},
99 []int{nr.fwParent, nr.fwCurrent, nr.fwPreview},
100 )
101 wParent, wCurrent, wPreview := ws[0], ws[1], ws[2]
102
103 bParent := render(nr.parent, wParent)
104 b.extendRight(bParent, 0)
105
106 bCurrent := render(nr.current, wCurrent)
107 b.extendRight(bCurrent, wParent+margin)
108
109 if wPreview > 0 {
110 bPreview := render(nr.preview, wPreview)
111 b.extendRight(bPreview, wParent+wCurrent+2*margin)
112 }
113 }
114
115 // linesRenderer renders lines with a uniform style.
116 type linesRenderer struct {
117 lines []string
118 style string
119 }
120
121 func (nr linesRenderer) render(b *buffer) {
122 b.writes(strings.Join(nr.lines, "\n"), "")
123 }
124
125 // cmdlineRenderer renders the command line, including the prompt, the user's
126 // input and the rprompt.
127 type cmdlineRenderer struct {
128 prompt []*ui.Styled
129 line string
130 styling *highlight.Styling
131 dot int
132 rprompt []*ui.Styled
133
134 hasComp bool
135 compBegin int
136 compEnd int
137 compText string
138
139 hasHist bool
140 histBegin int
141 histText string
142 }
143
144 func newCmdlineRenderer(p []*ui.Styled, l string, s *highlight.Styling, d int, rp []*ui.Styled) *cmdlineRenderer {
145 return &cmdlineRenderer{prompt: p, line: l, styling: s, dot: d, rprompt: rp}
146 }
147
148 func (clr *cmdlineRenderer) setComp(b, e int, t string) {
149 clr.hasComp = true
150 clr.compBegin, clr.compEnd, clr.compText = b, e, t
151 }
152
153 func (clr *cmdlineRenderer) setHist(b int, t string) {
154 clr.hasHist = true
155 clr.histBegin, clr.histText = b, t
156 }
157
158 func (clr *cmdlineRenderer) render(b *buffer) {
159 b.eagerWrap = true
160
161 b.writeStyleds(clr.prompt)
162
163 // If the prompt takes less than half of a line, set the indent.
164 if len(b.lines) == 1 && b.col*2 < b.width {
165 b.indent = b.col
166 }
167
168 // i keeps track of number of bytes written.
169 i := 0
170
171 applier := clr.styling.Apply()
172
173 // nowAt is called at every rune boundary.
174 nowAt := func(i int) {
175 applier.At(i)
176 if clr.hasComp && i == clr.compBegin {
177 b.writes(clr.compText, styleForCompleted.String())
178 }
179 if i == clr.dot {
180 b.dot = b.cursor()
181 }
182 }
183 nowAt(0)
184
185 for _, r := range clr.line {
186 if clr.hasComp && clr.compBegin <= i && i < clr.compEnd {
187 // Do nothing. This part is replaced by the completion candidate.
188 } else {
189 b.write(r, applier.Get())
190 }
191 i += utf8.RuneLen(r)
192
193 nowAt(i)
194 if clr.hasHist && i == clr.histBegin {
195 break
196 }
197 }
198
199 if clr.hasHist {
200 // Put the rest of current history and position the cursor at the
201 // end of the line.
202 b.writes(clr.histText, styleForCompletedHistory.String())
203 b.dot = b.cursor()
204 }
205
206 // Write rprompt
207 if len(clr.rprompt) > 0 {
208 padding := b.width - b.col
209 for _, s := range clr.rprompt {
210 padding -= util.Wcswidth(s.Text)
211 }
212 if padding >= 1 {
213 b.eagerWrap = false
214 b.writePadding(padding, "")
215 b.writeStyleds(clr.rprompt)
216 }
217 }
218 }
219
220 // editorRenderer renders the entire editor.
221 type editorRenderer struct {
222 *editorState
223 height int
224 bufNoti *buffer
225 }
226
227 func (er *editorRenderer) render(buf *buffer) {
228 height, width, es := er.height, buf.width, er.editorState
229
230 var bufNoti, bufLine, bufMode, bufTips, bufListing *buffer
231 // butNoti
232 if len(es.notifications) > 0 {
233 bufNoti = render(linesRenderer{es.notifications, ""}, width)
234 es.notifications = nil
235 }
236
237 // bufLine
238 clr := newCmdlineRenderer(es.promptContent, es.line, es.styling, es.dot, es.rpromptContent)
239 // TODO(xiaq): Instead of doing a type switch, expose an API for modes to
240 // modify the text (and mark their part as modified).
241 switch es.mode.(type) {
242 case *completion:
243 c := es.completion
244 clr.setComp(c.begin, c.end, c.selectedCandidate().code)
245 case *hist:
246 begin := len(es.hist.Prefix())
247 clr.setHist(begin, es.hist.CurrentCmd()[begin:])
248 }
249 bufLine = render(clr, width)
250
251 // bufMode
252 bufMode = render(es.mode.ModeLine(), width)
253
254 // bufTips
255 // TODO tips is assumed to contain no newlines.
256 if len(es.tips) > 0 {
257 bufTips = render(linesRenderer{es.tips, styleForTip.String()}, width)
258 }
259
260 hListing := 0
261 // Trim lines and determine the maximum height for bufListing
262 // TODO come up with a UI to tell the user that something is not shown.
263 switch {
264 case height >= buffersHeight(bufNoti, bufLine, bufMode, bufTips):
265 hListing = height - buffersHeight(bufLine, bufMode, bufTips)
266 case height >= buffersHeight(bufNoti, bufLine, bufTips):
267 bufMode = nil
268 case height >= buffersHeight(bufNoti, bufLine):
269 bufMode = nil
270 if bufTips != nil {
271 bufTips.trimToLines(0, height-buffersHeight(bufNoti, bufLine))
272 }
273 case height >= buffersHeight(bufLine):
274 bufTips, bufMode = nil, nil
275 if bufNoti != nil {
276 n := len(bufNoti.lines)
277 bufNoti.trimToLines(n-(height-buffersHeight(bufLine)), n)
278 }
279 case height >= 1:
280 bufNoti, bufTips, bufMode = nil, nil, nil
281 dotLine := bufLine.dot.line
282 bufLine.trimToLines(dotLine+1-height, dotLine+1)
283 default:
284 // Broken terminal. Still try to render one line of bufLine.
285 bufNoti, bufTips, bufMode = nil, nil, nil
286 dotLine := bufLine.dot.line
287 bufLine.trimToLines(dotLine, dotLine+1)
288 }
289
290 // bufListing.
291 if hListing > 0 {
292 if lister, ok := es.mode.(ListRenderer); ok {
293 bufListing = lister.ListRender(width, hListing)
294 } else if lister, ok := es.mode.(Lister); ok {
295 bufListing = render(lister.List(hListing), width)
296 }
297 // XXX When in completion mode, we re-render the mode line, since the
298 // scrollbar in the mode line depends on completion.lastShown which is
299 // only known after the listing has been rendered. Since rendering the
300 // scrollbar never adds additional lines to bufMode, we may do this
301 // without recalculating the layout.
302 if _, ok := es.mode.(*completion); ok {
303 bufMode = render(es.mode.ModeLine(), width)
304 }
305 }
306
307 if logWriterDetail {
308 logger.Printf("bufLine %d, bufMode %d, bufTips %d, bufListing %d",
309 buffersHeight(bufLine), buffersHeight(bufMode), buffersHeight(bufTips), buffersHeight(bufListing))
310 }
311
312 // XXX
313 buf.lines = nil
314 // Combine buffers (reusing bufLine)
315 buf.extend(bufLine, true)
316 cursorOnModeLine := false
317 if coml, ok := es.mode.(CursorOnModeLiner); ok {
318 cursorOnModeLine = coml.CursorOnModeLine()
319 }
320 buf.extend(bufMode, cursorOnModeLine)
321 buf.extend(bufTips, false)
322 buf.extend(bufListing, false)
323
324 er.bufNoti = bufNoti
325 }
0 package edit
1
2 import (
3 "github.com/elves/elvish/edit/ui"
4 )
5
6 var styleForCompilerError = ui.Styles{"white", "bg-red"}
7
8 // Styles for UI.
9 var (
10 //styleForPrompt = ""
11 //styleForRPrompt = "inverse"
12 styleForCompleted = ui.Styles{"underlined"}
13 styleForCompletedHistory = ui.Styles{"underlined"}
14 styleForMode = ui.Styles{"bold", "lightgray", "bg-magenta"}
15 styleForTip = ui.Styles{}
16 styleForFilter = ui.Styles{"underlined"}
17 styleForSelected = ui.Styles{"inverse"}
18 styleForScrollBarArea = ui.Styles{"magenta"}
19 styleForScrollBarThumb = ui.Styles{"magenta", "inverse"}
20
21 styleForControlChar = ui.Styles{"inverse"}
22
23 // Use default style for completion listing
24 styleForCompletion = ui.Styles{}
25 // Use inverse style for selected completion entry
26 styleForSelectedCompletion = ui.Styles{"inverse"}
27 )
0 package edit
1
2 import (
3 "github.com/elves/elvish/edit/ui"
4 "github.com/elves/elvish/eval"
5 )
6
7 func styled(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
8 var textv, stylev eval.String
9 eval.ScanArgs(args, &textv, &stylev)
10 text, style := string(textv), string(stylev)
11 eval.TakeNoOpt(opts)
12
13 out := ec.OutputChan()
14 out <- &ui.Styled{text, ui.StylesFromString(style)}
15 }
0 package tty
1
2 import (
3 "bufio"
4 "io"
5 "os"
6 "syscall"
7
8 "github.com/elves/elvish/sys"
9 )
10
11 const (
12 asyncReaderChanSize int = 128
13 )
14
15 // AsyncReader delivers a Unix fd stream to a channel of runes.
16 type AsyncReader struct {
17 rd *os.File
18 bufrd *bufio.Reader
19 rCtrl, wCtrl *os.File
20 ctrlCh chan struct{}
21 ch chan rune
22 errCh chan error
23 }
24
25 // NewAsyncReader creates a new AsyncReader from a file.
26 func NewAsyncReader(rd *os.File) *AsyncReader {
27 ar := &AsyncReader{
28 rd: rd,
29 bufrd: bufio.NewReaderSize(rd, 0),
30 ctrlCh: make(chan struct{}),
31 ch: make(chan rune, asyncReaderChanSize),
32 }
33
34 r, w, err := os.Pipe()
35 if err != nil {
36 panic(err)
37 }
38 ar.rCtrl, ar.wCtrl = r, w
39 return ar
40 }
41
42 // Chan returns a channel onto which the AsyncReader writes the runes it reads.
43 func (ar *AsyncReader) Chan() <-chan rune {
44 return ar.ch
45 }
46
47 // ErrorChan returns a channel onto which the AsyncReader writes the errors it
48 // encounters.
49 func (ar *AsyncReader) ErrorChan() <-chan error {
50 return ar.errCh
51 }
52
53 // Run runs the AsyncReader. It blocks until Quit is called and should be
54 // called in a separate goroutine.
55 func (ar *AsyncReader) Run() {
56 fd := int(ar.rd.Fd())
57 cfd := int(ar.rCtrl.Fd())
58 maxfd := max(fd, cfd)
59 fs := sys.NewFdSet()
60 var cBuf [1]byte
61
62 if nonblock, _ := sys.GetNonblock(fd); !nonblock {
63 sys.SetNonblock(fd, true)
64 defer sys.SetNonblock(fd, false)
65 }
66
67 for {
68 fs.Set(fd, cfd)
69 err := sys.Select(maxfd+1, fs, nil, nil, nil)
70 if err != nil {
71 switch err {
72 case syscall.EINTR:
73 continue
74 default:
75 ar.errCh <- err
76 return
77 }
78 }
79 if fs.IsSet(cfd) {
80 // Consume the written byte
81 ar.rCtrl.Read(cBuf[:])
82 <-ar.ctrlCh
83 return
84 }
85 ReadRune:
86 for {
87 r, _, err := ar.bufrd.ReadRune()
88 switch err {
89 case nil:
90 // Logger.Printf("read rune: %q", r)
91 select {
92 case ar.ch <- r:
93 case <-ar.ctrlCh:
94 ar.rCtrl.Read(cBuf[:])
95 return
96 }
97 case io.EOF:
98 return
99 default:
100 // BUG(xiaq): AsyncReader relies on the undocumented fact
101 // that (*os.File).Read returns an *os.File.PathError
102 patherr, ok := err.(*os.PathError) //.Err
103 if ok && patherr.Err == syscall.EWOULDBLOCK || patherr.Err == syscall.EAGAIN {
104 break ReadRune
105 } else {
106 select {
107 case ar.errCh <- err:
108 case <-ar.ctrlCh:
109 ar.rCtrl.Read(cBuf[:])
110 return
111 }
112 }
113 }
114 }
115 }
116 }
117
118 // Quit terminates the loop of Run.
119 func (ar *AsyncReader) Quit() {
120 _, err := ar.wCtrl.Write([]byte{'q'})
121 if err != nil {
122 panic(err)
123 }
124 ar.ctrlCh <- struct{}{}
125 }
126
127 // Close releases files and channels associated with the AsyncReader. It does
128 // not close the file used to create it.
129 func (ar *AsyncReader) Close() {
130 ar.rCtrl.Close()
131 ar.wCtrl.Close()
132 close(ar.ctrlCh)
133 close(ar.ch)
134 }
135
136 func max(a, b int) int {
137 if a >= b {
138 return a
139 }
140 return b
141 }
0 package tty
1
2 import (
3 "fmt"
4 "math/rand"
5 "os"
6 "testing"
7 "time"
8
9 "github.com/elves/elvish/sys"
10 )
11
12 // Pretty arbitrary numbers. May not reveal deadlocks on all machines.
13
14 var (
15 DeadlockNWrite = 1024
16 DeadlockRun = 64
17 DeadlockTimeout = 500 * time.Millisecond
18 DeadlockMaxJitter = time.Millisecond
19 )
20
21 func jitter() {
22 time.Sleep(time.Duration(float64(DeadlockMaxJitter) * rand.Float64()))
23 }
24
25 func f(done chan struct{}) {
26 r, w, err := os.Pipe()
27 if err != nil {
28 panic(err)
29 }
30 defer r.Close()
31 defer w.Close()
32
33 ar := NewAsyncReader(r)
34 defer ar.Close()
35 fmt.Fprintf(w, "%*s", DeadlockNWrite, "")
36 go func() {
37 jitter()
38 ar.Run()
39 }()
40 jitter()
41 ar.Quit()
42 done <- struct{}{}
43 }
44
45 func TestAsyncReaderDeadlock(t *testing.T) {
46 done := make(chan struct{})
47 isatty := sys.IsATTY(1)
48 rand.Seed(time.Now().UTC().UnixNano())
49
50 timer := time.NewTimer(DeadlockTimeout)
51 for i := 0; i < DeadlockRun; i++ {
52 if isatty {
53 fmt.Printf("\r%d/%d ", i+1, DeadlockRun)
54 }
55
56 go f(done)
57
58 select {
59 case <-done:
60 // no deadlock trigerred
61 case <-timer.C:
62 // deadlock
63 t.Errorf("%s", sys.DumpStack())
64 t.Fatalf("AsyncReader deadlock trigerred on run %d/%d, stack trace:\n%s", i, DeadlockRun, sys.DumpStack())
65 }
66 timer.Reset(DeadlockTimeout)
67 }
68 if isatty {
69 fmt.Print("\r \r")
70 }
71 }
72
73 var ReadTimeout = time.Second
74
75 func TestAsyncReader(t *testing.T) {
76 r, w, err := os.Pipe()
77 if err != nil {
78 panic(err)
79 }
80 defer r.Close()
81 defer w.Close()
82
83 ar := NewAsyncReader(r)
84 defer ar.Close()
85 go ar.Run()
86
87 go func() {
88 var i rune
89 for i = 0; i <= 1280; i += 10 {
90 w.WriteString(string(i))
91 }
92 }()
93
94 var i rune
95 timer := time.NewTimer(ReadTimeout)
96 for i = 0; i <= 1280; i += 10 {
97 select {
98 case r := <-ar.Chan():
99 if r != i {
100 t.Fatalf("expect %q, got %q\n", i, r)
101 }
102 case <-timer.C:
103 t.Fatalf("read timeout (i = %d)", i)
104 }
105 timer.Reset(ReadTimeout)
106 }
107 ar.Quit()
108 }
0 package tty
1
2 // Pos is the position in a terminal.
3 type Pos struct {
4 line, col int
5 }
0 package tty
1
2 import "github.com/elves/elvish/edit/ui"
3
4 // ReadUnit represents one "thing" that the Reader has read. It is one of the
5 // following: RawRune (when the reader is in the raw mode), Key, CursorPosition,
6 // MouseEvent, or PasteSetting.
7 type ReadUnit interface {
8 isReadUnit()
9 }
10
11 type RawRune rune
12 type Key ui.Key
13 type CursorPosition Pos
14 type PasteSetting bool
15
16 func (RawRune) isReadUnit() {}
17 func (Key) isReadUnit() {}
18 func (CursorPosition) isReadUnit() {}
19 func (MouseEvent) isReadUnit() {}
20 func (PasteSetting) isReadUnit() {}
0 package tty
1
2 import (
3 "fmt"
4 "os"
5 "time"
6
7 "github.com/elves/elvish/edit/ui"
8 )
9
10 var (
11 // EscSequenceTimeout is the amount of time within which runes that make up
12 // an escape sequence are supposed to follow each other. Modern terminal
13 // emulators send escape sequences very fast, so 10ms is more than
14 // sufficient. SSH connections on a slow link might be problematic though.
15 EscSequenceTimeout = 10 * time.Millisecond
16 )
17
18 // Special rune values used in the return value of (*Reader).ReadRune.
19 const (
20 // No rune received before specified time.
21 runeTimeout rune = -1 - iota
22 // Error occurred in AsyncReader. The error is left at the readError field.
23 runeReadError
24 )
25
26 // Reader converts a stream of events on separate channels.
27 type Reader struct {
28 ar *AsyncReader
29 raw bool
30
31 unitChan chan ReadUnit
32 /*
33 rawRuneChan chan rune
34 keyChan chan ui.Key
35 cprChan chan Pos
36 mouseChan chan MouseEvent
37 pasteChan chan bool
38 */
39 errChan chan error
40 quit chan struct{}
41 }
42
43 type MouseEvent struct {
44 Pos
45 Down bool
46 // Number of the Button, 0-based. -1 for unknown.
47 Button int
48 Mod ui.Mod
49 }
50
51 // NewReader creates a new Reader on the given terminal file.
52 func NewReader(f *os.File) *Reader {
53 rd := &Reader{
54 NewAsyncReader(f),
55 false,
56 make(chan ReadUnit),
57 /*
58 make(chan rune),
59 make(chan ui.Key),
60 make(chan Pos),
61 make(chan MouseEvent),
62 make(chan bool),
63 */
64 make(chan error),
65 nil,
66 }
67 return rd
68 }
69
70 // SetRaw turns the raw option on or off. If the reader is in the middle of
71 // reading one event, it takes effect after this event is fully read.
72 func (rd *Reader) SetRaw(raw bool) {
73 rd.raw = raw
74 }
75
76 // UnitChan returns the channel onto which the Reader writes what it has read.
77 func (rd *Reader) UnitChan() <-chan ReadUnit {
78 return rd.unitChan
79 }
80
81 // ErrorChan returns the channel onto which the Reader writes errors it came
82 // across during the reading process.
83 func (rd *Reader) ErrorChan() <-chan error {
84 return rd.errChan
85 }
86
87 // Run runs the Reader. It blocks until Quit is called and should be called in
88 // a separate goroutine.
89 func (rd *Reader) Run() {
90 runes := rd.ar.Chan()
91 quit := make(chan struct{})
92 rd.quit = quit
93 go rd.ar.Run()
94
95 for {
96 select {
97 case r := <-runes:
98 if rd.raw {
99 rd.unitChan <- RawRune(r)
100 } else {
101 rd.readOne(r)
102 }
103 case <-quit:
104 return
105 }
106 }
107 }
108
109 // Quit terminates the loop of Run.
110 func (rd *Reader) Quit() {
111 rd.ar.Quit()
112 close(rd.quit)
113 }
114
115 // Close releases files associated with the Reader. It does not close the file
116 // used to create it.
117 func (rd *Reader) Close() {
118 rd.ar.Close()
119 }
120
121 // readOne attempts to read one key or CPR, led by a rune already read.
122 func (rd *Reader) readOne(r rune) {
123 var unit ReadUnit
124 var err error
125 currentSeq := string(r)
126
127 badSeq := func(msg string) {
128 err = fmt.Errorf("%s: %q", msg, currentSeq)
129 }
130
131 // readRune attempts to read a rune within EscSequenceTimeout. It writes to
132 // the err and currentSeq variable in the outer scope.
133 readRune :=
134 func() rune {
135 select {
136 case r := <-rd.ar.Chan():
137 currentSeq += string(r)
138 return r
139 case err = <-rd.ar.ErrorChan():
140 return runeReadError
141 case <-time.After(EscSequenceTimeout):
142 return runeTimeout
143 }
144 }
145
146 defer func() {
147 if unit != nil {
148 select {
149 case rd.unitChan <- unit:
150 case <-rd.quit:
151 }
152 }
153 if err != nil {
154 select {
155 case rd.errChan <- err:
156 case <-rd.quit:
157 }
158 }
159 }()
160
161 switch r {
162 case 0x1b: // ^[ Escape
163 r2 := readRune()
164 if r2 == runeTimeout || r2 == runeReadError {
165 // Nothing follows. Taken as a lone Escape.
166 unit = Key{'[', ui.Ctrl}
167 break
168 }
169 switch r2 {
170 case '[':
171 // A '[' follows. CSI style function key sequence.
172 r = readRune()
173 if r == runeTimeout || r == runeReadError {
174 unit = Key{'[', ui.Alt}
175 return
176 }
177
178 nums := make([]int, 0, 2)
179 var starter rune
180
181 // Read an optional starter.
182 switch r {
183 case '<':
184 starter = r
185 r = readRune()
186 case 'M':
187 // Mouse event.
188 cb := readRune()
189 if cb == runeTimeout || cb == runeReadError {
190 badSeq("Incomplete mouse event")
191 return
192 }
193 cx := readRune()
194 if cx == runeTimeout || cx == runeReadError {
195 badSeq("Incomplete mouse event")
196 return
197 }
198 cy := readRune()
199 if cy == runeTimeout || cy == runeReadError {
200 badSeq("Incomplete mouse event")
201 return
202 }
203 down := true
204 button := int(cb & 3)
205 if button == 3 {
206 down = false
207 button = -1
208 }
209 mod := mouseModify(int(cb))
210 unit = MouseEvent{
211 Pos{int(cy) - 32, int(cx) - 32}, down, button, mod}
212 return
213 }
214 CSISeq:
215 for {
216 switch {
217 case r == ';':
218 nums = append(nums, 0)
219 case '0' <= r && r <= '9':
220 if len(nums) == 0 {
221 nums = append(nums, 0)
222 }
223 cur := len(nums) - 1
224 nums[cur] = nums[cur]*10 + int(r-'0')
225 case r == runeTimeout:
226 // Incomplete CSI.
227 badSeq("Incomplete CSI")
228 return
229 case r == runeReadError:
230 // TODO Also complain about incomplte CSI.
231 return
232 default: // Treat as a terminator.
233 break CSISeq
234 }
235
236 r = readRune()
237 }
238 if starter == 0 && r == 'R' {
239 // Cursor position report.
240 if len(nums) != 2 {
241 badSeq("bad CPR")
242 return
243 }
244 unit = CursorPosition{nums[0], nums[1]}
245 } else if starter == '<' && (r == 'm' || r == 'M') {
246 // SGR-style mouse event.
247 if len(nums) != 3 {
248 badSeq("bad SGR mouse event")
249 return
250 }
251 down := r == 'M'
252 button := nums[0] & 3
253 mod := mouseModify(nums[0])
254 unit = MouseEvent{Pos{nums[2], nums[1]}, down, button, mod}
255 } else if r == '~' && len(nums) == 1 && (nums[0] == 200 || nums[0] == 201) {
256 b := nums[0] == 200
257 unit = PasteSetting(b)
258 } else {
259 k := parseCSI(nums, r, currentSeq)
260 if k == (ui.Key{}) {
261 badSeq("bad CSI")
262 } else {
263 unit = Key(k)
264 }
265 }
266 case 'O':
267 // An 'O' follows. G3 style function key sequence: read one rune.
268 r = readRune()
269 if r == runeTimeout || r == runeReadError {
270 // Nothing follows after 'O'. Taken as ui.Alt-o.
271 unit = Key{'o', ui.Alt}
272 return
273 }
274 r, ok := g3Seq[r]
275 if ok {
276 unit = Key{r, 0}
277 } else {
278 badSeq("bad G3")
279 }
280 default:
281 // Something other than '[' or 'O' follows. Taken as an
282 // ui.Alt-modified key, possibly also modified by ui.Ctrl.
283 k := ctrlModify(r2)
284 k.Mod |= ui.Alt
285 unit = Key(k)
286 }
287 default:
288 k := ctrlModify(r)
289 unit = Key(k)
290 }
291 }
292
293 // ctrlModify determines whether a rune corresponds to a ui.Ctrl-modified key and
294 // returns the ui.Key the rune represents.
295 func ctrlModify(r rune) ui.Key {
296 switch r {
297 case 0x0:
298 return ui.Key{'`', ui.Ctrl} // ^@
299 case 0x1e:
300 return ui.Key{'6', ui.Ctrl} // ^^
301 case 0x1f:
302 return ui.Key{'/', ui.Ctrl} // ^_
303 case ui.Tab, ui.Enter, ui.Backspace: // ^I ^J ^?
304 return ui.Key{r, 0}
305 default:
306 // Regular ui.Ctrl sequences.
307 if 0x1 <= r && r <= 0x1d {
308 return ui.Key{r + 0x40, ui.Ctrl}
309 }
310 }
311 return ui.Key{r, 0}
312 }
313
314 // G3-style key sequences: \eO followed by exactly one character. For instance,
315 // \eOP is ui.F1.
316 var g3Seq = map[rune]rune{
317 'A': ui.Up, 'B': ui.Down, 'C': ui.Right, 'D': ui.Left,
318
319 // ui.F1-ui.F4: xterm, libvte and tmux
320 'P': ui.F1, 'Q': ui.F2,
321 'R': ui.F3, 'S': ui.F4,
322
323 // ui.Home and ui.End: libvte
324 'H': ui.Home, 'F': ui.End,
325 }
326
327 // Tables for CSI-style key sequences, which are \e[ followed by a list of
328 // semicolon-delimited numeric arguments, before being concluded by a
329 // non-numeric, non-semicolon rune.
330
331 // CSI-style key sequences that can be identified based on the ending rune. For
332 // instance, \e[A is ui.Up.
333 var keyByLast = map[rune]ui.Key{
334 'A': {ui.Up, 0}, 'B': {ui.Down, 0},
335 'C': {ui.Right, 0}, 'D': {ui.Left, 0},
336 'H': {ui.Home, 0}, 'F': {ui.End, 0},
337 'Z': {ui.Tab, ui.Shift},
338 }
339
340 // CSI-style key sequences ending with '~' and can be identified based on the
341 // only number argument. For instance, \e[~ is ui.Home. When they are
342 // modified, they take two arguments, first being 1 and second identifying the
343 // modifier (see xtermModify). For instance, \e[1;4~ is Shift-Alt-Home.
344 var keyByNum0 = map[int]rune{
345 1: ui.Home, 2: ui.Insert, 3: ui.Delete, 4: ui.End,
346 5: ui.PageUp, 6: ui.PageDown,
347 11: ui.F1, 12: ui.F2, 13: ui.F3, 14: ui.F4,
348 15: ui.F5, 17: ui.F6, 18: ui.F7, 19: ui.F8,
349 20: ui.F9, 21: ui.F10, 23: ui.F11, 24: ui.F12,
350 }
351
352 // CSI-style key sequences ending with '~', with 27 as the first numeric
353 // argument. For instance, \e[27;9~ is ui.Tab.
354 //
355 // The list is taken blindly from tmux source xterm-keys.c. I don't have a
356 // keyboard-terminal combination that generate such sequences, but assumably
357 // some PC keyboard with a numpad can.
358 var keyByNum2 = map[int]rune{
359 9: '\t', 13: '\r',
360 33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-',
361 46: '.',
362 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7',
363 56: '8', 57: '9',
364 58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';',
365 }
366
367 // parseCSI parses a CSI-style key sequence.
368 func parseCSI(nums []int, last rune, seq string) ui.Key {
369 if k, ok := keyByLast[last]; ok {
370 if len(nums) == 0 {
371 // Unmodified: \e[A (ui.Up)
372 return k
373 } else if len(nums) == 2 && nums[0] == 1 {
374 // Modified: \e[1;5A (ui.Ctrl-ui.Up)
375 return xtermModify(k, nums[1], seq)
376 } else {
377 return ui.Key{}
378 }
379 }
380
381 if last == '~' {
382 if len(nums) == 1 || len(nums) == 2 {
383 if r, ok := keyByNum0[nums[0]]; ok {
384 k := ui.Key{r, 0}
385 if len(nums) == 1 {
386 // Unmodified: \e[5~ (ui.PageUp)
387 return k
388 }
389 // Modified: \e[5;5~ (ui.Ctrl-ui.PageUp)
390 return xtermModify(k, nums[1], seq)
391 }
392 } else if len(nums) == 3 && nums[0] == 27 {
393 if r, ok := keyByNum2[nums[2]]; ok {
394 k := ui.Key{r, 0}
395 return xtermModify(k, nums[1], seq)
396 }
397 }
398 }
399
400 return ui.Key{}
401 }
402
403 func xtermModify(k ui.Key, mod int, seq string) ui.Key {
404 switch mod {
405 case 0:
406 // do nothing
407 case 2:
408 k.Mod |= ui.Shift
409 case 3:
410 k.Mod |= ui.Alt
411 case 4:
412 k.Mod |= ui.Shift | ui.Alt
413 case 5:
414 k.Mod |= ui.Ctrl
415 case 6:
416 k.Mod |= ui.Shift | ui.Ctrl
417 case 7:
418 k.Mod |= ui.Alt | ui.Ctrl
419 case 8:
420 k.Mod |= ui.Shift | ui.Alt | ui.Ctrl
421 default:
422 return ui.Key{}
423 }
424 return k
425 }
426
427 func mouseModify(n int) ui.Mod {
428 var mod ui.Mod
429 if n&4 != 0 {
430 mod |= ui.Shift
431 }
432 if n&8 != 0 {
433 mod |= ui.Alt
434 }
435 if n&16 != 0 {
436 mod |= ui.Ctrl
437 }
438 return mod
439 }
0 package tty
1
2 import (
3 "os"
4 "testing"
5 "time"
6
7 "github.com/elves/elvish/edit/ui"
8 )
9
10 // timeout is the longest time the tests wait between writing something on
11 // the writer and reading it from the reader before declaring that the
12 // reader has a bug.
13 const timeoutInterval = 100 * time.Millisecond
14
15 func timeout() <-chan time.Time {
16 return time.After(timeoutInterval)
17 }
18
19 var (
20 writer *os.File
21 reader *Reader
22 )
23
24 func TestMain(m *testing.M) {
25 r, w, err := os.Pipe()
26 if err != nil {
27 panic("os.Pipe returned error, something is seriously wrong")
28 }
29 defer r.Close()
30 defer w.Close()
31 writer = w
32 reader = NewReader(r)
33 go reader.Run()
34 defer reader.Quit()
35
36 os.Exit(m.Run())
37 }
38
39 var keyTests = []struct {
40 input string
41 want ReadUnit
42 }{
43 // Simple graphical key.
44 {"x", Key{'x', 0}},
45 {"X", Key{'X', 0}},
46 {" ", Key{' ', 0}},
47
48 // Ctrl key.
49 {"\001", Key{'A', ui.Ctrl}},
50 {"\033", Key{'[', ui.Ctrl}},
51
52 // Ctrl-ish keys, but not thought as Ctrl keys by our reader.
53 {"\n", Key{'\n', 0}},
54 {"\t", Key{'\t', 0}},
55 {"\x7f", Key{'\x7f', 0}}, // backspace
56
57 // Alt plus simple graphical key.
58 {"\033a", Key{'a', ui.Alt}},
59 {"\033[", Key{'[', ui.Alt}},
60
61 // G3-style key.
62 {"\033OA", Key{ui.Up, 0}},
63 {"\033OH", Key{ui.Home, 0}},
64
65 // CSI-sequence key identified by the ending rune.
66 {"\033[A", Key{ui.Up, 0}},
67 {"\033[H", Key{ui.Home, 0}},
68 // Test for all possible modifier
69 {"\033[1;2A", Key{ui.Up, ui.Shift}},
70
71 // CSI-sequence key with one argument, always ending in '~'.
72 {"\033[1~", Key{ui.Home, 0}},
73 {"\033[11~", Key{ui.F1, 0}},
74
75 // CSI-sequence key with three arguments and ending in '~'. The first
76 // argument is always 27, the second identifies the modifier and the last
77 // identifies the key.
78 {"\033[27;4;63~", Key{';', ui.Shift | ui.Alt}},
79 }
80
81 func TestKey(t *testing.T) {
82 for _, test := range keyTests {
83 writer.WriteString(test.input)
84 select {
85 case k := <-reader.UnitChan():
86 if k != test.want {
87 t.Errorf("Reader reads key %v, want %v", k, test.want)
88 }
89 case <-timeout():
90 t.Errorf("Reader fails to convert literal key")
91 }
92 }
93 }
0 package ui
1
2 import (
3 "bytes"
4 "errors"
5 "fmt"
6 "strings"
7
8 "github.com/elves/elvish/eval"
9 "github.com/elves/elvish/util"
10 )
11
12 var ErrKeyMustBeString = errors.New("key must be string")
13
14 // Key represents a single keyboard input, typically assembled from a escape
15 // sequence.
16 type Key struct {
17 Rune rune
18 Mod Mod
19 }
20
21 // Default is used in the key binding table to indicate default binding.
22 var Default = Key{DefaultBindingRune, 0}
23
24 // Mod represents a modifier key.
25 type Mod byte
26
27 // Values for Mod.
28 const (
29 // Shift is the shift modifier. It is only applied to special keys (e.g.
30 // Shift-F1). For instance 'A' and '@' which are typically entered with the
31 // shift key pressed, are not considered to be shift-modified.
32 Shift Mod = 1 << iota
33 // Alt is the alt modifier, traditionally known as the meta modifier.
34 Alt
35 Ctrl
36 )
37
38 // Special negative runes to represent function keys, used in the Rune field of
39 // the Key struct.
40 const (
41 F1 rune = -iota - 1
42 F2
43 F3
44 F4
45 F5
46 F6
47 F7
48 F8
49 F9
50 F10
51 F11
52 F12
53
54 Up
55 Down
56 Right
57 Left
58
59 Home
60 Insert
61 Delete
62 End
63 PageUp
64 PageDown
65
66 DefaultBindingRune // A special value to represent default binding.
67
68 // Some function key names are just aliases for their ASCII representation
69
70 Tab = '\t'
71 Enter = '\n'
72 Backspace = 0x7f
73 )
74
75 // functionKey stores the names of function keys, where the name of a function
76 // key k is stored at index -k. For instance, functionKeyNames[-F1] = "F1".
77 var functionKeyNames = [...]string{
78 "(Invalid)",
79 "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
80 "Up", "Down", "Right", "Left",
81 "Home", "Insert", "Delete", "End", "PageUp", "PageDown", "default",
82 }
83
84 // keyNames stores the name of function keys with a positive rune.
85 var keyNames = map[rune]string{
86 Tab: "Tab", Enter: "Enter", Backspace: "Backspace",
87 }
88
89 func (k Key) String() string {
90 var b bytes.Buffer
91 if k.Mod&Ctrl != 0 {
92 b.WriteString("Ctrl-")
93 }
94 if k.Mod&Alt != 0 {
95 b.WriteString("Alt-")
96 }
97 if k.Mod&Shift != 0 {
98 b.WriteString("Shift-")
99 }
100 if k.Rune > 0 {
101 if name, ok := keyNames[k.Rune]; ok {
102 b.WriteString(name)
103 } else {
104 b.WriteRune(k.Rune)
105 }
106 } else {
107 i := int(-k.Rune)
108 if i >= len(functionKeyNames) {
109 fmt.Fprintf(&b, "(bad function key %d)", i)
110 } else {
111 b.WriteString(functionKeyNames[-k.Rune])
112 }
113 }
114 return b.String()
115 }
116
117 // modifierByName maps a name to an modifier. It is used for parsing keys where
118 // the modifier string is first turned to lower case, so that all of C, c,
119 // CTRL, Ctrl and ctrl can represent the Ctrl modifier.
120 var modifierByName = map[string]Mod{
121 "s": Shift, "shift": Shift,
122 "a": Alt, "alt": Alt,
123 "m": Alt, "meta": Alt,
124 "c": Ctrl, "ctrl": Ctrl,
125 }
126
127 // parseKey parses a key. The syntax is:
128 //
129 // Key = { Mod ('+' | '-') } BareKey
130 //
131 // BareKey = FunctionKeyName | SingleRune
132 func parseKey(s string) (Key, error) {
133 var k Key
134 // parse modifiers
135 for {
136 i := strings.IndexAny(s, "+-")
137 if i == -1 {
138 break
139 }
140 modname := strings.ToLower(s[:i])
141 mod, ok := modifierByName[modname]
142 if !ok {
143 return Key{}, fmt.Errorf("bad modifier: %q", modname)
144 }
145 k.Mod |= mod
146 s = s[i+1:]
147 }
148
149 if len(s) == 1 {
150 k.Rune = rune(s[0])
151 // XXX The following assumptions about keys with Ctrl are not checked
152 // with all terminals.
153 if k.Mod&Ctrl != 0 {
154 // Keys with Ctrl as one of the modifiers and a single ASCII letter
155 // as the base rune do not distinguish between cases. So we
156 // normalize the base rune to upper case.
157 if 'a' <= k.Rune && k.Rune <= 'z' {
158 k.Rune += 'A' - 'a'
159 }
160 // Tab is equivalent to Ctrl-I and Ctrl-J is equivalent to Enter.
161 // Normalize Ctrl-I to Tab and Ctrl-J to Enter.
162 if k.Rune == 'I' {
163 k.Mod &= ^Ctrl
164 k.Rune = Tab
165 } else if k.Rune == 'J' {
166 k.Mod &= ^Ctrl
167 k.Rune = Enter
168 }
169 }
170 return k, nil
171 }
172
173 for r, name := range keyNames {
174 if s == name {
175 k.Rune = r
176 return k, nil
177 }
178 }
179
180 for i, name := range functionKeyNames[1:] {
181 if s == name {
182 k.Rune = rune(-i - 1)
183 return k, nil
184 }
185 }
186
187 return Key{}, fmt.Errorf("bad key: %q", s)
188 }
189
190 // ToKey converts an elvish String to a Key. If the passed Value is not a
191 // String, it throws an error.
192 func ToKey(idx eval.Value) Key {
193 skey, ok := idx.(eval.String)
194 if !ok {
195 util.Throw(ErrKeyMustBeString)
196 }
197 key, err := parseKey(string(skey))
198 if err != nil {
199 util.Throw(err)
200 }
201 return key
202 }
0 package ui
1
2 import "testing"
3
4 var parseKeyTests = []struct {
5 s string
6 wantKey Key
7 }{
8 // Alt- keys are case-sensitive.
9 {"a-x", Key{'x', Alt}},
10 {"a-X", Key{'X', Alt}},
11
12 // Ctrl- keys are case-insensitive.
13 {"C-x", Key{'X', Ctrl}},
14 {"C-X", Key{'X', Ctrl}},
15
16 // + is the same as -.
17 {"C+X", Key{'X', Ctrl}},
18
19 // Full names and alternative names can also be used.
20 {"M-x", Key{'x', Alt}},
21 {"Meta-x", Key{'x', Alt}},
22
23 // Multiple modifiers can appear in any order.
24 {"Alt-Ctrl-Delete", Key{Delete, Alt | Ctrl}},
25 {"Ctrl-Alt-Delete", Key{Delete, Alt | Ctrl}},
26 }
27
28 func TestParseKey(t *testing.T) {
29 for _, test := range parseKeyTests {
30 key, err := parseKey(test.s)
31 if key != test.wantKey {
32 t.Errorf("ParseKey(%q) => %v, want %v", test.s, key, test.wantKey)
33 }
34 if err != nil {
35 t.Errorf("ParseKey(%q) => error %v, want nil", test.s, err)
36 }
37 }
38 }
0 package ui
1
2 import (
3 "strings"
4
5 "github.com/elves/elvish/parse"
6 )
7
8 // Styled is a piece of text with style.
9 type Styled struct {
10 Text string
11 Styles Styles
12 }
13
14 var styleTranslationTable = map[string]string{
15 "bold": "1",
16 "dim": "2",
17 "italic": "3",
18 "underlined": "4",
19 "blink": "5",
20 "inverse": "7",
21
22 "black": "30",
23 "red": "31",
24 "green": "32",
25 "yellow": "33",
26 "blue": "34",
27 "magenta": "35",
28 "cyan": "36",
29 "lightgray": "37",
30 "gray": "90",
31 "lightred": "91",
32 "lightgreen": "92",
33 "lightyellow": "93",
34 "lightblue": "94",
35 "lightmagenta": "95",
36 "lightcyan": "96",
37 "white": "97",
38
39 "bg-default": "49",
40 "bg-black": "40",
41 "bg-red": "41",
42 "bg-green": "42",
43 "bg-yellow": "43",
44 "bg-blue": "44",
45 "bg-magenta": "45",
46 "bg-cyan": "46",
47 "bg-lightgray": "47",
48 "bg-gray": "100",
49 "bg-lightred": "101",
50 "bg-lightgreen": "102",
51 "bg-lightyellow": "103",
52 "bg-lightblue": "104",
53 "bg-lightmagenta": "105",
54 "bg-lightcyan": "106",
55 "bg-white": "107",
56 }
57
58 func Unstyled(s string) Styled {
59 return Styled{s, Styles{}}
60 }
61
62 func (s *Styled) Kind() string {
63 return "styled"
64 }
65
66 func (s *Styled) String() string {
67 return "\033[" + s.Styles.String() + "m" + s.Text + "\033[m"
68 }
69
70 func (s *Styled) Repr(indent int) string {
71 return "(le:styled " + parse.Quote(s.Text) + " " + parse.Quote(s.Styles.String()) + ")"
72 }
73
74 type Styles []string
75
76 func JoinStyles(so Styles, st ...Styles) Styles {
77 for _, v := range st {
78 so = append(so, v...)
79 }
80
81 return so
82 }
83
84 func TranslateStyle(s string) string {
85 v, ok := styleTranslationTable[s]
86 if ok {
87 return v
88 }
89 return s
90 }
91
92 func StylesFromString(s string) Styles {
93 var st Styles
94 for _, v := range strings.Split(s, ";") {
95 st = append(st, v)
96 }
97
98 return st
99 }
100
101 func (s Styles) String() string {
102 var o string
103 for i, v := range s {
104 if len(v) > 0 {
105 if i > 0 {
106 o += ";"
107 }
108 o += TranslateStyle(v)
109 }
110 }
111
112 return o
113 }
0 // Package ui contains types that may be used by different editor frontends.
1 package ui
0 package edit
1
2 import (
3 "bytes"
4 "fmt"
5 "os"
6
7 "github.com/elves/elvish/sys"
8 )
9
10 var logWriterDetail = false
11
12 // Writer renders the editor UI.
13 type Writer struct {
14 file *os.File
15 oldBuf *buffer
16 }
17
18 func newWriter(f *os.File) *Writer {
19 writer := &Writer{file: f, oldBuf: &buffer{}}
20 return writer
21 }
22
23 func (w *Writer) resetOldBuf() {
24 w.oldBuf = &buffer{}
25 }
26
27 // deltaPos calculates the escape sequence needed to move the cursor from one
28 // position to another. It use relative movements to move to the destination
29 // line and absolute movement to move to the destination column.
30 func deltaPos(from, to Pos) []byte {
31 buf := new(bytes.Buffer)
32 if from.line < to.line {
33 // move down
34 fmt.Fprintf(buf, "\033[%dB", to.line-from.line)
35 } else if from.line > to.line {
36 // move up
37 fmt.Fprintf(buf, "\033[%dA", from.line-to.line)
38 }
39 fmt.Fprintf(buf, "\033[%dG", to.col+1)
40 return buf.Bytes()
41 }
42
43 // commitBuffer updates the terminal display to reflect current buffer.
44 // TODO Instead of erasing w.oldBuf entirely and then draw buf, compute a
45 // delta between w.oldBuf and buf
46 func (w *Writer) commitBuffer(bufNoti, buf *buffer, fullRefresh bool) error {
47 if buf.width != w.oldBuf.width && w.oldBuf.lines != nil {
48 // Width change, force full refresh
49 w.oldBuf.lines = nil
50 fullRefresh = true
51 }
52
53 bytesBuf := new(bytes.Buffer)
54
55 // Hide cursor.
56 bytesBuf.WriteString("\033[?25l")
57
58 // Rewind cursor
59 if pLine := w.oldBuf.dot.line; pLine > 0 {
60 fmt.Fprintf(bytesBuf, "\033[%dA", pLine)
61 }
62 bytesBuf.WriteString("\r")
63
64 if fullRefresh {
65 // Do an erase.
66 bytesBuf.WriteString("\033[J")
67 }
68
69 // style of last written cell.
70 style := ""
71
72 switchStyle := func(newstyle string) {
73 if newstyle != style {
74 fmt.Fprintf(bytesBuf, "\033[0;%sm", newstyle)
75 style = newstyle
76 }
77 }
78
79 writeCells := func(cs []cell) {
80 for _, c := range cs {
81 if c.width > 0 {
82 switchStyle(c.style)
83 }
84 bytesBuf.WriteString(c.string)
85 }
86 }
87
88 if bufNoti != nil {
89 if logWriterDetail {
90 logger.Printf("going to write %d lines of notifications", len(bufNoti.lines))
91 }
92
93 // Write notifications
94 for _, line := range bufNoti.lines {
95 writeCells(line)
96 switchStyle("")
97 bytesBuf.WriteString("\033[K\n")
98 }
99 // XXX Hacky.
100 if len(w.oldBuf.lines) > 0 {
101 w.oldBuf.lines = w.oldBuf.lines[1:]
102 }
103 }
104
105 if logWriterDetail {
106 logger.Printf("going to write %d lines, oldBuf had %d", len(buf.lines), len(w.oldBuf.lines))
107 }
108
109 for i, line := range buf.lines {
110 if i > 0 {
111 bytesBuf.WriteString("\n")
112 }
113 var j int // First column where buf and oldBuf differ
114 // No need to update current line
115 if !fullRefresh && i < len(w.oldBuf.lines) {
116 var eq bool
117 if eq, j = compareCells(line, w.oldBuf.lines[i]); eq {
118 continue
119 }
120 }
121 // Move to the first differing column if necessary.
122 firstCol := cellsWidth(line[:j])
123 if firstCol != 0 {
124 fmt.Fprintf(bytesBuf, "\033[%dG", firstCol+1)
125 }
126 // Erase the rest of the line if necessary.
127 if !fullRefresh && i < len(w.oldBuf.lines) && j < len(w.oldBuf.lines[i]) {
128 switchStyle("")
129 bytesBuf.WriteString("\033[K")
130 }
131 writeCells(line[j:])
132 }
133 if len(w.oldBuf.lines) > len(buf.lines) && !fullRefresh {
134 // If the old buffer is higher, erase old content.
135 // Note that we cannot simply write \033[J, because if the cursor is
136 // just over the last column -- which is precisely the case if we have a
137 // rprompt, \033[J will also erase the last column.
138 switchStyle("")
139 bytesBuf.WriteString("\n\033[J\033[A")
140 }
141 switchStyle("")
142 cursor := buf.cursor()
143 bytesBuf.Write(deltaPos(cursor, buf.dot))
144
145 // Show cursor.
146 bytesBuf.WriteString("\033[?25h")
147
148 if logWriterDetail {
149 logger.Printf("going to write %q", bytesBuf.String())
150 }
151
152 fd := int(w.file.Fd())
153 if nonblock, _ := sys.GetNonblock(fd); nonblock {
154 sys.SetNonblock(fd, false)
155 defer sys.SetNonblock(fd, true)
156 }
157
158 _, err := w.file.Write(bytesBuf.Bytes())
159 if err != nil {
160 return err
161 }
162
163 w.oldBuf = buf
164 return nil
165 }
166
167 // findWindow finds a window of lines around the selected line in a total
168 // number of height lines, that is at most max lines.
169 func findWindow(height, selected, max int) (low, high int) {
170 if height <= max {
171 // No need for windowing
172 return 0, height
173 }
174 low = selected - max/2
175 high = low + max
176 switch {
177 case low < 0:
178 // Near top of the list, move the window down
179 low = 0
180 high = low + max
181 case high > height:
182 // Near bottom of the list, move the window down
183 high = height
184 low = high - max
185 }
186 return
187 }
188
189 func trimToWindow(s []string, selected, max int) ([]string, int) {
190 low, high := findWindow(len(s), selected, max)
191 return s[low:high], low
192 }
193
194 // refresh redraws the line editor. The dot is passed as an index into text;
195 // the corresponding position will be calculated.
196 func (w *Writer) refresh(es *editorState, fullRefresh bool) error {
197 height, width := sys.GetWinsize(int(w.file.Fd()))
198 er := &editorRenderer{es, height, nil}
199 buf := render(er, width)
200 return w.commitBuffer(er.bufNoti, buf, fullRefresh)
201 }
0 package eval
1
2 import "github.com/elves/elvish/parse"
3
4 type argsWalker struct {
5 cp *compiler
6 form *parse.Form
7 idx int
8 }
9
10 func (cp *compiler) walkArgs(f *parse.Form) *argsWalker {
11 return &argsWalker{cp, f, 0}
12 }
13
14 func (aw *argsWalker) more() bool {
15 return aw.idx < len(aw.form.Args)
16 }
17
18 func (aw *argsWalker) peek() *parse.Compound {
19 if !aw.more() {
20 aw.cp.errorpf(aw.form.End(), aw.form.End(), "need more arguments")
21 }
22 return aw.form.Args[aw.idx]
23 }
24
25 func (aw *argsWalker) next() *parse.Compound {
26 n := aw.peek()
27 aw.idx++
28 return n
29 }
30
31 // nextIs returns whether the next argument's source matches the given text. It
32 // also consumes the argument if it is.
33 func (aw *argsWalker) nextIs(text string) bool {
34 if aw.more() && aw.form.Args[aw.idx].SourceText() == text {
35 aw.idx++
36 return true
37 }
38 return false
39 }
40
41 // nextMustLambda fetches the next argument, raising an error if it is not a
42 // lambda.
43 func (aw *argsWalker) nextMustLambda() *parse.Primary {
44 n := aw.next()
45 if len(n.Indexings) != 1 {
46 aw.cp.errorpf(n.Begin(), n.End(), "must be lambda")
47 }
48 if len(n.Indexings[0].Indicies) != 0 {
49 aw.cp.errorpf(n.Begin(), n.End(), "must be lambda")
50 }
51 pn := n.Indexings[0].Head
52 if pn.Type != parse.Lambda {
53 aw.cp.errorpf(n.Begin(), n.End(), "must be lambda")
54 }
55 return pn
56 }
57
58 func (aw *argsWalker) nextMustLambdaIfAfter(leader string) *parse.Primary {
59 if aw.nextIs(leader) {
60 return aw.nextMustLambda()
61 }
62 return nil
63 }
64
65 func (aw *argsWalker) mustEnd() {
66 if aw.more() {
67 aw.cp.errorpf(aw.form.Args[aw.idx].Begin(), aw.form.End(), "too many arguments")
68 }
69 }
0 package eval
1
2 import "github.com/elves/elvish/parse"
3
4 func (cp *compiler) chunkOp(n *parse.Chunk) Op {
5 cp.compiling(n)
6 return Op{cp.chunk(n), n.Begin(), n.End()}
7 }
8
9 func (cp *compiler) chunkOps(ns []*parse.Chunk) []Op {
10 ops := make([]Op, len(ns))
11 for i, n := range ns {
12 ops[i] = cp.chunkOp(n)
13 }
14 return ops
15 }
16
17 func (cp *compiler) pipelineOp(n *parse.Pipeline) Op {
18 cp.compiling(n)
19 return Op{cp.pipeline(n), n.Begin(), n.End()}
20 }
21
22 func (cp *compiler) pipelineOps(ns []*parse.Pipeline) []Op {
23 ops := make([]Op, len(ns))
24 for i, n := range ns {
25 ops[i] = cp.pipelineOp(n)
26 }
27 return ops
28 }
29
30 func (cp *compiler) formOp(n *parse.Form) Op {
31 cp.compiling(n)
32 return Op{cp.form(n), n.Begin(), n.End()}
33 }
34
35 func (cp *compiler) formOps(ns []*parse.Form) []Op {
36 ops := make([]Op, len(ns))
37 for i, n := range ns {
38 ops[i] = cp.formOp(n)
39 }
40 return ops
41 }
42
43 func (cp *compiler) assignmentOp(n *parse.Assignment) Op {
44 cp.compiling(n)
45 return Op{cp.assignment(n), n.Begin(), n.End()}
46 }
47
48 func (cp *compiler) assignmentOps(ns []*parse.Assignment) []Op {
49 ops := make([]Op, len(ns))
50 for i, n := range ns {
51 ops[i] = cp.assignmentOp(n)
52 }
53 return ops
54 }
55
56 func (cp *compiler) redirOp(n *parse.Redir) Op {
57 cp.compiling(n)
58 return Op{cp.redir(n), n.Begin(), n.End()}
59 }
60
61 func (cp *compiler) redirOps(ns []*parse.Redir) []Op {
62 ops := make([]Op, len(ns))
63 for i, n := range ns {
64 ops[i] = cp.redirOp(n)
65 }
66 return ops
67 }
68
69 func (cp *compiler) compoundOp(n *parse.Compound) ValuesOp {
70 cp.compiling(n)
71 return ValuesOp{cp.compound(n), n.Begin(), n.End()}
72 }
73
74 func (cp *compiler) compoundOps(ns []*parse.Compound) []ValuesOp {
75 ops := make([]ValuesOp, len(ns))
76 for i, n := range ns {
77 ops[i] = cp.compoundOp(n)
78 }
79 return ops
80 }
81
82 func (cp *compiler) arrayOp(n *parse.Array) ValuesOp {
83 cp.compiling(n)
84 return ValuesOp{cp.array(n), n.Begin(), n.End()}
85 }
86
87 func (cp *compiler) arrayOps(ns []*parse.Array) []ValuesOp {
88 ops := make([]ValuesOp, len(ns))
89 for i, n := range ns {
90 ops[i] = cp.arrayOp(n)
91 }
92 return ops
93 }
94
95 func (cp *compiler) indexingOp(n *parse.Indexing) ValuesOp {
96 cp.compiling(n)
97 return ValuesOp{cp.indexing(n), n.Begin(), n.End()}
98 }
99
100 func (cp *compiler) indexingOps(ns []*parse.Indexing) []ValuesOp {
101 ops := make([]ValuesOp, len(ns))
102 for i, n := range ns {
103 ops[i] = cp.indexingOp(n)
104 }
105 return ops
106 }
107
108 func (cp *compiler) primaryOp(n *parse.Primary) ValuesOp {
109 cp.compiling(n)
110 return ValuesOp{cp.primary(n), n.Begin(), n.End()}
111 }
112
113 func (cp *compiler) primaryOps(ns []*parse.Primary) []ValuesOp {
114 ops := make([]ValuesOp, len(ns))
115 for i, n := range ns {
116 ops[i] = cp.primaryOp(n)
117 }
118 return ops
119 }
120
121 func (cp *compiler) listOp(n *parse.Array) ValuesOp {
122 cp.compiling(n)
123 return ValuesOp{cp.list(n), n.Begin(), n.End()}
124 }
125
126 func (cp *compiler) listOps(ns []*parse.Array) []ValuesOp {
127 ops := make([]ValuesOp, len(ns))
128 for i, n := range ns {
129 ops[i] = cp.listOp(n)
130 }
131 return ops
132 }
133
134 func (cp *compiler) exceptionCaptureOp(n *parse.Chunk) ValuesOp {
135 cp.compiling(n)
136 return ValuesOp{cp.exceptionCapture(n), n.Begin(), n.End()}
137 }
138
139 func (cp *compiler) exceptionCaptureOps(ns []*parse.Chunk) []ValuesOp {
140 ops := make([]ValuesOp, len(ns))
141 for i, n := range ns {
142 ops[i] = cp.exceptionCaptureOp(n)
143 }
144 return ops
145 }
146
147 func (cp *compiler) outputCaptureOp(n *parse.Primary) ValuesOp {
148 cp.compiling(n)
149 return ValuesOp{cp.outputCapture(n), n.Begin(), n.End()}
150 }
151
152 func (cp *compiler) outputCaptureOps(ns []*parse.Primary) []ValuesOp {
153 ops := make([]ValuesOp, len(ns))
154 for i, n := range ns {
155 ops[i] = cp.outputCaptureOp(n)
156 }
157 return ops
158 }
159
160 func (cp *compiler) lambdaOp(n *parse.Primary) ValuesOp {
161 cp.compiling(n)
162 return ValuesOp{cp.lambda(n), n.Begin(), n.End()}
163 }
164
165 func (cp *compiler) lambdaOps(ns []*parse.Primary) []ValuesOp {
166 ops := make([]ValuesOp, len(ns))
167 for i, n := range ns {
168 ops[i] = cp.lambdaOp(n)
169 }
170 return ops
171 }
172
173 func (cp *compiler) map_Op(n *parse.Primary) ValuesOp {
174 cp.compiling(n)
175 return ValuesOp{cp.map_(n), n.Begin(), n.End()}
176 }
177
178 func (cp *compiler) map_Ops(ns []*parse.Primary) []ValuesOp {
179 ops := make([]ValuesOp, len(ns))
180 for i, n := range ns {
181 ops[i] = cp.map_Op(n)
182 }
183 return ops
184 }
185
186 func (cp *compiler) bracedOp(n *parse.Primary) ValuesOp {
187 cp.compiling(n)
188 return ValuesOp{cp.braced(n), n.Begin(), n.End()}
189 }
190
191 func (cp *compiler) bracedOps(ns []*parse.Primary) []ValuesOp {
192 ops := make([]ValuesOp, len(ns))
193 for i, n := range ns {
194 ops[i] = cp.bracedOp(n)
195 }
196 return ops
197 }
0 #!/usr/bin/python2.7
1 import re
2 import os
3
4
5 def put_compile_s(out, name, intype, extraargs, outtype):
6 if not outtype.endswith('Func'):
7 return
8 outtype = outtype[:-4]
9 extranames = ', '.join(a.split(' ')[0] for a in extraargs.split(', ')) if extraargs else ''
10 print >>out, '''
11 func (cp *compiler) {name}Op(n {intype}{extraargs}) {outtype} {{
12 cp.compiling(n)
13 return {outtype}{{cp.{name}(n{extranames}), n.Begin(), n.End()}}
14 }}
15
16 func (cp *compiler) {name}Ops(ns []{intype}{extraargs}) []{outtype} {{
17 ops := make([]{outtype}, len(ns))
18 for i, n := range ns {{
19 ops[i] = cp.{name}Op(n{extranames})
20 }}
21 return ops
22 }}
23 '''.format(name=name, intype=intype, outtype=outtype, extraargs=extraargs,
24 extranames=extranames)
25
26
27 def main():
28 out = open('boilerplate.go', 'w')
29 print >>out, '''package eval
30
31 import "github.com/elves/elvish/parse"'''
32 for fname in 'compile_op.go', 'compile_value.go':
33 for line in file(fname):
34 m = re.match(r'^func \(cp \*compiler\) (\w+)\(\w+ ([^,\[\]]+)(.*)\) (\w*OpFunc) {$', line)
35 if m:
36 put_compile_s(out, *m.groups())
37 out.close()
38 os.system('gofmt -w boilerplate.go')
39
40
41 if __name__ == '__main__':
42 main()
0 package eval
1
2 // Builtin functions.
3
4 import (
5 "bytes"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io"
10 "io/ioutil"
11 "math"
12 "math/rand"
13 "net"
14 "os"
15 "path/filepath"
16 "reflect"
17 "regexp"
18 "runtime"
19 "strconv"
20 "strings"
21 "sync"
22 "syscall"
23 "time"
24 "unicode/utf8"
25
26 "github.com/elves/elvish/parse"
27 "github.com/elves/elvish/store/storedefs"
28 "github.com/elves/elvish/sys"
29 "github.com/elves/elvish/util"
30 )
31
32 var builtinFns []*BuiltinFn
33
34 // BuiltinFn is a builtin function.
35 type BuiltinFn struct {
36 Name string
37 Impl func(*EvalCtx, []Value, map[string]Value)
38 }
39
40 var _ CallableValue = &BuiltinFn{}
41
42 // Kind returns "fn".
43 func (*BuiltinFn) Kind() string {
44 return "fn"
45 }
46
47 // Repr returns an opaque representation "<builtin xxx>".
48 func (b *BuiltinFn) Repr(int) string {
49 return "<builtin " + b.Name + ">"
50 }
51
52 // Call calls a builtin function.
53 func (b *BuiltinFn) Call(ec *EvalCtx, args []Value, opts map[string]Value) {
54 b.Impl(ec, args, opts)
55 }
56
57 func init() {
58 // Needed to work around init loop.
59 builtinFns = []*BuiltinFn{
60 // Trivial builtin
61 {"nop", nop},
62
63 // Introspection
64 {"kind-of", kindOf},
65
66 // Generic identity and equality
67 {"is", is},
68 {"eq", eq},
69
70 // Value output
71 {"put", put},
72
73 // Bytes output
74 {"print", print},
75 {"echo", echo},
76 {"pprint", pprint},
77 {"repr", repr},
78
79 // Bytes to value
80 {"slurp", slurp},
81 {"from-lines", fromLines},
82 {"from-json", fromJSON},
83
84 // Value to bytes
85 {"to-lines", toLines},
86 {"to-json", toJSON},
87
88 // Exception and control
89 {"fail", fail},
90 {"multi-error", multiErrorFn},
91 {"return", returnFn},
92 {"break", breakFn},
93 {"continue", continueFn},
94
95 // Misc functional
96 {"constantly", constantly},
97
98 // Misc shell basic
99 {"source", source},
100
101 // Iterations.
102 {"each", each},
103 {"peach", peach},
104 {"repeat", repeat},
105
106 // Sequence primitives
107 {"explode", explode},
108 {"take", take},
109 {"range", rangeFn},
110 {"count", count},
111
112 // String
113 {"joins", joins},
114 {"splits", splits},
115
116 // String operations
117 {"ord", ord},
118 {"base", base},
119 {"wcswidth", wcswidth},
120 {"-override-wcwidth", overrideWcwidth},
121
122 // String predicates
123 {"has-prefix", hasPrefix},
124 {"has-suffix", hasSuffix},
125
126 // String comparison
127 {"<s",
128 wrapStrCompare(func(a, b string) bool { return a < b })},
129 {"<=s",
130 wrapStrCompare(func(a, b string) bool { return a <= b })},
131 {"==s",
132 wrapStrCompare(func(a, b string) bool { return a == b })},
133 {"!=s",
134 wrapStrCompare(func(a, b string) bool { return a != b })},
135 {">s",
136 wrapStrCompare(func(a, b string) bool { return a > b })},
137 {">=s",
138 wrapStrCompare(func(a, b string) bool { return a >= b })},
139
140 // eawk
141 {"eawk", eawk},
142
143 // Directory
144 {"cd", cd},
145 {"dirs", dirs},
146
147 // Path
148 {"path-abs", WrapStringToStringError(filepath.Abs)},
149 {"path-base", WrapStringToString(filepath.Base)},
150 {"path-clean", WrapStringToString(filepath.Clean)},
151 {"path-dir", WrapStringToString(filepath.Dir)},
152 {"path-ext", WrapStringToString(filepath.Ext)},
153 {"eval-symlinks", WrapStringToStringError(filepath.EvalSymlinks)},
154 {"tilde-abbr", tildeAbbr},
155
156 // Boolean operations
157 {"bool", boolFn},
158 {"not", not},
159
160 // Arithmetics
161 {"+", plus},
162 {"-", minus},
163 {"*", times},
164 {"/", slash},
165 {"^", pow},
166 {"%", mod},
167
168 // Random
169 {"rand", randFn},
170 {"randint", randint},
171
172 // Numerical comparison
173 {"<",
174 wrapNumCompare(func(a, b float64) bool { return a < b })},
175 {"<=",
176 wrapNumCompare(func(a, b float64) bool { return a <= b })},
177 {"==",
178 wrapNumCompare(func(a, b float64) bool { return a == b })},
179 {"!=",
180 wrapNumCompare(func(a, b float64) bool { return a != b })},
181 {">",
182 wrapNumCompare(func(a, b float64) bool { return a > b })},
183 {">=",
184 wrapNumCompare(func(a, b float64) bool { return a >= b })},
185
186 // Command resolution
187 {"resolve", resolveFn},
188 {"has-external", hasExternal},
189 {"search-external", searchExternal},
190
191 // File and pipe
192 {"fopen", fopen},
193 {"fclose", fclose},
194 {"pipe", pipe},
195 {"prclose", prclose},
196 {"pwclose", pwclose},
197
198 // Process control
199 {"fg", fg},
200 {"exec", exec},
201 {"exit", exit},
202
203 // Time
204 {"esleep", sleep},
205 {"-time", _time},
206
207 // Debugging
208 {"-gc", _gc},
209 {"-stack", _stack},
210 {"-log", _log},
211
212 {"-ifaddrs", _ifaddrs},
213 }
214 // For rand and randint.
215 rand.Seed(time.Now().UTC().UnixNano())
216 }
217
218 // Errors thrown by builtins.
219 var (
220 ErrArgs = errors.New("args error")
221 ErrInput = errors.New("input error")
222 ErrStoreNotConnected = errors.New("store not connected")
223 ErrNoMatchingDir = errors.New("no matching directory")
224 ErrNotInSameGroup = errors.New("not in the same process group")
225 ErrInterrupted = errors.New("interrupted")
226 )
227
228 func WrapStringToString(f func(string) string) func(*EvalCtx, []Value, map[string]Value) {
229 return func(ec *EvalCtx, args []Value, opts map[string]Value) {
230 TakeNoOpt(opts)
231 s := mustGetOneString(args)
232 ec.ports[1].Chan <- String(f(s))
233 }
234 }
235
236 func WrapStringToStringError(f func(string) (string, error)) func(*EvalCtx, []Value, map[string]Value) {
237 return func(ec *EvalCtx, args []Value, opts map[string]Value) {
238 TakeNoOpt(opts)
239 s := mustGetOneString(args)
240 result, err := f(s)
241 maybeThrow(err)
242 ec.ports[1].Chan <- String(result)
243 }
244 }
245
246 func wrapStrCompare(cmp func(a, b string) bool) func(*EvalCtx, []Value, map[string]Value) {
247 return func(ec *EvalCtx, args []Value, opts map[string]Value) {
248 TakeNoOpt(opts)
249 for _, a := range args {
250 if _, ok := a.(String); !ok {
251 throw(ErrArgs)
252 }
253 }
254 result := true
255 for i := 0; i < len(args)-1; i++ {
256 if !cmp(string(args[i].(String)), string(args[i+1].(String))) {
257 result = false
258 break
259 }
260 }
261 ec.OutputChan() <- Bool(result)
262 }
263 }
264
265 func wrapNumCompare(cmp func(a, b float64) bool) func(*EvalCtx, []Value, map[string]Value) {
266 return func(ec *EvalCtx, args []Value, opts map[string]Value) {
267 TakeNoOpt(opts)
268 floats := make([]float64, len(args))
269 for i, a := range args {
270 f, err := toFloat(a)
271 maybeThrow(err)
272 floats[i] = f
273 }
274 result := true
275 for i := 0; i < len(floats)-1; i++ {
276 if !cmp(floats[i], floats[i+1]) {
277 result = false
278 break
279 }
280 }
281 ec.OutputChan() <- Bool(result)
282 }
283 }
284
285 var errMustBeOneString = errors.New("must be one string argument")
286
287 func mustGetOneString(args []Value) string {
288 if len(args) != 1 {
289 throw(errMustBeOneString)
290 }
291 s, ok := args[0].(String)
292 if !ok {
293 throw(errMustBeOneString)
294 }
295 return string(s)
296 }
297
298 // ScanArgs scans arguments into pointers to supported argument types. If the
299 // arguments cannot be scanned, an error is thrown.
300 func ScanArgs(s []Value, args ...interface{}) {
301 if len(s) != len(args) {
302 throwf("arity mistmatch: want %d arguments, got %d", len(args), len(s))
303 }
304 for i, value := range s {
305 scanArg(value, args[i])
306 }
307 }
308
309 // ScanArgsVariadic is like ScanArgs, but the last element of args should be a
310 // pointer to a slice, and the rest of arguments will be scanned into it.
311 func ScanArgsVariadic(s []Value, args ...interface{}) {
312 if len(s) < len(args)-1 {
313 throwf("arity mistmatch: want at least %d arguments, got %d", len(args)-1, len(s))
314 }
315 ScanArgs(s[:len(args)-1], args[:len(args)-1]...)
316
317 // Scan the rest of arguments into a slice.
318 rest := s[len(args)-1:]
319 dst := reflect.ValueOf(args[len(args)-1])
320 if dst.Kind() != reflect.Ptr || dst.Elem().Kind() != reflect.Slice {
321 throwf("internal bug: %T to ScanArgsVariadic, need pointer to slice", args[len(args)-1])
322 }
323 scanned := reflect.MakeSlice(dst.Elem().Type(), len(rest), len(rest))
324 for i, value := range rest {
325 scanArg(value, scanned.Index(i).Addr().Interface())
326 }
327 reflect.Indirect(dst).Set(scanned)
328 }
329
330 // ScanArgsAndOptionalIterate is like ScanArgs, but the argument can contain an
331 // optional iterable value at the end. The return value is a function that
332 // iterates the iterable value if it exists, or the input otherwise.
333 func ScanArgsAndOptionalIterate(ec *EvalCtx, s []Value, args ...interface{}) func(func(Value)) {
334 switch len(s) {
335 case len(args):
336 ScanArgs(s, args...)
337 return ec.IterateInputs
338 case len(args) + 1:
339 ScanArgs(s[:len(args)], args...)
340 value := s[len(args)]
341 iterable, ok := value.(Iterable)
342 if !ok {
343 throwf("need iterable argument, got %s", value.Kind())
344 }
345 return func(f func(Value)) {
346 iterable.Iterate(func(v Value) bool {
347 f(v)
348 return true
349 })
350 }
351 default:
352 throwf("arity mistmatch: want %d or %d arguments, got %d", len(args), len(args)+1, len(s))
353 return nil
354 }
355 }
356
357 // Opt is a data structure for an option that is intended to be used in ScanOpts.
358 type Opt struct {
359 Name string
360 Ptr interface{}
361 Default Value
362 }
363
364 // ScanOpts scans options from a map.
365 func ScanOpts(m map[string]Value, opts ...Opt) {
366 scanned := make(map[string]bool)
367 for _, opt := range opts {
368 a := opt.Ptr
369 value, ok := m[opt.Name]
370 if !ok {
371 value = opt.Default
372 }
373 scanArg(value, a)
374 scanned[opt.Name] = true
375 }
376 for key := range m {
377 if !scanned[key] {
378 throwf("unknown option %s", parse.Quote(key))
379 }
380 }
381 }
382
383 func scanArg(value Value, a interface{}) {
384 ptr := reflect.ValueOf(a)
385 if ptr.Kind() != reflect.Ptr {
386 throwf("internal bug: %T to ScanArgs, need pointer", a)
387 }
388 v := reflect.Indirect(ptr)
389 switch v.Kind() {
390 case reflect.Int:
391 i, err := toInt(value)
392 maybeThrow(err)
393 v.Set(reflect.ValueOf(i))
394 case reflect.Float64:
395 f, err := toFloat(value)
396 maybeThrow(err)
397 v.Set(reflect.ValueOf(f))
398 default:
399 if reflect.TypeOf(value).ConvertibleTo(v.Type()) {
400 v.Set(reflect.ValueOf(value).Convert(v.Type()))
401 } else {
402 throwf("need %T argument, got %s", v.Interface(), value.Kind())
403 }
404 }
405 }
406
407 func nop(ec *EvalCtx, args []Value, opts map[string]Value) {
408 }
409
410 func kindOf(ec *EvalCtx, args []Value, opts map[string]Value) {
411 TakeNoOpt(opts)
412 out := ec.ports[1].Chan
413 for _, a := range args {
414 out <- String(a.Kind())
415 }
416 }
417
418 func is(ec *EvalCtx, args []Value, opts map[string]Value) {
419 TakeNoOpt(opts)
420 result := true
421 for i := 0; i+1 < len(args); i++ {
422 if args[i] != args[i+1] {
423 result = false
424 break
425 }
426 }
427 ec.OutputChan() <- Bool(result)
428 }
429
430 func eq(ec *EvalCtx, args []Value, opts map[string]Value) {
431 TakeNoOpt(opts)
432 result := true
433 for i := 0; i+1 < len(args); i++ {
434 if !DeepEq(args[i], args[i+1]) {
435 result = false
436 break
437 }
438 }
439 ec.OutputChan() <- Bool(result)
440 }
441
442 func put(ec *EvalCtx, args []Value, opts map[string]Value) {
443 TakeNoOpt(opts)
444 out := ec.ports[1].Chan
445 for _, a := range args {
446 out <- a
447 }
448 }
449
450 func print(ec *EvalCtx, args []Value, opts map[string]Value) {
451 var sepv String
452 ScanOpts(opts, Opt{"sep", &sepv, String(" ")})
453
454 out := ec.ports[1].File
455 sep := string(sepv)
456 for i, arg := range args {
457 if i > 0 {
458 out.WriteString(sep)
459 }
460 out.WriteString(ToString(arg))
461 }
462 }
463
464 func echo(ec *EvalCtx, args []Value, opts map[string]Value) {
465 print(ec, args, opts)
466 ec.ports[1].File.WriteString("\n")
467 }
468
469 func pprint(ec *EvalCtx, args []Value, opts map[string]Value) {
470 TakeNoOpt(opts)
471 out := ec.ports[1].File
472 for _, arg := range args {
473 out.WriteString(arg.Repr(0))
474 out.WriteString("\n")
475 }
476 }
477
478 func repr(ec *EvalCtx, args []Value, opts map[string]Value) {
479 TakeNoOpt(opts)
480 out := ec.ports[1].File
481 for i, arg := range args {
482 if i > 0 {
483 out.WriteString(" ")
484 }
485 out.WriteString(arg.Repr(NoPretty))
486 }
487 out.WriteString("\n")
488 }
489
490 func slurp(ec *EvalCtx, args []Value, opts map[string]Value) {
491 TakeNoArg(args)
492 TakeNoOpt(opts)
493
494 in := ec.ports[0].File
495 out := ec.ports[1].Chan
496
497 all, err := ioutil.ReadAll(in)
498 if err != nil {
499 b, err := sys.GetNonblock(0)
500 fmt.Println("stdin is nonblock:", b, err)
501 fmt.Println("stdin is stdin:", in == os.Stdin)
502 }
503 maybeThrow(err)
504 out <- String(string(all))
505 }
506
507 func fromLines(ec *EvalCtx, args []Value, opts map[string]Value) {
508 TakeNoArg(args)
509 TakeNoOpt(opts)
510
511 in := ec.ports[0].File
512 out := ec.ports[1].Chan
513
514 linesToChan(in, out)
515 }
516
517 // fromJSON parses a stream of JSON data into Value's.
518 func fromJSON(ec *EvalCtx, args []Value, opts map[string]Value) {
519 TakeNoArg(args)
520 TakeNoOpt(opts)
521
522 in := ec.ports[0].File
523 out := ec.ports[1].Chan
524
525 dec := json.NewDecoder(in)
526 var v interface{}
527 for {
528 err := dec.Decode(&v)
529 if err != nil {
530 if err == io.EOF {
531 return
532 }
533 throw(err)
534 }
535 out <- FromJSONInterface(v)
536 }
537 }
538
539 func toLines(ec *EvalCtx, args []Value, opts map[string]Value) {
540 iterate := ScanArgsAndOptionalIterate(ec, args)
541 TakeNoOpt(opts)
542
543 out := ec.ports[1].File
544
545 iterate(func(v Value) {
546 fmt.Fprintln(out, ToString(v))
547 })
548 }
549
550 // toJSON converts a stream of Value's to JSON data.
551 func toJSON(ec *EvalCtx, args []Value, opts map[string]Value) {
552 iterate := ScanArgsAndOptionalIterate(ec, args)
553 TakeNoOpt(opts)
554
555 out := ec.ports[1].File
556
557 enc := json.NewEncoder(out)
558 iterate(func(v Value) {
559 err := enc.Encode(v)
560 maybeThrow(err)
561 })
562 }
563
564 func fail(ec *EvalCtx, args []Value, opts map[string]Value) {
565 var msg String
566 ScanArgs(args, &msg)
567 TakeNoOpt(opts)
568
569 throw(errors.New(string(msg)))
570 }
571
572 func multiErrorFn(ec *EvalCtx, args []Value, opts map[string]Value) {
573 var excs []*Exception
574 ScanArgsVariadic(args, &excs)
575 TakeNoOpt(opts)
576
577 throw(PipelineError{excs})
578 }
579
580 func returnFn(ec *EvalCtx, args []Value, opts map[string]Value) {
581 TakeNoArg(args)
582 TakeNoOpt(opts)
583
584 throw(Return)
585 }
586
587 func breakFn(ec *EvalCtx, args []Value, opts map[string]Value) {
588 TakeNoArg(args)
589 TakeNoOpt(opts)
590
591 throw(Break)
592 }
593
594 func continueFn(ec *EvalCtx, args []Value, opts map[string]Value) {
595 TakeNoArg(args)
596 TakeNoOpt(opts)
597
598 throw(Continue)
599 }
600
601 func constantly(ec *EvalCtx, args []Value, opts map[string]Value) {
602 TakeNoOpt(opts)
603
604 out := ec.ports[1].Chan
605 // XXX Repr of this fn is not right
606 out <- &BuiltinFn{
607 "created by constantly",
608 func(ec *EvalCtx, a []Value, o map[string]Value) {
609 TakeNoOpt(o)
610 if len(a) != 0 {
611 throw(ErrArgs)
612 }
613 out := ec.ports[1].Chan
614 for _, v := range args {
615 out <- v
616 }
617 },
618 }
619 }
620
621 func source(ec *EvalCtx, args []Value, opts map[string]Value) {
622 var fname String
623 ScanArgs(args, &fname)
624 ScanOpts(opts)
625
626 ec.Source(string(fname))
627 }
628
629 // each takes a single closure and applies it to all input values.
630 func each(ec *EvalCtx, args []Value, opts map[string]Value) {
631 var f CallableValue
632 iterate := ScanArgsAndOptionalIterate(ec, args, &f)
633 TakeNoOpt(opts)
634
635 broken := false
636 iterate(func(v Value) {
637 if broken {
638 return
639 }
640 // NOTE We don't have the position range of the closure in the source.
641 // Ideally, it should be kept in the Closure itself.
642 newec := ec.fork("closure of each")
643 newec.ports[0] = DevNullClosedChan
644 ex := newec.PCall(f, []Value{v}, NoOpts)
645 ClosePorts(newec.ports)
646
647 if ex != nil {
648 switch ex.(*Exception).Cause {
649 case nil, Continue:
650 // nop
651 case Break:
652 broken = true
653 default:
654 throw(ex)
655 }
656 }
657 })
658 }
659
660 // peach takes a single closure and applies it to all input values in parallel.
661 func peach(ec *EvalCtx, args []Value, opts map[string]Value) {
662 var f CallableValue
663 iterate := ScanArgsAndOptionalIterate(ec, args, &f)
664 TakeNoOpt(opts)
665
666 var w sync.WaitGroup
667 broken := false
668 var err error
669 iterate(func(v Value) {
670 if broken || err != nil {
671 return
672 }
673 w.Add(1)
674 go func() {
675 // NOTE We don't have the position range of the closure in the source.
676 // Ideally, it should be kept in the Closure itself.
677 newec := ec.fork("closure of each")
678 newec.ports[0] = DevNullClosedChan
679 ex := newec.PCall(f, []Value{v}, NoOpts)
680 ClosePorts(newec.ports)
681
682 if ex != nil {
683 switch ex.(*Exception).Cause {
684 case nil, Continue:
685 // nop
686 case Break:
687 broken = true
688 default:
689 err = ex
690 }
691 }
692 w.Done()
693 }()
694 })
695 w.Wait()
696 maybeThrow(err)
697 }
698
699 func repeat(ec *EvalCtx, args []Value, opts map[string]Value) {
700 var (
701 n int
702 v Value
703 )
704 ScanArgs(args, &n, &v)
705 TakeNoOpt(opts)
706
707 out := ec.OutputChan()
708 for i := 0; i < n; i++ {
709 out <- v
710 }
711 }
712
713 // explode puts each element of the argument.
714 func explode(ec *EvalCtx, args []Value, opts map[string]Value) {
715 var v IterableValue
716 ScanArgs(args, &v)
717 TakeNoOpt(opts)
718
719 out := ec.ports[1].Chan
720 v.Iterate(func(e Value) bool {
721 out <- e
722 return true
723 })
724 }
725
726 func take(ec *EvalCtx, args []Value, opts map[string]Value) {
727 var n int
728 iterate := ScanArgsAndOptionalIterate(ec, args, &n)
729 TakeNoOpt(opts)
730
731 out := ec.ports[1].Chan
732 i := 0
733 iterate(func(v Value) {
734 if i < n {
735 out <- v
736 }
737 i++
738 })
739 }
740
741 func rangeFn(ec *EvalCtx, args []Value, opts map[string]Value) {
742 var step float64
743 ScanOpts(opts, Opt{"step", &step, String("1")})
744
745 var lower, upper float64
746 var err error
747
748 switch len(args) {
749 case 1:
750 upper, err = toFloat(args[0])
751 maybeThrow(err)
752 case 2:
753 lower, err = toFloat(args[0])
754 maybeThrow(err)
755 upper, err = toFloat(args[1])
756 maybeThrow(err)
757 default:
758 throw(ErrArgs)
759 }
760
761 out := ec.ports[1].Chan
762 for i := lower; i < upper; i += step {
763 out <- String(fmt.Sprintf("%g", i))
764 }
765 }
766
767 func count(ec *EvalCtx, args []Value, opts map[string]Value) {
768 TakeNoOpt(opts)
769
770 var n int
771 switch len(args) {
772 case 0:
773 // Count inputs.
774 ec.IterateInputs(func(Value) {
775 n++
776 })
777 case 1:
778 // Get length of argument.
779 v := args[0]
780 if lener, ok := v.(Lener); ok {
781 n = lener.Len()
782 } else if iterator, ok := v.(Iterable); ok {
783 iterator.Iterate(func(Value) bool {
784 n++
785 return true
786 })
787 } else {
788 throw(fmt.Errorf("cannot get length of a %s", v.Kind()))
789 }
790 default:
791 throw(errors.New("want 0 or 1 argument"))
792 }
793 ec.ports[1].Chan <- String(strconv.Itoa(n))
794 }
795
796 // joins joins all input strings with a delimiter.
797 func joins(ec *EvalCtx, args []Value, opts map[string]Value) {
798 var sepv String
799 iterate := ScanArgsAndOptionalIterate(ec, args, &sepv)
800 sep := string(sepv)
801 TakeNoOpt(opts)
802
803 var buf bytes.Buffer
804 iterate(func(v Value) {
805 if s, ok := v.(String); ok {
806 if buf.Len() > 0 {
807 buf.WriteString(sep)
808 }
809 buf.WriteString(string(s))
810 } else {
811 throwf("join wants string input, got %s", v.Kind())
812 }
813 })
814 out := ec.ports[1].Chan
815 out <- String(buf.String())
816 }
817
818 // splits splits an argument strings by a delimiter and writes all pieces.
819 func splits(ec *EvalCtx, args []Value, opts map[string]Value) {
820 var s, sep String
821 ScanArgs(args, &s)
822 ScanOpts(opts, Opt{"sep", &sep, String("")})
823
824 out := ec.ports[1].Chan
825 parts := strings.Split(string(s), string(sep))
826 for _, p := range parts {
827 out <- String(p)
828 }
829 }
830
831 func ord(ec *EvalCtx, args []Value, opts map[string]Value) {
832 var s String
833 ScanArgs(args, &s)
834 TakeNoOpt(opts)
835
836 out := ec.ports[1].Chan
837 for _, r := range s {
838 out <- String(fmt.Sprintf("0x%x", r))
839 }
840 }
841
842 // ErrBadBase is thrown by the "base" builtin if the base is smaller than 2 or
843 // greater than 36.
844 var ErrBadBase = errors.New("bad base")
845
846 func base(ec *EvalCtx, args []Value, opts map[string]Value) {
847 var (
848 b int
849 nums []int
850 )
851 ScanArgsVariadic(args, &b, &nums)
852 TakeNoOpt(opts)
853
854 if b < 2 || b > 36 {
855 throw(ErrBadBase)
856 }
857
858 out := ec.ports[1].Chan
859
860 for _, num := range nums {
861 out <- String(strconv.FormatInt(int64(num), b))
862 }
863 }
864
865 func wcswidth(ec *EvalCtx, args []Value, opts map[string]Value) {
866 var s String
867 ScanArgs(args, &s)
868 TakeNoOpt(opts)
869
870 out := ec.ports[1].Chan
871 out <- String(strconv.Itoa(util.Wcswidth(string(s))))
872 }
873
874 func overrideWcwidth(ec *EvalCtx, args []Value, opts map[string]Value) {
875 var (
876 s String
877 w int
878 )
879 ScanArgs(args, &s, &w)
880 TakeNoOpt(opts)
881
882 r, err := toRune(s)
883 maybeThrow(err)
884 util.OverrideWcwidth(r, w)
885 }
886
887 func hasPrefix(ec *EvalCtx, args []Value, opts map[string]Value) {
888 var s, prefix String
889 ScanArgs(args, &s, &prefix)
890 TakeNoOpt(opts)
891
892 ec.OutputChan() <- Bool(strings.HasPrefix(string(s), string(prefix)))
893 }
894
895 func hasSuffix(ec *EvalCtx, args []Value, opts map[string]Value) {
896 var s, suffix String
897 ScanArgs(args, &s, &suffix)
898 TakeNoOpt(opts)
899
900 ec.OutputChan() <- Bool(strings.HasSuffix(string(s), string(suffix)))
901 }
902
903 var eawkWordSep = regexp.MustCompile("[ \t]+")
904
905 // eawk takes a function. For each line in the input stream, it calls the
906 // function with the line and the words in the line. The words are found by
907 // stripping the line and splitting the line by whitespaces. The function may
908 // call break and continue. Overall this provides a similar functionality to
909 // awk, hence the name.
910 func eawk(ec *EvalCtx, args []Value, opts map[string]Value) {
911 var f CallableValue
912 iterate := ScanArgsAndOptionalIterate(ec, args, &f)
913 TakeNoOpt(opts)
914
915 broken := false
916 iterate(func(v Value) {
917 if broken {
918 return
919 }
920 line, ok := v.(String)
921 if !ok {
922 throw(ErrInput)
923 }
924 args := []Value{line}
925 for _, field := range eawkWordSep.Split(strings.Trim(string(line), " \t"), -1) {
926 args = append(args, String(field))
927 }
928
929 newec := ec.fork("fn of eawk")
930 // TODO: Close port 0 of newec.
931 ex := newec.PCall(f, args, NoOpts)
932 ClosePorts(newec.ports)
933
934 if ex != nil {
935 switch ex.(*Exception).Cause {
936 case nil, Continue:
937 // nop
938 case Break:
939 broken = true
940 default:
941 throw(ex)
942 }
943 }
944 })
945 }
946
947 func cd(ec *EvalCtx, args []Value, opts map[string]Value) {
948 TakeNoOpt(opts)
949
950 var dir string
951 if len(args) == 0 {
952 dir = mustGetHome("")
953 } else if len(args) == 1 {
954 dir = ToString(args[0])
955 } else {
956 throw(ErrArgs)
957 }
958
959 cdInner(dir, ec)
960 }
961
962 func cdInner(dir string, ec *EvalCtx) {
963 maybeThrow(Chdir(dir, ec.Daemon))
964 }
965
966 var dirFieldNames = []string{"path", "score"}
967
968 func dirs(ec *EvalCtx, args []Value, opts map[string]Value) {
969 TakeNoArg(args)
970 TakeNoOpt(opts)
971
972 if ec.Daemon == nil {
973 throw(ErrStoreNotConnected)
974 }
975 dirs, err := ec.Daemon.Dirs(storedefs.NoBlacklist)
976 if err != nil {
977 throw(errors.New("store error: " + err.Error()))
978 }
979 out := ec.ports[1].Chan
980 for _, dir := range dirs {
981 out <- &Struct{dirFieldNames, []Variable{
982 NewRoVariable(String(dir.Path)),
983 NewRoVariable(String(fmt.Sprint(dir.Score))),
984 }}
985 }
986 }
987
988 func tildeAbbr(ec *EvalCtx, args []Value, opts map[string]Value) {
989 var pathv String
990 ScanArgs(args, &pathv)
991 path := string(pathv)
992 TakeNoOpt(opts)
993
994 out := ec.ports[1].Chan
995 out <- String(util.TildeAbbr(path))
996 }
997
998 func boolFn(ec *EvalCtx, args []Value, opts map[string]Value) {
999 var v Value
1000 ScanArgs(args, &v)
1001 TakeNoOpt(opts)
1002
1003 ec.OutputChan() <- Bool(ToBool(v))
1004 }
1005
1006 func not(ec *EvalCtx, args []Value, opts map[string]Value) {
1007 var v Value
1008 ScanArgs(args, &v)
1009 TakeNoOpt(opts)
1010
1011 ec.OutputChan() <- Bool(!ToBool(v))
1012 }
1013
1014 func plus(ec *EvalCtx, args []Value, opts map[string]Value) {
1015 var nums []float64
1016 ScanArgsVariadic(args, &nums)
1017 TakeNoOpt(opts)
1018
1019 out := ec.ports[1].Chan
1020 sum := 0.0
1021 for _, f := range nums {
1022 sum += f
1023 }
1024 out <- String(fmt.Sprintf("%g", sum))
1025 }
1026
1027 func minus(ec *EvalCtx, args []Value, opts map[string]Value) {
1028 var (
1029 sum float64
1030 nums []float64
1031 )
1032 ScanArgsVariadic(args, &sum, &nums)
1033 TakeNoOpt(opts)
1034
1035 out := ec.ports[1].Chan
1036 if len(nums) == 0 {
1037 // Unary -
1038 sum = -sum
1039 } else {
1040 for _, f := range nums {
1041 sum -= f
1042 }
1043 }
1044 out <- String(fmt.Sprintf("%g", sum))
1045 }
1046
1047 func times(ec *EvalCtx, args []Value, opts map[string]Value) {
1048 var nums []float64
1049 ScanArgsVariadic(args, &nums)
1050 TakeNoOpt(opts)
1051
1052 out := ec.ports[1].Chan
1053 prod := 1.0
1054 for _, f := range nums {
1055 prod *= f
1056 }
1057 out <- String(fmt.Sprintf("%g", prod))
1058 }
1059
1060 func slash(ec *EvalCtx, args []Value, opts map[string]Value) {
1061 TakeNoOpt(opts)
1062 if len(args) == 0 {
1063 // cd /
1064 cdInner("/", ec)
1065 return
1066 }
1067 // Division
1068 divide(ec, args, opts)
1069 }
1070
1071 func divide(ec *EvalCtx, args []Value, opts map[string]Value) {
1072 var (
1073 prod float64
1074 nums []float64
1075 )
1076 ScanArgsVariadic(args, &prod, &nums)
1077 TakeNoOpt(opts)
1078
1079 out := ec.ports[1].Chan
1080 for _, f := range nums {
1081 prod /= f
1082 }
1083 out <- String(fmt.Sprintf("%g", prod))
1084 }
1085
1086 func pow(ec *EvalCtx, args []Value, opts map[string]Value) {
1087 var b, p float64
1088 ScanArgs(args, &b, &p)
1089 TakeNoOpt(opts)
1090
1091 out := ec.ports[1].Chan
1092 out <- String(fmt.Sprintf("%g", math.Pow(b, p)))
1093 }
1094
1095 func mod(ec *EvalCtx, args []Value, opts map[string]Value) {
1096 var a, b int
1097 ScanArgs(args, &a, &b)
1098 TakeNoOpt(opts)
1099
1100 out := ec.ports[1].Chan
1101 out <- String(strconv.Itoa(a % b))
1102 }
1103
1104 func randFn(ec *EvalCtx, args []Value, opts map[string]Value) {
1105 TakeNoArg(args)
1106 TakeNoOpt(opts)
1107
1108 out := ec.ports[1].Chan
1109 out <- String(fmt.Sprint(rand.Float64()))
1110 }
1111
1112 func randint(ec *EvalCtx, args []Value, opts map[string]Value) {
1113 var low, high int
1114 ScanArgs(args, &low, &high)
1115 TakeNoOpt(opts)
1116
1117 if low >= high {
1118 throw(ErrArgs)
1119 }
1120 out := ec.ports[1].Chan
1121 i := low + rand.Intn(high-low)
1122 out <- String(strconv.Itoa(i))
1123 }
1124
1125 func resolveFn(ec *EvalCtx, args []Value, opts map[string]Value) {
1126 var cmd String
1127 ScanArgs(args, &cmd)
1128 TakeNoOpt(opts)
1129
1130 out := ec.ports[1].Chan
1131 out <- resolve(string(cmd), ec)
1132 }
1133
1134 func hasExternal(ec *EvalCtx, args []Value, opts map[string]Value) {
1135 var cmd String
1136 ScanArgs(args, &cmd)
1137 TakeNoOpt(opts)
1138
1139 _, err := ec.Search(string(cmd))
1140 ec.OutputChan() <- Bool(err == nil)
1141 }
1142
1143 func searchExternal(ec *EvalCtx, args []Value, opts map[string]Value) {
1144 var cmd String
1145 ScanArgs(args, &cmd)
1146 TakeNoOpt(opts)
1147
1148 path, err := ec.Search(string(cmd))
1149 maybeThrow(err)
1150
1151 out := ec.ports[1].Chan
1152 out <- String(path)
1153 }
1154
1155 func fopen(ec *EvalCtx, args []Value, opts map[string]Value) {
1156 var namev String
1157 ScanArgs(args, &namev)
1158 name := string(namev)
1159 TakeNoOpt(opts)
1160
1161 // TODO support opening files for writing etc as well.
1162 out := ec.ports[1].Chan
1163 f, err := os.Open(name)
1164 maybeThrow(err)
1165 out <- File{f}
1166 }
1167
1168 func fclose(ec *EvalCtx, args []Value, opts map[string]Value) {
1169 var f File
1170 ScanArgs(args, &f)
1171 TakeNoOpt(opts)
1172
1173 maybeThrow(f.inner.Close())
1174 }
1175
1176 func pipe(ec *EvalCtx, args []Value, opts map[string]Value) {
1177 TakeNoArg(args)
1178 TakeNoOpt(opts)
1179
1180 r, w, err := os.Pipe()
1181 out := ec.ports[1].Chan
1182 maybeThrow(err)
1183 out <- Pipe{r, w}
1184 }
1185
1186 func prclose(ec *EvalCtx, args []Value, opts map[string]Value) {
1187 var p Pipe
1188 ScanArgs(args, &p)
1189 TakeNoOpt(opts)
1190
1191 maybeThrow(p.r.Close())
1192 }
1193
1194 func pwclose(ec *EvalCtx, args []Value, opts map[string]Value) {
1195 var p Pipe
1196 ScanArgs(args, &p)
1197 TakeNoOpt(opts)
1198
1199 maybeThrow(p.w.Close())
1200 }
1201
1202 func fg(ec *EvalCtx, args []Value, opts map[string]Value) {
1203 var pids []int
1204 ScanArgsVariadic(args, &pids)
1205 TakeNoOpt(opts)
1206
1207 if len(pids) == 0 {
1208 throw(ErrArgs)
1209 }
1210 var thepgid int
1211 for i, pid := range pids {
1212 pgid, err := syscall.Getpgid(pid)
1213 maybeThrow(err)
1214 if i == 0 {
1215 thepgid = pgid
1216 } else if pgid != thepgid {
1217 throw(ErrNotInSameGroup)
1218 }
1219 }
1220
1221 err := sys.Tcsetpgrp(0, thepgid)
1222 maybeThrow(err)
1223
1224 errors := make([]*Exception, len(pids))
1225
1226 for i, pid := range pids {
1227 err := syscall.Kill(pid, syscall.SIGCONT)
1228 if err != nil {
1229 errors[i] = &Exception{err, nil}
1230 }
1231 }
1232
1233 for i, pid := range pids {
1234 if errors[i] != nil {
1235 continue
1236 }
1237 var ws syscall.WaitStatus
1238 _, err = syscall.Wait4(pid, &ws, syscall.WUNTRACED, nil)
1239 if err != nil {
1240 errors[i] = &Exception{err, nil}
1241 } else {
1242 // TODO find command name
1243 errors[i] = &Exception{NewExternalCmdExit(fmt.Sprintf("(pid %d)", pid), ws, pid), nil}
1244 }
1245 }
1246
1247 maybeThrow(ComposeExceptionsFromPipeline(errors))
1248 }
1249
1250 func exec(ec *EvalCtx, args []Value, opts map[string]Value) {
1251 TakeNoOpt(opts)
1252
1253 var argstrings []string
1254 if len(args) == 0 {
1255 argstrings = []string{"elvish"}
1256 } else {
1257 argstrings = make([]string, len(args))
1258 for i, a := range args {
1259 argstrings[i] = ToString(a)
1260 }
1261 }
1262
1263 var err error
1264 argstrings[0], err = ec.Search(argstrings[0])
1265 maybeThrow(err)
1266
1267 preExit(ec)
1268
1269 err = syscall.Exec(argstrings[0], argstrings, os.Environ())
1270 maybeThrow(err)
1271 }
1272
1273 func exit(ec *EvalCtx, args []Value, opts map[string]Value) {
1274 var codes []int
1275 ScanArgsVariadic(args, &codes)
1276 TakeNoOpt(opts)
1277
1278 doexit := func(i int) {
1279 preExit(ec)
1280 os.Exit(i)
1281 }
1282 switch len(codes) {
1283 case 0:
1284 doexit(0)
1285 case 1:
1286 doexit(codes[0])
1287 default:
1288 throw(ErrArgs)
1289 }
1290 }
1291
1292 func sleep(ec *EvalCtx, args []Value, opts map[string]Value) {
1293 var t float64
1294 ScanArgs(args, &t)
1295 TakeNoOpt(opts)
1296
1297 d := time.Duration(float64(time.Second) * t)
1298 select {
1299 case <-ec.Interrupts():
1300 throw(ErrInterrupted)
1301 case <-time.After(d):
1302 }
1303 }
1304
1305 func _time(ec *EvalCtx, args []Value, opts map[string]Value) {
1306 var f CallableValue
1307 ScanArgs(args, &f)
1308 TakeNoOpt(opts)
1309
1310 t0 := time.Now()
1311 f.Call(ec, NoArgs, NoOpts)
1312 t1 := time.Now()
1313
1314 dt := t1.Sub(t0)
1315 fmt.Fprintln(ec.ports[1].File, dt)
1316 }
1317
1318 func _gc(ec *EvalCtx, args []Value, opts map[string]Value) {
1319 TakeNoArg(args)
1320 TakeNoOpt(opts)
1321
1322 runtime.GC()
1323 }
1324
1325 func _stack(ec *EvalCtx, args []Value, opts map[string]Value) {
1326 TakeNoArg(args)
1327 TakeNoOpt(opts)
1328
1329 out := ec.ports[1].File
1330 // XXX dup with main.go
1331 buf := make([]byte, 1024)
1332 for runtime.Stack(buf, true) == cap(buf) {
1333 buf = make([]byte, cap(buf)*2)
1334 }
1335 out.Write(buf)
1336 }
1337
1338 func _log(ec *EvalCtx, args []Value, opts map[string]Value) {
1339 var fnamev String
1340 ScanArgs(args, &fnamev)
1341 fname := string(fnamev)
1342 TakeNoOpt(opts)
1343
1344 maybeThrow(util.SetOutputFile(fname))
1345 }
1346
1347 func _ifaddrs(ec *EvalCtx, args []Value, opts map[string]Value) {
1348 TakeNoArg(args)
1349 TakeNoOpt(opts)
1350
1351 out := ec.ports[1].Chan
1352
1353 addrs, err := net.InterfaceAddrs()
1354 maybeThrow(err)
1355 for _, addr := range addrs {
1356 out <- String(addr.String())
1357 }
1358 }
1359
1360 func toFloat(arg Value) (float64, error) {
1361 if _, ok := arg.(String); !ok {
1362 return 0, fmt.Errorf("must be string")
1363 }
1364 s := string(arg.(String))
1365 num, err := strconv.ParseFloat(s, 64)
1366 if err != nil {
1367 num, err2 := strconv.ParseInt(s, 0, 64)
1368 if err2 != nil {
1369 return 0, err
1370 }
1371 return float64(num), nil
1372 }
1373 return num, nil
1374 }
1375
1376 func toInt(arg Value) (int, error) {
1377 arg, ok := arg.(String)
1378 if !ok {
1379 return 0, fmt.Errorf("must be string")
1380 }
1381 num, err := strconv.ParseInt(string(arg.(String)), 0, 0)
1382 if err != nil {
1383 return 0, err
1384 }
1385 return int(num), nil
1386 }
1387
1388 func toRune(arg Value) (rune, error) {
1389 ss, ok := arg.(String)
1390 if !ok {
1391 return -1, fmt.Errorf("must be string")
1392 }
1393 s := string(ss)
1394 r, size := utf8.DecodeRuneInString(s)
1395 if r == utf8.RuneError {
1396 return -1, fmt.Errorf("string is not valid UTF-8")
1397 }
1398 if size != len(s) {
1399 return -1, fmt.Errorf("string has multiple runes")
1400 }
1401 return r, nil
1402 }
1403
1404 func preExit(ec *EvalCtx) {
1405 err := ec.Daemon.Close()
1406 if err != nil {
1407 fmt.Fprintln(os.Stderr, err)
1408 }
1409 }
0 package eval
1
2 import (
3 "strconv"
4 "strings"
5 "syscall"
6
7 "github.com/elves/elvish/daemon/api"
8 )
9
10 func makeBuiltinNamespace(daemon *api.Client) Namespace {
11 ns := Namespace{
12 "pid": NewRoVariable(String(strconv.Itoa(syscall.Getpid()))),
13 "ok": NewRoVariable(OK),
14 "true": NewRoVariable(Bool(true)),
15 "false": NewRoVariable(Bool(false)),
16 "paths": &EnvPathList{envName: "PATH"},
17 "pwd": PwdVariable{daemon},
18 }
19 AddBuiltinFns(ns, builtinFns...)
20 return ns
21 }
22
23 // AddBuiltinFns adds builtin functions to a namespace.
24 func AddBuiltinFns(ns Namespace, fns ...*BuiltinFn) {
25 for _, b := range fns {
26 name := b.Name
27 if i := strings.IndexRune(b.Name, ':'); i != -1 {
28 name = b.Name[i+1:]
29 }
30 ns[FnPrefix+name] = NewRoVariable(b)
31 }
32 }
0 package eval
1
2 // Builtin special forms. Special forms behave mostly like ordinary commands -
3 // they are valid commands syntactically, and can take part in pipelines - but
4 // they have special rules for the evaluation of their arguments and can affect
5 // the compilation phase (whereas ordinary commands can only affect the
6 // evaluation phase).
7 //
8 // For instance, the "and" special form evaluates its arguments from left to
9 // right, and stops as soon as one booleanly false value is obtained: the
10 // command "and $false (fail haha)" does not produce an exception.
11 //
12 // As another instance, the "del" special form removes a variable, affecting the
13 // compiler.
14 //
15 // Flow control structures are also implemented as special forms in elvish, with
16 // closures functioning as code blocks.
17
18 import (
19 "errors"
20 "fmt"
21 "os"
22 "strings"
23
24 "github.com/elves/elvish/parse"
25 )
26
27 type compileBuiltin func(*compiler, *parse.Form) OpFunc
28
29 // ErrNoDataDir is thrown by the "use" special form when there is no data
30 // directory.
31 var ErrNoDataDir = errors.New("There is no data directory")
32
33 var builtinSpecials map[string]compileBuiltin
34
35 // IsBuiltinSpecial is the set of all names of builtin special forms. It is
36 // intended for external consumption, e.g. the syntax highlighter.
37 var IsBuiltinSpecial = map[string]bool{}
38
39 func init() {
40 // Needed to avoid initialization loop
41 builtinSpecials = map[string]compileBuiltin{
42 "del": compileDel,
43 "fn": compileFn,
44 "use": compileUse,
45 "and": compileAnd,
46 "or": compileOr,
47 "if": compileIf,
48 "while": compileWhile,
49 "for": compileFor,
50 "try": compileTry,
51 }
52 for name := range builtinSpecials {
53 IsBuiltinSpecial[name] = true
54 }
55 }
56
57 // DelForm = 'del' { VariablePrimary }
58 func compileDel(cp *compiler, fn *parse.Form) OpFunc {
59 // Do conventional compiling of all compound expressions, including
60 // ensuring that variables can be resolved
61 var names, envNames []string
62 for _, cn := range fn.Args {
63 cp.compiling(cn)
64 qname := mustString(cp, cn, "should be a literal variable name")
65 explode, ns, name := ParseAndFixVariable(qname)
66 if explode {
67 cp.errorf("removing exploded variable makes no sense")
68 }
69 switch ns {
70 case "", "local":
71 if !cp.thisScope()[name] {
72 cp.errorf("variable $%s not found on current local scope", name)
73 }
74 delete(cp.thisScope(), name)
75 names = append(names, name)
76 case "E":
77 envNames = append(envNames, name)
78 default:
79 cp.errorf("can only delete a variable in local: or E:")
80 }
81
82 }
83 return func(ec *EvalCtx) {
84 for _, name := range names {
85 delete(ec.local, name)
86 }
87 for _, name := range envNames {
88 // BUG(xiaq): We rely on the fact that os.Unsetenv always returns
89 // nil.
90 os.Unsetenv(name)
91 }
92 }
93 }
94
95 // makeFnOp wraps an op such that a return is converted to an ok.
96 func makeFnOp(op Op) Op {
97 return Op{func(ec *EvalCtx) {
98 err := ec.PEval(op)
99 if err != nil && err.(*Exception).Cause != Return {
100 // rethrow
101 throw(err)
102 }
103 }, op.Begin, op.End}
104 }
105
106 // FnForm = 'fn' StringPrimary LambdaPrimary
107 //
108 // fn f []{foobar} is a shorthand for set '&'f = []{foobar}.
109 func compileFn(cp *compiler, fn *parse.Form) OpFunc {
110 args := cp.walkArgs(fn)
111 nameNode := args.next()
112 varName := FnPrefix + mustString(cp, nameNode, "must be a literal string")
113 bodyNode := args.nextMustLambda()
114 args.mustEnd()
115
116 cp.registerVariableSet(":" + varName)
117 op := cp.lambda(bodyNode)
118
119 return func(ec *EvalCtx) {
120 // Initialize the function variable with the builtin nop
121 // function. This step allows the definition of recursive
122 // functions; the actual function will never be called.
123 ec.local[varName] = NewPtrVariable(&BuiltinFn{"<shouldn't be called>", nop})
124 closure := op(ec)[0].(*Closure)
125 closure.Op = makeFnOp(closure.Op)
126 ec.local[varName].Set(closure)
127 }
128 }
129
130 // UseForm = 'use' StringPrimary [ Compound ]
131 func compileUse(cp *compiler, fn *parse.Form) OpFunc {
132 var modname string
133 var filenameOp ValuesOp
134 var filenameBegin, filenameEnd int
135
136 switch len(fn.Args) {
137 case 0:
138 end := fn.Head.End()
139 cp.errorpf(end, end, "lack module name")
140 case 2:
141 filenameOp = cp.compoundOp(fn.Args[1])
142 filenameBegin = fn.Args[1].Begin()
143 filenameEnd = fn.Args[1].End()
144 fallthrough
145 case 1:
146 modname = mustString(cp, fn.Args[0], "should be a literal module name")
147 default:
148 cp.errorpf(fn.Args[2].Begin(), fn.Args[len(fn.Args)-1].End(), "superfluous argument(s)")
149 }
150
151 return func(ec *EvalCtx) {
152 if filenameOp.Func != nil {
153 values := filenameOp.Exec(ec)
154 valuesMust := &muster{ec, "module filename", filenameBegin, filenameEnd, values}
155 filename := string(valuesMust.mustOneStr())
156 use(ec, modname, &filename)
157 } else {
158 use(ec, modname, nil)
159 }
160 }
161 }
162
163 func use(ec *EvalCtx, modname string, pfilename *string) {
164 if _, ok := ec.Evaler.Modules[modname]; ok {
165 // Module already loaded.
166 return
167 }
168
169 // Load the source.
170 var filename, source string
171
172 if pfilename != nil {
173 filename = *pfilename
174 var err error
175 source, err = readFileUTF8(filename)
176 maybeThrow(err)
177 } else {
178 // No filename; defaulting to $datadir/lib/$modname.elv.
179 if ec.DataDir == "" {
180 throw(ErrNoDataDir)
181 }
182 filename = ec.DataDir + "/lib/" + strings.Replace(modname, ":", "/", -1) + ".elv"
183 if _, err := os.Stat(filename); os.IsNotExist(err) {
184 // File does not exist. Try loading from the table of builtin
185 // modules.
186 var ok bool
187 if source, ok = embeddedModules[modname]; ok {
188 // Source is loaded. Do nothing more.
189 filename = "<builtin module>"
190 } else {
191 throw(fmt.Errorf("cannot load %s: %s does not exist", modname, filename))
192 }
193 } else {
194 // File exists. Load it.
195 source, err = readFileUTF8(filename)
196 maybeThrow(err)
197 }
198 }
199
200 n, err := parse.Parse(filename, source)
201 maybeThrow(err)
202
203 // Make an empty namespace.
204 local := Namespace{}
205
206 // TODO(xiaq): Should handle failures when evaluting the module
207 newEc := &EvalCtx{
208 ec.Evaler, "module " + modname,
209 filename, source,
210 local, Namespace{},
211 ec.ports, nil,
212 0, len(source), ec.addTraceback(), false,
213 }
214
215 op, err := newEc.Compile(n, filename, source)
216 // TODO the err originates in another source, should add appropriate information.
217 maybeThrow(err)
218
219 // Load the namespace before executing. This avoids mutual and self use's to
220 // result in an infinite recursion.
221 ec.Evaler.Modules[modname] = local
222 err = newEc.PEval(op)
223 if err != nil {
224 // Unload the namespace.
225 delete(ec.Modules, modname)
226 throw(err)
227 }
228 }
229
230 // compileAnd compiles the "and" special form.
231 // The and special form evaluates arguments until a false-ish values is found
232 // and outputs it; the remaining arguments are not evaluated. If there are no
233 // false-ish values, the last value is output. If there are no arguments, it
234 // outputs $true, as if there is a hidden $true before actual arguments.
235 func compileAnd(cp *compiler, fn *parse.Form) OpFunc {
236 return compileAndOr(cp, fn, true, false)
237 }
238
239 // compileOr compiles the "or" special form.
240 // The or special form evaluates arguments until a true-ish values is found and
241 // outputs it; the remaining arguments are not evaluated. If there are no
242 // true-ish values, the last value is output. If there are no arguments, it
243 // outputs $false, as if there is a hidden $false before actual arguments.
244 func compileOr(cp *compiler, fn *parse.Form) OpFunc {
245 return compileAndOr(cp, fn, false, true)
246 }
247
248 func compileAndOr(cp *compiler, fn *parse.Form, init, stopAt bool) OpFunc {
249 argOps := cp.compoundOps(fn.Args)
250 return func(ec *EvalCtx) {
251 var lastValue Value = Bool(init)
252 for _, op := range argOps {
253 values := op.Exec(ec)
254 for _, value := range values {
255 if ToBool(value) == stopAt {
256 ec.OutputChan() <- value
257 return
258 }
259 lastValue = value
260 }
261 }
262 ec.OutputChan() <- lastValue
263 }
264 }
265
266 func compileIf(cp *compiler, fn *parse.Form) OpFunc {
267 args := cp.walkArgs(fn)
268 var condNodes []*parse.Compound
269 var bodyNodes []*parse.Primary
270 for {
271 condNodes = append(condNodes, args.next())
272 bodyNodes = append(bodyNodes, args.nextMustLambda())
273 if !args.nextIs("elif") {
274 break
275 }
276 }
277 elseNode := args.nextMustLambdaIfAfter("else")
278 args.mustEnd()
279
280 condOps := cp.compoundOps(condNodes)
281 bodyOps := cp.primaryOps(bodyNodes)
282 var elseOp ValuesOp
283 if elseNode != nil {
284 elseOp = cp.primaryOp(elseNode)
285 }
286
287 return func(ec *EvalCtx) {
288 bodies := make([]Callable, len(bodyOps))
289 for i, bodyOp := range bodyOps {
290 bodies[i] = bodyOp.execlambdaOp(ec)
291 }
292 else_ := elseOp.execlambdaOp(ec)
293 for i, condOp := range condOps {
294 if allTrue(condOp.Exec(ec.fork("if cond"))) {
295 bodies[i].Call(ec.fork("if body"), NoArgs, NoOpts)
296 return
297 }
298 }
299 if elseOp.Func != nil {
300 else_.Call(ec.fork("if else"), NoArgs, NoOpts)
301 }
302 }
303 }
304
305 func compileWhile(cp *compiler, fn *parse.Form) OpFunc {
306 args := cp.walkArgs(fn)
307 condNode := args.next()
308 bodyNode := args.nextMustLambda()
309 args.mustEnd()
310
311 condOp := cp.compoundOp(condNode)
312 bodyOp := cp.primaryOp(bodyNode)
313
314 return func(ec *EvalCtx) {
315 body := bodyOp.execlambdaOp(ec)
316
317 for {
318 cond := condOp.Exec(ec.fork("while cond"))
319 if !allTrue(cond) {
320 break
321 }
322 err := ec.fork("while").PCall(body, NoArgs, NoOpts)
323 if err != nil {
324 exc := err.(*Exception)
325 if exc.Cause == Continue {
326 // do nothing
327 } else if exc.Cause == Break {
328 continue
329 } else {
330 throw(err)
331 }
332 }
333 }
334 }
335 }
336
337 func compileFor(cp *compiler, fn *parse.Form) OpFunc {
338 args := cp.walkArgs(fn)
339 varNode := args.next()
340 iterNode := args.next()
341 bodyNode := args.nextMustLambda()
342 elseNode := args.nextMustLambdaIfAfter("else")
343 args.mustEnd()
344
345 varOp, restOp := cp.lvaluesOp(varNode.Indexings[0])
346 if restOp.Func != nil {
347 cp.errorpf(restOp.Begin, restOp.End, "rest not allowed")
348 }
349
350 iterOp := cp.compoundOp(iterNode)
351 bodyOp := cp.primaryOp(bodyNode)
352 var elseOp ValuesOp
353 if elseNode != nil {
354 elseOp = cp.primaryOp(elseNode)
355 }
356
357 return func(ec *EvalCtx) {
358 variables := varOp.Exec(ec)
359 if len(variables) != 1 {
360 ec.errorpf(varOp.Begin, varOp.End, "only one variable allowed")
361 }
362 variable := variables[0]
363
364 iterables := iterOp.Exec(ec)
365 if len(iterables) != 1 {
366 ec.errorpf(iterOp.Begin, iterOp.End, "should be one iterable")
367 }
368 iterable, ok := iterables[0].(Iterable)
369 if !ok {
370 ec.errorpf(iterOp.Begin, iterOp.End, "should be one iterable")
371 }
372
373 body := bodyOp.execlambdaOp(ec)
374 elseBody := elseOp.execlambdaOp(ec)
375
376 iterated := false
377 iterable.Iterate(func(v Value) bool {
378 iterated = true
379 variable.Set(v)
380 err := ec.fork("for").PCall(body, NoArgs, NoOpts)
381 if err != nil {
382 exc := err.(*Exception)
383 if exc.Cause == Continue {
384 // do nothing
385 } else if exc.Cause == Break {
386 return false
387 } else {
388 throw(err)
389 }
390 }
391 return true
392 })
393
394 if !iterated && elseBody != nil {
395 elseBody.Call(ec.fork("for else"), NoArgs, NoOpts)
396 }
397 }
398 }
399
400 func compileTry(cp *compiler, fn *parse.Form) OpFunc {
401 logger.Println("compiling try")
402 args := cp.walkArgs(fn)
403 bodyNode := args.nextMustLambda()
404 logger.Printf("body is %q", bodyNode.SourceText())
405 var exceptVarNode *parse.Indexing
406 var exceptNode *parse.Primary
407 if args.nextIs("except") {
408 logger.Println("except-ing")
409 n := args.peek()
410 // Is this a variable?
411 if len(n.Indexings) == 1 && n.Indexings[0].Head.Type == parse.Bareword {
412 exceptVarNode = n.Indexings[0]
413 args.next()
414 }
415 exceptNode = args.nextMustLambda()
416 }
417 elseNode := args.nextMustLambdaIfAfter("else")
418 finallyNode := args.nextMustLambdaIfAfter("finally")
419 args.mustEnd()
420
421 var exceptVarOp LValuesOp
422 var bodyOp, exceptOp, elseOp, finallyOp ValuesOp
423 bodyOp = cp.primaryOp(bodyNode)
424 if exceptVarNode != nil {
425 var restOp LValuesOp
426 exceptVarOp, restOp = cp.lvaluesOp(exceptVarNode)
427 if restOp.Func != nil {
428 cp.errorpf(restOp.Begin, restOp.End, "may not use @rest in except variable")
429 }
430 }
431 if exceptNode != nil {
432 exceptOp = cp.primaryOp(exceptNode)
433 }
434 if elseNode != nil {
435 elseOp = cp.primaryOp(elseNode)
436 }
437 if finallyNode != nil {
438 finallyOp = cp.primaryOp(finallyNode)
439 }
440
441 return func(ec *EvalCtx) {
442 body := bodyOp.execlambdaOp(ec)
443 exceptVar := exceptVarOp.execMustOne(ec)
444 except := exceptOp.execlambdaOp(ec)
445 else_ := elseOp.execlambdaOp(ec)
446 finally := finallyOp.execlambdaOp(ec)
447
448 err := ec.fork("try body").PCall(body, NoArgs, NoOpts)
449 if err != nil {
450 if except != nil {
451 if exceptVar != nil {
452 exceptVar.Set(err.(*Exception))
453 }
454 err = ec.fork("try except").PCall(except, NoArgs, NoOpts)
455 }
456 } else {
457 if else_ != nil {
458 err = ec.fork("try else").PCall(else_, NoArgs, NoOpts)
459 }
460 }
461 if finally != nil {
462 finally.Call(ec.fork("try finally"), NoArgs, NoOpts)
463 }
464 if err != nil {
465 throw(err)
466 }
467 }
468 }
469
470 // execLambdaOp executes a ValuesOp that is known to yield a lambda and returns
471 // the lambda. If the ValuesOp is empty, it returns a nil.
472 func (op ValuesOp) execlambdaOp(ec *EvalCtx) Callable {
473 if op.Func == nil {
474 return nil
475 }
476
477 return op.Exec(ec)[0].(Callable)
478 }
479
480 // execMustOne executes the LValuesOp and raises an exception if it does not
481 // evaluate to exactly one Variable. If the given LValuesOp is empty, it returns
482 // nil.
483 func (op LValuesOp) execMustOne(ec *EvalCtx) Variable {
484 if op.Func == nil {
485 return nil
486 }
487 variables := op.Exec(ec)
488 if len(variables) != 1 {
489 ec.errorpf(op.Begin, op.End, "should be one variable")
490 }
491 return variables[0]
492 }
0 package eval
1
2 import (
3 "os"
4
5 "github.com/elves/elvish/daemon/api"
6 )
7
8 // Chdir changes the current directory. On success it also updates the PWD
9 // environment variable and records the new directory in the directory history.
10 // It returns nil as long as the directory changing part succeeds.
11 func Chdir(path string, daemon *api.Client) error {
12 err := os.Chdir(path)
13 if err != nil {
14 return err
15 }
16 pwd, err := os.Getwd()
17 if err != nil {
18 logger.Println("getwd after cd:", err)
19 return nil
20 }
21 os.Setenv("PWD", pwd)
22 if daemon != nil {
23 daemon.Waits().Add(1)
24 go func() {
25 daemon.AddDir(pwd, 1)
26 daemon.Waits().Done()
27 }()
28 }
29 return nil
30 }
0 package eval
1
2 import (
3 "errors"
4 "fmt"
5 )
6
7 // ErrArityMismatch is thrown by a closure when the number of arguments the user
8 // supplies does not match with what is required.
9 var ErrArityMismatch = errors.New("arity mismatch")
10
11 var unnamedRestArg = "@"
12
13 // Closure is a closure defined in elvish script.
14 type Closure struct {
15 ArgNames []string
16 // The name for the rest argument. If empty, the function has fixed arity.
17 // If equal to unnamedRestArg, the rest argument is unnamed but can be
18 // accessed via $args.
19 RestArg string
20 Op Op
21 Captured map[string]Variable
22 SourceName string
23 Source string
24 }
25
26 var _ CallableValue = &Closure{}
27
28 // Kind returns "fn".
29 func (*Closure) Kind() string {
30 return "fn"
31 }
32
33 // Repr returns an opaque representation "<closure 0x23333333>".
34 func (c *Closure) Repr(int) string {
35 return fmt.Sprintf("<closure %p>", c)
36 }
37
38 // Call calls a closure.
39 func (c *Closure) Call(ec *EvalCtx, args []Value, opts map[string]Value) {
40 // TODO Support keyword arguments
41 if c.RestArg != "" {
42 if len(c.ArgNames) > len(args) {
43 throw(ErrArityMismatch)
44 }
45 } else {
46 if len(c.ArgNames) != len(args) {
47 throw(ErrArityMismatch)
48 }
49 }
50
51 // This evalCtx is dedicated to the current form, so we modify it in place.
52 // BUG(xiaq): When evaluating closures, async access to global variables
53 // and ports can be problematic.
54
55 // Make upvalue namespace and capture variables.
56 ec.up = make(map[string]Variable)
57 for name, variable := range c.Captured {
58 ec.up[name] = variable
59 }
60 // Make local namespace and pass arguments.
61 ec.local = make(map[string]Variable)
62 for i, name := range c.ArgNames {
63 ec.local[name] = NewPtrVariable(args[i])
64 }
65 if c.RestArg != "" && c.RestArg != unnamedRestArg {
66 ec.local[c.RestArg] = NewPtrVariable(NewList(args[len(c.ArgNames):]...))
67 }
68 // Logger.Printf("EvalCtx=%p, args=%v, opts=%v", ec, args, opts)
69 ec.positionals = args
70 ec.local["args"] = NewPtrVariable(NewList(args...))
71 // XXX This conversion was done by the other direction.
72 convertedOpts := make(map[Value]Value)
73 for k, v := range opts {
74 convertedOpts[String(k)] = v
75 }
76 ec.local["opts"] = NewPtrVariable(Map{&convertedOpts})
77
78 ec.traceback = ec.addTraceback()
79
80 ec.srcName, ec.src = c.SourceName, c.Source
81 c.Op.Exec(ec)
82 }
0 package eval
1
2 import (
3 "bytes"
4 "fmt"
5
6 "github.com/elves/elvish/util"
7 )
8
9 // CompilationError represents a compilation error and can pretty print it.
10 type CompilationError struct {
11 Message string
12 Context util.SourceContext
13 }
14
15 func (ce *CompilationError) Error() string {
16 return fmt.Sprintf("compilation error: %d-%d in %s: %s",
17 ce.Context.Begin, ce.Context.End, ce.Context.Name, ce.Message)
18 }
19
20 // Pprint pretty-prints a compilation error.
21 func (ce *CompilationError) Pprint(indent string) string {
22 buf := new(bytes.Buffer)
23
24 fmt.Fprintf(buf, "Compilation error: \033[31;1m%s\033[m\n", ce.Message)
25 fmt.Fprint(buf, indent+" ")
26 ce.Context.Pprint(buf, indent+" ")
27
28 return buf.String()
29 }
0 package eval
1
2 import (
3 "errors"
4
5 "github.com/elves/elvish/parse"
6 )
7
8 // LValuesOp is an operation on an EvalCtx that produce Variable's.
9 type LValuesOp struct {
10 Func LValuesOpFunc
11 Begin, End int
12 }
13
14 // LValuesOpFunc is the body of an LValuesOp.
15 type LValuesOpFunc func(*EvalCtx) []Variable
16
17 // Exec executes an LValuesOp, producing Variable's.
18 func (op LValuesOp) Exec(ec *EvalCtx) []Variable {
19 // Empty value is considered to generate no lvalues.
20 if op.Func == nil {
21 return []Variable{}
22 }
23 ec.begin, ec.end = op.Begin, op.End
24 return op.Func(ec)
25 }
26
27 // lvaluesOp compiles lvalues, returning the fixed part and, optionally a rest
28 // part.
29 //
30 // In the AST an lvalue is either an Indexing node where the head is a string
31 // literal, or a braced list of such Indexing nodes. The last Indexing node may
32 // be prefixed by @, in which case they become the rest part. For instance, in
33 // {a[x],b,@c[z]}, "a[x],b" is the fixed part and "c[z]" is the rest part.
34 func (cp *compiler) lvaluesOp(n *parse.Indexing) (LValuesOp, LValuesOp) {
35 if n.Head.Type == parse.Braced {
36 // Braced list of variable specs, possibly with indicies. The braced list
37 if len(n.Indicies) > 0 {
38 cp.errorf("may not have indicies")
39 }
40 return cp.lvaluesMulti(n.Head.Braced)
41 }
42 rest, opFunc := cp.lvaluesOne(n, "must be an lvalue or a braced list of those")
43 op := LValuesOp{opFunc, n.Begin(), n.End()}
44 if rest {
45 return LValuesOp{}, op
46 }
47 return op, LValuesOp{}
48 }
49
50 func (cp *compiler) lvaluesMulti(nodes []*parse.Compound) (LValuesOp, LValuesOp) {
51 opFuncs := make([]LValuesOpFunc, len(nodes))
52 var restNode *parse.Indexing
53 var restOpFunc LValuesOpFunc
54
55 // Compile each spec inside the brace.
56 fixedEnd := 0
57 for i, cn := range nodes {
58 if len(cn.Indexings) != 1 {
59 cp.errorpf(cn.Begin(), cn.End(), "must be an lvalue")
60 }
61 var rest bool
62 rest, opFuncs[i] = cp.lvaluesOne(cn.Indexings[0], "must be an lvalue ")
63 // Only the last one may a rest part.
64 if rest {
65 if i == len(nodes)-1 {
66 restNode = cn.Indexings[0]
67 restOpFunc = opFuncs[i]
68 } else {
69 cp.errorpf(cn.Begin(), cn.End(), "only the last lvalue may have @")
70 }
71 } else {
72 fixedEnd = cn.End()
73 }
74 }
75
76 var restOp LValuesOp
77 // If there is a rest part, make LValuesOp for it and remove it from opFuncs.
78 if restOpFunc != nil {
79 restOp = LValuesOp{restOpFunc, restNode.Begin(), restNode.End()}
80 opFuncs = opFuncs[:len(opFuncs)-1]
81 }
82
83 var op LValuesOp
84 // If there is still anything left in opFuncs, make LValuesOp for the fixed part.
85 if len(opFuncs) > 0 {
86 op = LValuesOp{func(ec *EvalCtx) []Variable {
87 var variables []Variable
88 for _, opFunc := range opFuncs {
89 variables = append(variables, opFunc(ec)...)
90 }
91 return variables
92 }, nodes[0].Begin(), fixedEnd}
93 }
94
95 return op, restOp
96 }
97
98 func (cp *compiler) lvaluesOne(n *parse.Indexing, msg string) (bool, LValuesOpFunc) {
99 varname := cp.literal(n.Head, msg)
100 cp.registerVariableSet(varname)
101 explode, ns, barename := ParseAndFixVariable(varname)
102
103 if len(n.Indicies) == 0 {
104 return explode, func(ec *EvalCtx) []Variable {
105 variable := ec.ResolveVar(ns, barename)
106 if variable == nil {
107 if ns == "" || ns == "local" {
108 // New variable.
109 // XXX We depend on the fact that this variable will
110 // immeidately be set.
111 variable = NewPtrVariable(nil)
112 ec.local[barename] = variable
113 } else if mod, ok := ec.Modules[ns]; ok {
114 variable = NewPtrVariable(nil)
115 mod[barename] = variable
116 } else {
117 throwf("cannot set $%s", varname)
118 }
119 }
120 return []Variable{variable}
121 }
122 }
123
124 p := n.Begin()
125 indexOps := cp.arrayOps(n.Indicies)
126
127 return explode, func(ec *EvalCtx) []Variable {
128 variable := ec.ResolveVar(ns, barename)
129 if variable == nil {
130 throwf("variable $%s does not exisit, compiler bug", varname)
131 }
132
133 // Indexing. Do Index up to the last but one index.
134 value := variable.Get()
135 n := len(indexOps)
136 // TODO set location information according.
137 for _, op := range indexOps[:n-1] {
138 indexer := mustIndexer(value, ec)
139
140 indicies := op.Exec(ec)
141 values := indexer.Index(indicies)
142 if len(values) != 1 {
143 throw(errors.New("multi indexing not implemented"))
144 }
145 value = values[0]
146 }
147 // Now this must be an IndexSetter.
148 indexSetter, ok := value.(IndexSetter)
149 if !ok {
150 // XXX the indicated end location will fall on or after the opening
151 // bracket of the last index, instead of exactly on the penultimate
152 // index.
153 ec.errorpf(p, indexOps[n-1].Begin, "cannot be indexed for setting (value is %s, type %s)", value.Repr(NoPretty), value.Kind())
154 }
155 // XXX Duplicate code.
156 indicies := indexOps[n-1].Exec(ec)
157 if len(indicies) != 1 {
158 ec.errorpf(indexOps[n-1].Begin, indexOps[n-1].End, "index must eval to a single Value (got %v)", indicies)
159 }
160 return []Variable{elemVariable{indexSetter, indicies[0]}}
161 }
162 }
0 package eval
1
2 import (
3 "fmt"
4 "os"
5 "sync"
6
7 "github.com/elves/elvish/parse"
8 )
9
10 // Op is an operation on an EvalCtx.
11 type Op struct {
12 Func OpFunc
13 Begin, End int
14 }
15
16 // OpFunc is the body of an Op.
17 type OpFunc func(*EvalCtx)
18
19 // Exec executes an Op.
20 func (op Op) Exec(ec *EvalCtx) {
21 ec.begin, ec.end = op.Begin, op.End
22 op.Func(ec)
23 }
24
25 func (cp *compiler) chunk(n *parse.Chunk) OpFunc {
26 ops := cp.pipelineOps(n.Pipelines)
27
28 return func(ec *EvalCtx) {
29 for _, op := range ops {
30 op.Exec(ec)
31 }
32 }
33 }
34
35 const pipelineChanBufferSize = 32
36
37 func (cp *compiler) pipeline(n *parse.Pipeline) OpFunc {
38 ops := cp.formOps(n.Forms)
39
40 return func(ec *EvalCtx) {
41 bg := n.Background
42 if bg {
43 ec = ec.fork("background job " + n.SourceText())
44 ec.intCh = nil
45 ec.background = true
46
47 if ec.Editor != nil {
48 // TODO: Redirect output in interactive mode so that the line
49 // editor does not get messed up.
50 }
51 }
52
53 nforms := len(ops)
54
55 var wg sync.WaitGroup
56 wg.Add(nforms)
57 errors := make([]*Exception, nforms)
58
59 var nextIn *Port
60
61 // For each form, create a dedicated evalCtx and run asynchronously
62 for i, op := range ops {
63 newEc := ec.fork(fmt.Sprintf("form op %v", op))
64 if i > 0 {
65 newEc.ports[0] = nextIn
66 }
67 if i < nforms-1 {
68 // Each internal port pair consists of a (byte) pipe pair and a
69 // channel.
70 // os.Pipe sets O_CLOEXEC, which is what we want.
71 reader, writer, e := os.Pipe()
72 if e != nil {
73 throwf("failed to create pipe: %s", e)
74 }
75 ch := make(chan Value, pipelineChanBufferSize)
76 newEc.ports[1] = &Port{
77 File: writer, Chan: ch, CloseFile: true, CloseChan: true}
78 nextIn = &Port{
79 File: reader, Chan: ch, CloseFile: true, CloseChan: false}
80 }
81 thisOp := op
82 thisError := &errors[i]
83 go func() {
84 err := newEc.PEval(thisOp)
85 // Logger.Printf("closing ports of %s", newEc.context)
86 ClosePorts(newEc.ports)
87 if err != nil {
88 *thisError = err.(*Exception)
89 }
90 wg.Done()
91 }()
92 }
93
94 if bg {
95 // Background job, wait for form termination asynchronously.
96 go func() {
97 wg.Wait()
98 msg := "job " + n.SourceText() + " finished"
99 err := ComposeExceptionsFromPipeline(errors)
100 if err != nil {
101 msg += ", errors = " + err.Error()
102 }
103 if ec.Editor != nil {
104 m := ec.Editor.ActiveMutex()
105 m.Lock()
106 defer m.Unlock()
107
108 if ec.Editor.Active() {
109 ec.Editor.Notify("%s", msg)
110 } else {
111 ec.ports[2].File.WriteString(msg + "\n")
112 }
113 } else {
114 ec.ports[2].File.WriteString(msg + "\n")
115 }
116 }()
117 } else {
118 wg.Wait()
119 maybeThrow(ComposeExceptionsFromPipeline(errors))
120 }
121 }
122 }
123
124 func (cp *compiler) form(n *parse.Form) OpFunc {
125 var saveVarsOps []LValuesOp
126 var assignmentOps []Op
127 if len(n.Assignments) > 0 {
128 assignmentOps = cp.assignmentOps(n.Assignments)
129 if n.Head == nil && n.Vars == nil {
130 // Permanent assignment.
131 return func(ec *EvalCtx) {
132 for _, op := range assignmentOps {
133 op.Exec(ec)
134 }
135 }
136 }
137 for _, a := range n.Assignments {
138 v, r := cp.lvaluesOp(a.Left)
139 saveVarsOps = append(saveVarsOps, v, r)
140 }
141 logger.Println("temporary assignment of", len(n.Assignments), "pairs")
142 }
143
144 if n.Head != nil {
145 headStr, ok := oneString(n.Head)
146 if ok {
147 compileForm, ok := builtinSpecials[headStr]
148 if ok {
149 // special form
150 return compileForm(cp, n)
151 }
152 // Ignore the output. If a matching function exists it will be
153 // captured and eventually the Evaler executes it. If not, nothing
154 // happens here and the Evaler executes an external command.
155 cp.registerVariableGet(FnPrefix + headStr)
156 // XXX Dynamic head names should always refer to external commands
157 }
158 }
159
160 argOps := cp.compoundOps(n.Args)
161
162 var headOp ValuesOp
163 var spaceyAssignOp Op
164 if n.Head != nil {
165 headOp = cp.compoundOp(n.Head)
166 } else {
167 varsOp, restOp := cp.lvaluesMulti(n.Vars)
168 argsOp := ValuesOp{
169 func(ec *EvalCtx) []Value {
170 var vs []Value
171 for _, op := range argOps {
172 vs = append(vs, op.Exec(ec)...)
173 }
174 return vs
175 },
176 -1, -1,
177 }
178 if len(argOps) > 0 {
179 argsOp.Begin = argOps[0].Begin
180 argsOp.End = argOps[len(argOps)-1].End
181 }
182 spaceyAssignOp = Op{
183 makeAssignmentOpFunc(varsOp, restOp, argsOp),
184 n.Begin(), argsOp.End,
185 }
186 }
187
188 optsOp := cp.mapPairs(n.Opts)
189 redirOps := cp.redirOps(n.Redirs)
190 // TODO: n.ErrorRedir
191
192 begin, end := n.Begin(), n.End()
193 // ec here is always a subevaler created in compiler.pipeline, so it can
194 // be safely modified.
195 return func(ec *EvalCtx) {
196 // Temporary assignment.
197 if len(saveVarsOps) > 0 {
198 // There is a temporary assignment.
199 // Save variables.
200 var saveVars []Variable
201 var saveVals []Value
202 for _, op := range saveVarsOps {
203 saveVars = append(saveVars, op.Exec(ec)...)
204 }
205 for _, v := range saveVars {
206 val := v.Get()
207 saveVals = append(saveVals, val)
208 logger.Printf("saved %s = %s", v, val)
209 }
210 // Do assignment.
211 for _, op := range assignmentOps {
212 op.Exec(ec)
213 }
214 // Defer variable restoration. Will be executed even if an error
215 // occurs when evaling other part of the form.
216 defer func() {
217 for i, v := range saveVars {
218 val := saveVals[i]
219 if val == nil {
220 // XXX Old value is nonexistent. We should delete the
221 // variable. However, since the compiler now doesn't delete
222 // it, we don't delete it in the evaler either.
223 val = String("")
224 }
225 v.Set(val)
226 logger.Printf("restored %s = %s", v, val)
227 }
228 }()
229 }
230
231 var headFn Callable
232 var args []Value
233 if headOp.Func != nil {
234 // head
235 headValues := headOp.Exec(ec)
236 ec.must(headValues, "head of command", headOp.Begin, headOp.End).mustLen(1)
237 headFn = mustFn(headValues[0])
238
239 // args
240 for _, argOp := range argOps {
241 args = append(args, argOp.Exec(ec)...)
242 }
243 }
244
245 // opts
246 // XXX This conversion should be avoided.
247 opts := optsOp(ec)[0].(Map)
248 convertedOpts := make(map[string]Value)
249 for k, v := range *opts.inner {
250 if ks, ok := k.(String); ok {
251 convertedOpts[string(ks)] = v
252 } else {
253 throwf("Option key must be string, got %s", k.Kind())
254 }
255 }
256
257 // redirs
258 for _, redirOp := range redirOps {
259 redirOp.Exec(ec)
260 }
261
262 ec.begin, ec.end = begin, end
263
264 if headFn != nil {
265 headFn.Call(ec, args, convertedOpts)
266 } else {
267 spaceyAssignOp.Exec(ec)
268 }
269 }
270 }
271
272 func allTrue(vs []Value) bool {
273 for _, v := range vs {
274 if !ToBool(v) {
275 return false
276 }
277 }
278 return true
279 }
280
281 func (cp *compiler) assignment(n *parse.Assignment) OpFunc {
282 variablesOp, restOp := cp.lvaluesOp(n.Left)
283 valuesOp := cp.compoundOp(n.Right)
284 return makeAssignmentOpFunc(variablesOp, restOp, valuesOp)
285 }
286
287 func makeAssignmentOpFunc(variablesOp, restOp LValuesOp, valuesOp ValuesOp) OpFunc {
288 return func(ec *EvalCtx) {
289 variables := variablesOp.Exec(ec)
290 rest := restOp.Exec(ec)
291
292 // If any LHS ends up being nil, assign an empty string to all of them.
293 //
294 // This is to fix #176, which only happens in the top level of REPL; in
295 // other cases, a failure in the evaluation of the RHS causes this
296 // level to fail, making the variables unaccessible.
297 defer fixNilVariables(variables)
298 defer fixNilVariables(rest)
299
300 values := valuesOp.Exec(ec)
301
302 if len(rest) > 1 {
303 throw(ErrMoreThanOneRest)
304 }
305 if len(rest) == 1 {
306 if len(variables) > len(values) {
307 throw(ErrArityMismatch)
308 }
309 } else {
310 if len(variables) != len(values) {
311 throw(ErrArityMismatch)
312 }
313 }
314
315 for i, variable := range variables {
316 variable.Set(values[i])
317 }
318
319 if len(rest) == 1 {
320 rest[0].Set(NewList(values[len(variables):]...))
321 }
322 }
323 }
324
325 func fixNilVariables(vs []Variable) {
326 for _, v := range vs {
327 if v.Get() == nil {
328 v.Set(String(""))
329 }
330 }
331 }
332
333 func (cp *compiler) literal(n *parse.Primary, msg string) string {
334 switch n.Type {
335 case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted:
336 return n.Value
337 default:
338 cp.compiling(n)
339 cp.errorf(msg)
340 return "" // not reached
341 }
342 }
343
344 const defaultFileRedirPerm = 0644
345
346 // redir compiles a Redir into a op.
347 func (cp *compiler) redir(n *parse.Redir) OpFunc {
348 var dstOp ValuesOp
349 if n.Left != nil {
350 dstOp = cp.compoundOp(n.Left)
351 }
352 srcOp := cp.compoundOp(n.Right)
353 sourceIsFd := n.RightIsFd
354 mode := n.Mode
355 flag := makeFlag(mode)
356
357 return func(ec *EvalCtx) {
358 var dst int
359 if dstOp.Func == nil {
360 // use default dst fd
361 switch mode {
362 case parse.Read:
363 dst = 0
364 case parse.Write, parse.ReadWrite, parse.Append:
365 dst = 1
366 default:
367 // XXX should report parser bug
368 panic("bad RedirMode; parser bug")
369 }
370 } else {
371 // dst must be a valid fd
372 dst = ec.must(dstOp.Exec(ec), "FD", dstOp.Begin, dstOp.End).mustOneNonNegativeInt()
373 }
374
375 ec.growPorts(dst + 1)
376 // Logger.Printf("closing old port %d of %s", dst, ec.context)
377 ec.ports[dst].Close()
378
379 srcMust := ec.must(srcOp.Exec(ec), "redirection source", srcOp.Begin, srcOp.End)
380 if sourceIsFd {
381 src := string(srcMust.mustOneStr())
382 if src == "-" {
383 // close
384 ec.ports[dst] = &Port{}
385 } else {
386 fd := srcMust.zerothMustNonNegativeInt()
387 ec.ports[dst] = ec.ports[fd].Fork()
388 }
389 } else {
390 switch src := srcMust.mustOne().(type) {
391 case String:
392 f, err := os.OpenFile(string(src), flag, defaultFileRedirPerm)
393 if err != nil {
394 throwf("failed to open file %s: %s", src.Repr(NoPretty), err)
395 }
396 ec.ports[dst] = &Port{
397 File: f, Chan: BlackholeChan,
398 CloseFile: true,
399 }
400 case File:
401 ec.ports[dst] = &Port{
402 File: src.inner, Chan: BlackholeChan,
403 CloseFile: false,
404 }
405 case Pipe:
406 var f *os.File
407 switch mode {
408 case parse.Read:
409 f = src.r
410 case parse.Write:
411 f = src.w
412 default:
413 cp.errorf("can only use < or > with pipes")
414 }
415 ec.ports[dst] = &Port{
416 File: f, Chan: BlackholeChan,
417 CloseFile: false,
418 }
419 default:
420 srcMust.error("string or file", "%s", src.Kind())
421 }
422 }
423 }
424 }
0 package eval
1
2 import (
3 "bufio"
4 "errors"
5 "fmt"
6 "io"
7 "log"
8 "os"
9 "path"
10 "strings"
11
12 "github.com/elves/elvish/glob"
13 "github.com/elves/elvish/parse"
14 )
15
16 var outputCaptureBufferSize = 16
17
18 // ValuesOp is an operation on an EvalCtx that produce Value's.
19 type ValuesOp struct {
20 Func ValuesOpFunc
21 Begin, End int
22 }
23
24 // ValuesOpFunc is the body of ValuesOp.
25 type ValuesOpFunc func(*EvalCtx) []Value
26
27 // Exec executes a ValuesOp and produces Value's.
28 func (op ValuesOp) Exec(ec *EvalCtx) []Value {
29 ec.begin, ec.end = op.Begin, op.End
30 return op.Func(ec)
31 }
32
33 func (cp *compiler) compound(n *parse.Compound) ValuesOpFunc {
34 if len(n.Indexings) == 0 {
35 return literalStr("")
36 }
37
38 tilde := false
39 indexings := n.Indexings
40
41 if n.Indexings[0].Head.Type == parse.Tilde {
42 // A lone ~.
43 if len(n.Indexings) == 1 {
44 return func(ec *EvalCtx) []Value {
45 return []Value{String(mustGetHome(""))}
46 }
47 }
48 tilde = true
49 indexings = indexings[1:]
50 }
51
52 ops := cp.indexingOps(indexings)
53
54 return func(ec *EvalCtx) []Value {
55 // Accumulator.
56 vs := ops[0].Exec(ec)
57
58 // Logger.Printf("concatenating %v with %d more", vs, len(ops)-1)
59
60 for _, op := range ops[1:] {
61 us := op.Exec(ec)
62 vs = outerProduct(vs, us, cat)
63 // Logger.Printf("with %v => %v", us, vs)
64 }
65 if tilde {
66 newvs := make([]Value, len(vs))
67 for i, v := range vs {
68 newvs[i] = doTilde(v)
69 }
70 vs = newvs
71 }
72 hasGlob := false
73 for _, v := range vs {
74 if _, ok := v.(GlobPattern); ok {
75 hasGlob = true
76 break
77 }
78 }
79 if hasGlob {
80 newvs := make([]Value, 0, len(vs))
81 for _, v := range vs {
82 if gp, ok := v.(GlobPattern); ok {
83 // Logger.Printf("globbing %v", gp)
84 newvs = append(newvs, doGlob(gp, ec.Interrupts())...)
85 } else {
86 newvs = append(newvs, v)
87 }
88 }
89 vs = newvs
90 }
91 return vs
92 }
93 }
94
95 func cat(lhs, rhs Value) Value {
96 switch lhs := lhs.(type) {
97 case String:
98 switch rhs := rhs.(type) {
99 case String:
100 return lhs + rhs
101 case GlobPattern:
102 segs := stringToSegments(string(lhs))
103 // We know rhs contains exactly one segment.
104 segs = append(segs, rhs.Segments[0])
105 return GlobPattern{glob.Pattern{segs, ""}, rhs.Flags, rhs.Buts}
106 }
107 case GlobPattern:
108 // NOTE Modifies lhs in place.
109 switch rhs := rhs.(type) {
110 case String:
111 lhs.append(stringToSegments(string(rhs))...)
112 return lhs
113 case GlobPattern:
114 // We know rhs contains exactly one segment.
115 lhs.append(rhs.Segments[0])
116 lhs.Flags |= rhs.Flags
117 lhs.Buts = append(lhs.Buts, rhs.Buts...)
118 return lhs
119 }
120 }
121 throw(fmt.Errorf("unsupported concat: %s and %s", lhs.Kind(), rhs.Kind()))
122 panic("unreachable")
123 }
124
125 func outerProduct(vs []Value, us []Value, f func(Value, Value) Value) []Value {
126 ws := make([]Value, len(vs)*len(us))
127 nu := len(us)
128 for i, v := range vs {
129 for j, u := range us {
130 ws[i*nu+j] = f(v, u)
131 }
132 }
133 return ws
134 }
135
136 // Errors thrown when globbing.
137 var (
138 ErrBadGlobPattern = errors.New("bad GlobPattern; elvish bug")
139 ErrCannotDetermineUsername = errors.New("cannot determine user name from glob pattern")
140 )
141
142 func doTilde(v Value) Value {
143 switch v := v.(type) {
144 case String:
145 s := string(v)
146 i := strings.Index(s, "/")
147 var uname, rest string
148 if i == -1 {
149 uname = s
150 } else {
151 uname = s[:i]
152 rest = s[i+1:]
153 }
154 dir := mustGetHome(uname)
155 return String(path.Join(dir, rest))
156 case GlobPattern:
157 if len(v.Segments) == 0 {
158 throw(ErrBadGlobPattern)
159 }
160 switch seg := v.Segments[0].(type) {
161 case glob.Literal:
162 s := seg.Data
163 // Find / in the first segment to determine the username.
164 i := strings.Index(s, "/")
165 if i == -1 {
166 throw(ErrCannotDetermineUsername)
167 }
168 uname := s[:i]
169 dir := mustGetHome(uname)
170 // Replace ~uname in first segment with the found path.
171 v.Segments[0] = glob.Literal{dir + s[i:]}
172 case glob.Slash:
173 v.DirOverride = mustGetHome("")
174 default:
175 throw(ErrCannotDetermineUsername)
176 }
177 return v
178 default:
179 throw(fmt.Errorf("tilde doesn't work on value of type %s", v.Kind()))
180 panic("unreachable")
181 }
182 }
183
184 func (cp *compiler) array(n *parse.Array) ValuesOpFunc {
185 return catValuesOps(cp.compoundOps(n.Compounds))
186 }
187
188 func catValuesOps(ops []ValuesOp) ValuesOpFunc {
189 return func(ec *EvalCtx) []Value {
190 // Use number of compound expressions as an estimation of the number
191 // of values
192 vs := make([]Value, 0, len(ops))
193 for _, op := range ops {
194 us := op.Exec(ec)
195 vs = append(vs, us...)
196 }
197 return vs
198 }
199 }
200
201 func (cp *compiler) indexing(n *parse.Indexing) ValuesOpFunc {
202 if len(n.Indicies) == 0 {
203 return cp.primary(n.Head)
204 }
205
206 headOp := cp.primaryOp(n.Head)
207 indexOps := cp.arrayOps(n.Indicies)
208
209 return func(ec *EvalCtx) []Value {
210 vs := headOp.Exec(ec)
211 for _, indexOp := range indexOps {
212 indicies := indexOp.Exec(ec)
213 newvs := make([]Value, 0, len(vs)*len(indicies))
214 for _, v := range vs {
215 newvs = append(newvs, mustIndexer(v, ec).Index(indicies)...)
216 }
217 vs = newvs
218 }
219 return vs
220 }
221 }
222
223 func literalValues(v ...Value) ValuesOpFunc {
224 return func(e *EvalCtx) []Value {
225 return v
226 }
227 }
228
229 func literalStr(text string) ValuesOpFunc {
230 return literalValues(String(text))
231 }
232
233 func variable(qname string) ValuesOpFunc {
234 explode, ns, name := ParseAndFixVariable(qname)
235 return func(ec *EvalCtx) []Value {
236 variable := ec.ResolveVar(ns, name)
237 if variable == nil {
238 throwf("variable $%s not found", qname)
239 }
240 value := variable.Get()
241 if explode {
242 iterator, ok := value.(Iterable)
243 if !ok {
244 // Use qname[1:] to skip the leading "@"
245 throwf("variable $%s (kind %s) cannot be exploded", qname[1:], value.Kind())
246 }
247 return collectFromIterable(iterator)
248 }
249 return []Value{value}
250 }
251 }
252
253 func (cp *compiler) primary(n *parse.Primary) ValuesOpFunc {
254 switch n.Type {
255 case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted:
256 return literalStr(n.Value)
257 case parse.Variable:
258 qname := n.Value
259 if !cp.registerVariableGet(qname) {
260 cp.errorf("variable $%s not found", n.Value)
261 }
262 return variable(qname)
263 case parse.Wildcard:
264 seg, err := wildcardToSegment(n.SourceText())
265 if err != nil {
266 cp.errorf("%s", err)
267 }
268 vs := []Value{
269 GlobPattern{glob.Pattern{[]glob.Segment{seg}, ""}, 0, nil}}
270 return func(ec *EvalCtx) []Value {
271 return vs
272 }
273 case parse.Tilde:
274 cp.errorf("compiler bug: Tilde not handled in .compound")
275 return literalStr("~")
276 case parse.ExceptionCapture:
277 return cp.exceptionCapture(n.Chunk)
278 case parse.OutputCapture:
279 return cp.outputCapture(n)
280 case parse.List:
281 return cp.list(n.List)
282 case parse.Lambda:
283 return cp.lambda(n)
284 case parse.Map:
285 return cp.map_(n)
286 case parse.Braced:
287 return cp.braced(n)
288 default:
289 cp.errorf("bad PrimaryType; parser bug")
290 return literalStr(n.SourceText())
291 }
292 }
293
294 func (cp *compiler) list(n *parse.Array) ValuesOpFunc {
295 if len(n.Semicolons) == 0 {
296 op := cp.arrayOp(n)
297 return func(ec *EvalCtx) []Value {
298 return []Value{NewList(op.Exec(ec)...)}
299 }
300 }
301 ns := len(n.Semicolons)
302 rowOps := make([]ValuesOpFunc, ns+1)
303 f := func(k, i, j int) {
304 rowOps[k] = catValuesOps(cp.compoundOps(n.Compounds[i:j]))
305 }
306 f(0, 0, n.Semicolons[0])
307 for i := 1; i < ns; i++ {
308 f(i, n.Semicolons[i-1], n.Semicolons[i])
309 }
310 f(ns, n.Semicolons[ns-1], len(n.Compounds))
311 return func(ec *EvalCtx) []Value {
312 rows := make([]Value, ns+1)
313 for i := 0; i <= ns; i++ {
314 rows[i] = NewList(rowOps[i](ec)...)
315 }
316 return []Value{NewList(rows...)}
317 }
318 }
319
320 func (cp *compiler) exceptionCapture(n *parse.Chunk) ValuesOpFunc {
321 op := cp.chunkOp(n)
322 return func(ec *EvalCtx) []Value {
323 err := ec.PEval(op)
324 if err == nil {
325 return []Value{OK}
326 }
327 return []Value{err.(*Exception)}
328 }
329 }
330
331 func (cp *compiler) outputCapture(n *parse.Primary) ValuesOpFunc {
332 op := cp.chunkOp(n.Chunk)
333 return func(ec *EvalCtx) []Value {
334 return captureOutput(ec, op)
335 }
336 }
337
338 func captureOutput(ec *EvalCtx, op Op) []Value {
339 vs, err := pcaptureOutput(ec, op)
340 maybeThrow(err)
341 return vs
342 }
343
344 func pcaptureOutput(ec *EvalCtx, op Op) ([]Value, error) {
345 vs := []Value{}
346 newEc := ec.fork(fmt.Sprintf("output capture %v", op))
347
348 pipeRead, pipeWrite, err := os.Pipe()
349 if err != nil {
350 throw(fmt.Errorf("failed to create pipe: %v", err))
351 }
352 bufferedPipeRead := bufio.NewReader(pipeRead)
353 ch := make(chan Value, outputCaptureBufferSize)
354 bytesCollected := make(chan bool)
355 chCollected := make(chan bool)
356 newEc.ports[1] = &Port{Chan: ch, File: pipeWrite, CloseFile: true}
357 go func() {
358 for v := range ch {
359 vs = append(vs, v)
360 }
361 chCollected <- true
362 }()
363 go func() {
364 for {
365 line, err := bufferedPipeRead.ReadString('\n')
366 if err == io.EOF {
367 break
368 } else if err != nil {
369 // TODO report error
370 log.Println(err)
371 break
372 }
373 ch <- String(line[:len(line)-1])
374 }
375 bytesCollected <- true
376 }()
377
378 err = newEc.PEval(op)
379 ClosePorts(newEc.ports)
380
381 <-bytesCollected
382 pipeRead.Close()
383
384 close(ch)
385 <-chCollected
386
387 return vs, err
388 }
389
390 func (cp *compiler) lambda(n *parse.Primary) ValuesOpFunc {
391 // Collect argument names
392 var argNames []string
393 var restArg string
394 if n.List == nil {
395 // { chunk }
396 restArg = unnamedRestArg
397 } else {
398 // [argument list]{ chunk }
399 argNames = make([]string, len(n.List.Compounds))
400 for i, arg := range n.List.Compounds {
401 qname := mustString(cp, arg, "expect string")
402 explode, ns, name := ParseAndFixVariable(qname)
403 if ns != "" {
404 cp.errorpf(arg.Begin(), arg.End(), "must be unqualified")
405 }
406 if name == "" {
407 cp.errorpf(arg.Begin(), arg.End(), "argument name must not be empty")
408 }
409 if explode {
410 if i != len(n.List.Compounds)-1 {
411 cp.errorpf(arg.Begin(), arg.End(), "only the last argument may have @")
412 }
413 restArg = name
414 argNames = argNames[:i]
415 } else {
416 argNames[i] = name
417 }
418 }
419 }
420
421 // XXX The fiddlings with cp.capture is error-prone.
422 thisScope := cp.pushScope()
423 for _, argName := range argNames {
424 thisScope[argName] = true
425 }
426 if restArg != "" {
427 thisScope[restArg] = true
428 }
429 thisScope["args"] = true
430 thisScope["opts"] = true
431 op := cp.chunkOp(n.Chunk)
432 capture := cp.capture
433 cp.capture = scope{}
434 cp.popScope()
435
436 for name := range capture {
437 cp.registerVariableGet(name)
438 }
439
440 name, text := cp.name, cp.text
441
442 return func(ec *EvalCtx) []Value {
443 evCapture := make(map[string]Variable, len(capture))
444 for name := range capture {
445 evCapture[name] = ec.ResolveVar("", name)
446 }
447 return []Value{&Closure{argNames, restArg, op, evCapture, name, text}}
448 }
449 }
450
451 func (cp *compiler) map_(n *parse.Primary) ValuesOpFunc {
452 return cp.mapPairs(n.MapPairs)
453 }
454
455 func (cp *compiler) mapPairs(pairs []*parse.MapPair) ValuesOpFunc {
456 npairs := len(pairs)
457 keysOps := make([]ValuesOp, npairs)
458 valuesOps := make([]ValuesOp, npairs)
459 begins, ends := make([]int, npairs), make([]int, npairs)
460 for i, pair := range pairs {
461 keysOps[i] = cp.compoundOp(pair.Key)
462 if pair.Value == nil {
463 p := pair.End()
464 valuesOps[i] = ValuesOp{literalValues(Bool(true)), p, p}
465 } else {
466 valuesOps[i] = cp.compoundOp(pairs[i].Value)
467 }
468 begins[i], ends[i] = pair.Begin(), pair.End()
469 }
470 return func(ec *EvalCtx) []Value {
471 m := make(map[Value]Value)
472 for i := 0; i < npairs; i++ {
473 keys := keysOps[i].Exec(ec)
474 values := valuesOps[i].Exec(ec)
475 if len(keys) != len(values) {
476 ec.errorpf(begins[i], ends[i],
477 "%d keys but %d values", len(keys), len(values))
478 }
479 for j, key := range keys {
480 m[key] = values[j]
481 }
482 }
483 return []Value{Map{&m}}
484 }
485 }
486
487 func (cp *compiler) braced(n *parse.Primary) ValuesOpFunc {
488 ops := cp.compoundOps(n.Braced)
489 // TODO: n.IsRange
490 // isRange := n.IsRange
491 return catValuesOps(ops)
492 }
0 package eval
1
2 //go:generate ./boilerplate.py
3
4 import (
5 "fmt"
6 "strconv"
7
8 "github.com/elves/elvish/parse"
9 "github.com/elves/elvish/util"
10 )
11
12 type scope map[string]bool
13
14 // compiler maintains the set of states needed when compiling a single source
15 // file.
16 type compiler struct {
17 // Builtin scope.
18 builtin scope
19 // Lexical scopes.
20 scopes []scope
21 // Variables captured from outer scopes.
22 capture scope
23 // Position of what is being compiled.
24 begin, end int
25 // Information about the source.
26 name, text string
27 }
28
29 func compile(b, g scope, n *parse.Chunk, name, text string) (op Op, err error) {
30 cp := &compiler{b, []scope{g}, scope{}, 0, 0, name, text}
31 defer util.Catch(&err)
32 return cp.chunkOp(n), nil
33 }
34
35 func (cp *compiler) compiling(n parse.Node) {
36 cp.begin, cp.end = n.Begin(), n.End()
37 }
38
39 func (cp *compiler) errorpf(begin, end int, format string, args ...interface{}) {
40 throw(&CompilationError{fmt.Sprintf(format, args...),
41 util.SourceContext{cp.name, cp.text, begin, end, nil}})
42 }
43
44 func (cp *compiler) errorf(format string, args ...interface{}) {
45 cp.errorpf(cp.begin, cp.end, format, args...)
46 }
47
48 func (cp *compiler) thisScope() scope {
49 return cp.scopes[len(cp.scopes)-1]
50 }
51
52 func (cp *compiler) pushScope() scope {
53 sc := scope{}
54 cp.scopes = append(cp.scopes, sc)
55 return sc
56 }
57
58 func (cp *compiler) popScope() {
59 cp.scopes[len(cp.scopes)-1] = nil
60 cp.scopes = cp.scopes[:len(cp.scopes)-1]
61 }
62
63 func (cp *compiler) registerVariableGet(qname string) bool {
64 _, ns, name := ParseAndFixVariable(qname)
65 if ns != "" && ns != "local" && ns != "up" {
66 // Variable in another mod, do nothing
67 return true
68 }
69 _, err := strconv.Atoi(name)
70 isnum := err == nil
71 // Find in local scope
72 if ns == "" || ns == "local" {
73 if cp.thisScope()[name] || isnum {
74 return true
75 }
76 }
77 // Find in upper scopes
78 if ns == "" || ns == "up" {
79 for i := len(cp.scopes) - 2; i >= 0; i-- {
80 if cp.scopes[i][name] || isnum {
81 // Existing name: record capture and return.
82 cp.capture[name] = true
83 return true
84 }
85 }
86 }
87 // Find in builtin scope
88 if ns == "" || ns == "builtin" {
89 _, ok := cp.builtin[name]
90 if ok {
91 return true
92 }
93 }
94 return false
95 }
96
97 func (cp *compiler) registerVariableSet(qname string) bool {
98 _, ns, name := ParseAndFixVariable(qname)
99 switch ns {
100 case "local":
101 cp.thisScope()[name] = true
102 return true
103 case "up":
104 for i := len(cp.scopes) - 2; i >= 0; i-- {
105 if cp.scopes[i][name] {
106 // Existing name: record capture and return.
107 cp.capture[name] = true
108 return true
109 }
110 }
111 return false
112 case "builtin":
113 cp.errorf("cannot set builtin variable")
114 return false
115 case "":
116 if cp.thisScope()[name] {
117 // A name on current scope. Do nothing.
118 return true
119 }
120 // Walk up the upper scopes
121 for i := len(cp.scopes) - 2; i >= 0; i-- {
122 if cp.scopes[i][name] {
123 // Existing name. Do nothing
124 cp.capture[name] = true
125 return true
126 }
127 }
128 // New name. Register on this scope!
129 cp.thisScope()[name] = true
130 return true
131 default:
132 // Variable in another mod, do nothing
133 return true
134 }
135 }
0 package eval
1
2 import (
3 "errors"
4 "strconv"
5
6 "github.com/elves/elvish/daemon/api"
7 )
8
9 var ErrDaemonOffline = errors.New("daemon is offline")
10
11 func makeDaemonNamespace(daemon *api.Client) Namespace {
12 // Obtain process ID
13 daemonPid := func() Value {
14 req := &api.PidRequest{}
15 res := &api.PidResponse{}
16 err := daemon.CallDaemon("Pid", req, res)
17 maybeThrow(err)
18 return String(strconv.Itoa(res.Pid))
19 }
20
21 return Namespace{
22 "pid": MakeRoVariableFromCallback(daemonPid),
23 "sock": NewRoVariable(String(daemon.SockPath())),
24
25 FnPrefix + "spawn": NewRoVariable(&BuiltinFn{"daemon:spawn", daemonSpawn}),
26 }
27 }
28
29 func daemonSpawn(ec *EvalCtx, args []Value, opts map[string]Value) {
30 TakeNoArg(args)
31 TakeNoOpt(opts)
32 ec.ToSpawn.Spawn()
33 }
0 package eval
1
2 import "sync"
3
4 // Editor is the interface that the line editor has to satisfy. It is needed so
5 // that this package does not depend on the edit package.
6 type Editor interface {
7 Active() bool
8 ActiveMutex() *sync.Mutex
9 Notify(string, ...interface{})
10 }
0 package eval
1
2 var embeddedModules = map[string]string{
3 "readline-binding": `fn bind-mode [m k f]{
4 edit:binding[$m][$k] = $f
5 }
6
7 fn bind [k f]{
8 bind-mode insert $k $f
9 }
10
11 bind Ctrl-A $edit:&move-dot-sol
12 bind Ctrl-B $edit:&move-dot-left
13 bind Ctrl-D {
14 if (> (count $edit:current-command) 0) {
15 edit:kill-rune-right
16 } else {
17 edit:return-eof
18 }
19 }
20 bind Ctrl-E $edit:&move-dot-eol
21 bind Ctrl-F $edit:&move-dot-right
22 bind Ctrl-H $edit:&kill-rune-left
23 bind Ctrl-L { clear > /dev/tty }
24 bind Ctrl-N $edit:&end-of-history
25 # TODO: ^O
26 bind Ctrl-P $edit:history:&start
27 # TODO: ^S ^T ^X family ^Y ^_
28 bind Alt-b $edit:&move-dot-left-word
29 # TODO Alt-c Alt-d
30 bind Alt-f $edit:&move-dot-right-word
31 # TODO Alt-l Alt-r Alt-u
32
33 # Ctrl-N and Ctrl-L occupied by readline binding, bind to Alt- instead.
34 bind Alt-n $edit:nav:&start
35 bind Alt-l $edit:loc:&start
36
37 bind-mode completion Ctrl-B $edit:compl:&left
38 bind-mode completion Ctrl-F $edit:compl:&right
39 bind-mode completion Ctrl-N $edit:compl:&down
40 bind-mode completion Ctrl-P $edit:compl:&up
41 bind-mode completion Alt-f $edit:compl:&trigger-filter
42
43 bind-mode navigation Ctrl-B $edit:nav:&left
44 bind-mode navigation Ctrl-F $edit:nav:&right
45 bind-mode navigation Ctrl-N $edit:nav:&down
46 bind-mode navigation Ctrl-P $edit:nav:&up
47 bind-mode navigation Alt-f $edit:nav:&trigger-filter
48
49 bind-mode history Ctrl-N $edit:history:&down-or-quit
50 bind-mode history Ctrl-P $edit:history:&up
51 bind-mode history Ctrl-G $edit:insert:&start
52
53 # Binding for the listing "super mode".
54 bind-mode listing Ctrl-N $edit:listing:&down
55 bind-mode listing Ctrl-P $edit:listing:&up
56 bind-mode listing Ctrl-V $edit:listing:&page-down
57 bind-mode listing Alt-v $edit:listing:&page-up
58 bind-mode listing Ctrl-G $edit:insert:&start
59
60 bind-mode histlist Alt-g $edit:histlist:&toggle-case-sensitivity
61 bind-mode histlist Alt-d $edit:histlist:&toggle-dedup
62 `,
63 }
0 package eval
1
2 import (
3 "errors"
4 "os"
5 "strings"
6 "sync"
7 )
8
9 // Errors
10 var (
11 ErrCanOnlyAssignList = errors.New("can only assign compatible values")
12 ErrPathMustBeString = errors.New("path must be string")
13 ErrPathCannotContainColonZero = errors.New(`path cannot contain colon or \0`)
14 )
15
16 // EnvPathList is a variable whose value is constructed from an environment
17 // variable by splitting at colons. Changes to it are also propagated to the
18 // corresponding environment variable. Its elements cannot contain colons or
19 // \0; attempting to put colon or \0 in its elements will result in an error.
20 //
21 // EnvPathList implements both Value and Variable interfaces. It also satisfied
22 // ListLike.
23 type EnvPathList struct {
24 sync.RWMutex
25 envName string
26 cachedValue string
27 cachedPaths []string
28 }
29
30 var (
31 _ Variable = (*EnvPathList)(nil)
32 _ Value = (*EnvPathList)(nil)
33 _ ListLike = (*EnvPathList)(nil)
34 )
35
36 // Get returns a Value for an EnvPathList.
37 func (epl *EnvPathList) Get() Value {
38 return epl
39 }
40
41 // Set sets an EnvPathList. The underlying environment variable is set.
42 func (epl *EnvPathList) Set(v Value) {
43 iterator, ok := v.(Iterable)
44 if !ok {
45 throw(ErrCanOnlyAssignList)
46 }
47 var paths []string
48 iterator.Iterate(func(v Value) bool {
49 s, ok := v.(String)
50 if !ok {
51 throw(ErrPathMustBeString)
52 }
53 path := string(s)
54 if strings.ContainsAny(path, ":\x00") {
55 throw(ErrPathCannotContainColonZero)
56 }
57 paths = append(paths, string(s))
58 return true
59 })
60 epl.set(paths)
61 }
62
63 // Kind returns "list".
64 func (epl *EnvPathList) Kind() string {
65 return "list"
66 }
67
68 // Repr returns the representation of an EnvPathList, as if it were an ordinary
69 // list.
70 func (epl *EnvPathList) Repr(indent int) string {
71 var b ListReprBuilder
72 b.Indent = indent
73 for _, path := range epl.get() {
74 b.WriteElem(quote(path))
75 }
76 return b.String()
77 }
78
79 // Len returns the length of an EnvPathList.
80 func (epl *EnvPathList) Len() int {
81 return len(epl.get())
82 }
83
84 // Iterate iterates an EnvPathList.
85 func (epl *EnvPathList) Iterate(f func(Value) bool) {
86 for _, p := range epl.get() {
87 if !f(String(p)) {
88 break
89 }
90 }
91 }
92
93 // IndexOne returns the result of one indexing operation.
94 func (epl *EnvPathList) IndexOne(idx Value) Value {
95 paths := epl.get()
96 slice, i, j := ParseAndFixListIndex(ToString(idx), len(paths))
97 if slice {
98 sliced := paths[i:j]
99 values := make([]Value, len(sliced))
100 for i, p := range sliced {
101 values[i] = String(p)
102 }
103 return NewList(values...)
104 }
105 return String(paths[i])
106 }
107
108 // IndexSet sets one value in an EnvPathList.
109 func (epl *EnvPathList) IndexSet(idx, v Value) {
110 s, ok := v.(String)
111 if !ok {
112 throw(ErrPathMustBeString)
113 }
114
115 paths := epl.get()
116 slice, i, _ := ParseAndFixListIndex(ToString(idx), len(paths))
117 if slice {
118 throw(errors.New("slice set unimplemented"))
119 }
120
121 epl.Lock()
122 defer epl.Unlock()
123 paths[i] = string(s)
124 epl.syncFromPaths()
125 }
126
127 func (epl *EnvPathList) get() []string {
128 epl.Lock()
129 defer epl.Unlock()
130
131 value := os.Getenv(epl.envName)
132 if value == epl.cachedValue {
133 return epl.cachedPaths
134 }
135 epl.cachedValue = value
136 epl.cachedPaths = strings.Split(value, ":")
137 return epl.cachedPaths
138 }
139
140 func (epl *EnvPathList) set(paths []string) {
141 epl.Lock()
142 defer epl.Unlock()
143
144 epl.cachedPaths = paths
145 epl.syncFromPaths()
146 }
147
148 func (epl *EnvPathList) syncFromPaths() {
149 epl.cachedValue = strings.Join(epl.cachedPaths, ":")
150 err := os.Setenv(epl.envName, epl.cachedValue)
151 maybeThrow(err)
152 }
0 // Package eval handles evaluation of nodes and consists the runtime of the
1 // shell.
2 package eval
3
4 //go:generate ./gen-embedded-modules
5
6 import (
7 "bufio"
8 "bytes"
9 "errors"
10 "fmt"
11 "io"
12 "io/ioutil"
13 "os"
14 "os/signal"
15 "strconv"
16 "strings"
17 "sync"
18 "syscall"
19 "unicode/utf8"
20
21 "github.com/elves/elvish/daemon"
22 "github.com/elves/elvish/daemon/api"
23 "github.com/elves/elvish/parse"
24 "github.com/elves/elvish/sys"
25 "github.com/elves/elvish/util"
26 )
27
28 var logger = util.GetLogger("[eval] ")
29
30 // FnPrefix is the prefix for the variable names of functions. Defining a
31 // function "foo" is equivalent to setting a variable named FnPrefix + "foo".
32 const FnPrefix = "&"
33
34 // Namespace is a map from name to variables.
35 type Namespace map[string]Variable
36
37 // Evaler is used to evaluate elvish sources. It maintains runtime context
38 // shared among all evalCtx instances.
39 type Evaler struct {
40 Builtin Namespace
41 Global Namespace
42 Modules map[string]Namespace
43 Daemon *api.Client
44 ToSpawn *daemon.Daemon
45 Editor Editor
46 DataDir string
47 intCh chan struct{}
48 }
49
50 // EvalCtx maintains an Evaler along with its runtime context. After creation
51 // an EvalCtx is seldom modified, and new instances are created when needed.
52 type EvalCtx struct {
53 *Evaler
54 name, srcName, src string
55
56 local, up Namespace
57 ports []*Port
58 positionals []Value
59
60 begin, end int
61 traceback *util.SourceContext
62
63 background bool
64 }
65
66 // NewEvaler creates a new Evaler.
67 func NewEvaler(daemon *api.Client, toSpawn *daemon.Daemon,
68 dataDir string, extraModules map[string]Namespace) *Evaler {
69
70 // TODO(xiaq): Create daemon namespace asynchronously.
71 modules := map[string]Namespace{
72 "daemon": makeDaemonNamespace(daemon),
73 }
74 for name, mod := range extraModules {
75 modules[name] = mod
76 }
77
78 return &Evaler{
79 Builtin: makeBuiltinNamespace(daemon),
80 Global: Namespace{},
81 Modules: modules,
82 Daemon: daemon,
83 ToSpawn: toSpawn,
84 Editor: nil,
85 DataDir: dataDir,
86 intCh: nil,
87 }
88 }
89
90 func (ev *Evaler) searchPaths() []string {
91 return ev.Builtin["paths"].(*EnvPathList).get()
92 }
93
94 const (
95 outChanSize = 32
96 outChanLeader = "▶ "
97 falseIndicator = "✗"
98 initIndent = NoPretty
99 )
100
101 // NewTopEvalCtx creates a top-level evalCtx.
102 func NewTopEvalCtx(ev *Evaler, name, text string, ports []*Port) *EvalCtx {
103 return &EvalCtx{
104 ev, "top",
105 name, text,
106 ev.Global, Namespace{},
107 ports, nil,
108 0, len(text), nil, false,
109 }
110 }
111
112 // fork returns a modified copy of ec. The ports are forked, and the name is
113 // changed to the given value. Other fields are copied shallowly.
114 func (ec *EvalCtx) fork(name string) *EvalCtx {
115 newPorts := make([]*Port, len(ec.ports))
116 for i, p := range ec.ports {
117 newPorts[i] = p.Fork()
118 }
119 return &EvalCtx{
120 ec.Evaler, name,
121 ec.srcName, ec.src,
122 ec.local, ec.up,
123 newPorts, ec.positionals,
124 ec.begin, ec.end, ec.traceback, ec.background,
125 }
126 }
127
128 // port returns ec.ports[i] or nil if i is out of range. This makes it possible
129 // to treat ec.ports as if it has an infinite tail of nil's.
130 func (ec *EvalCtx) port(i int) *Port {
131 if i >= len(ec.ports) {
132 return nil
133 }
134 return ec.ports[i]
135 }
136
137 // growPorts makes the size of ec.ports at least n, adding nil's if necessary.
138 func (ec *EvalCtx) growPorts(n int) {
139 if len(ec.ports) >= n {
140 return
141 }
142 ports := ec.ports
143 ec.ports = make([]*Port, n)
144 copy(ec.ports, ports)
145 }
146
147 func makeScope(s Namespace) scope {
148 sc := scope{}
149 for name := range s {
150 sc[name] = true
151 }
152 return sc
153 }
154
155 // eval evaluates a chunk node n. The supplied name and text are used in
156 // diagnostic messages.
157 func (ev *Evaler) eval(op Op, ports []*Port, name, text string) error {
158 ec := NewTopEvalCtx(ev, name, text, ports)
159 return ec.PEval(op)
160 }
161
162 func (ec *EvalCtx) Interrupts() <-chan struct{} {
163 return ec.intCh
164 }
165
166 // Eval sets up the Evaler with standard ports and evaluates an Op. The supplied
167 // name and text are used in diagnostic messages.
168 func (ev *Evaler) Eval(op Op, name, text string) error {
169 inCh := make(chan Value)
170 close(inCh)
171
172 outCh := make(chan Value, outChanSize)
173 outDone := make(chan struct{})
174 go func() {
175 for v := range outCh {
176 fmt.Println(outChanLeader + v.Repr(initIndent))
177 }
178 close(outDone)
179 }()
180 defer func() {
181 close(outCh)
182 <-outDone
183 }()
184
185 ports := []*Port{
186 {File: os.Stdin, Chan: inCh},
187 {File: os.Stdout, Chan: outCh},
188 {File: os.Stderr, Chan: BlackholeChan},
189 }
190
191 return ev.EvalWithPorts(ports, op, name, text)
192 }
193
194 // EvalWithPorts sets up the Evaler with the given ports and evaluates an Op.
195 // The supplied name and text are used in diagnostic messages.
196 func (ev *Evaler) EvalWithPorts(ports []*Port, op Op, name, text string) error {
197 // signal.Ignore(syscall.SIGTTIN)
198
199 // Ignore TTOU.
200 // When a subprocess in its own process group puts itself in the foreground,
201 // the elvish will be in the background. In that case, elvish will move
202 // itself back to the foreground by calling tcsetpgrp. However, whenever a
203 // background process calls tcsetpgrp (or otherwise attempts to modify the
204 // terminal configuration), TTOU will be sent, whose default handler is to
205 // stop the process. When the process lives in an orphaned process group
206 // (most likely for elvish), the call will outright fail. Therefore, for
207 // elvish to be able to move itself back to the foreground, we need to
208 // ignore TTOU.
209 signal.Ignore(syscall.SIGTTOU)
210 stopSigGoroutine := make(chan struct{})
211 sigGoRoutineDone := make(chan struct{})
212 // Set up intCh.
213 ev.intCh = make(chan struct{})
214 sigCh := make(chan os.Signal)
215 signal.Notify(sigCh, syscall.SIGINT, syscall.SIGQUIT)
216 go func() {
217 closedIntCh := false
218 loop:
219 for {
220 select {
221 case <-sigCh:
222 if !closedIntCh {
223 close(ev.intCh)
224 closedIntCh = true
225 }
226 case <-stopSigGoroutine:
227 break loop
228 }
229 }
230 ev.intCh = nil
231 signal.Stop(sigCh)
232 close(sigGoRoutineDone)
233 }()
234
235 err := ev.eval(op, ports, name, text)
236
237 close(stopSigGoroutine)
238 <-sigGoRoutineDone
239
240 // Put myself in foreground, in case some command has put me in background.
241 // XXX Should probably use fd of /dev/tty instead of 0.
242 if sys.IsATTY(0) {
243 err := sys.Tcsetpgrp(0, syscall.Getpgrp())
244 if err != nil {
245 fmt.Println("failed to put myself in foreground:", err)
246 }
247 }
248
249 // Un-ignore TTOU.
250 signal.Reset(syscall.SIGTTOU)
251
252 return err
253 }
254
255 func summarize(text string) string {
256 // TODO Make a proper summary.
257 if len(text) < 32 {
258 return text
259 }
260 var b bytes.Buffer
261 for i, r := range text {
262 if i+len(string(r)) >= 32 {
263 break
264 }
265 b.WriteRune(r)
266 }
267 return b.String()
268 }
269
270 // Compile compiles elvish code in the global scope. If the error is not nil, it
271 // always has type CompilationError.
272 func (ev *Evaler) Compile(n *parse.Chunk, name, text string) (Op, error) {
273 return compile(makeScope(ev.Builtin), makeScope(ev.Global), n, name, text)
274 }
275
276 // PEval evaluates an op in a protected environment so that calls to errorf are
277 // wrapped in an Error.
278 func (ec *EvalCtx) PEval(op Op) (err error) {
279 defer catch(&err, ec)
280 op.Exec(ec)
281 return nil
282 }
283
284 func (ec *EvalCtx) PCall(f Callable, args []Value, opts map[string]Value) (err error) {
285 defer catch(&err, ec)
286 f.Call(ec, args, opts)
287 return nil
288 }
289
290 func (ec *EvalCtx) PCaptureOutput(f Callable, args []Value, opts map[string]Value) (vs []Value, err error) {
291 // XXX There is no source.
292 return pcaptureOutput(ec, Op{
293 func(newec *EvalCtx) { f.Call(newec, args, opts) }, -1, -1})
294 }
295
296 func catch(perr *error, ec *EvalCtx) {
297 // NOTE: We have to duplicate instead of calling util.Catch here, since
298 // recover can only catch a panic when called directly from a deferred
299 // function.
300 r := recover()
301 if r == nil {
302 return
303 }
304 if exc, ok := r.(util.Thrown); ok {
305 err := exc.Error
306 if _, ok := err.(*Exception); !ok {
307 err = ec.makeException(err)
308 }
309 *perr = err
310 } else if r != nil {
311 panic(r)
312 }
313 }
314
315 // makeException turns an error into an Exception by adding traceback.
316 func (ec *EvalCtx) makeException(e error) *Exception {
317 return &Exception{e, ec.addTraceback()}
318 }
319
320 func (ec *EvalCtx) addTraceback() *util.SourceContext {
321 return &util.SourceContext{
322 Name: ec.srcName, Source: ec.src,
323 Begin: ec.begin, End: ec.end, Next: ec.traceback,
324 }
325 }
326
327 // errorpf stops the ec.eval immediately by panicking with a diagnostic message.
328 // The panic is supposed to be caught by ec.eval.
329 func (ec *EvalCtx) errorpf(begin, end int, format string, args ...interface{}) {
330 ec.begin, ec.end = begin, end
331 throwf(format, args...)
332 }
333
334 // SourceText evaluates a chunk of elvish source.
335 func (ev *Evaler) SourceText(name, src string) error {
336 n, err := parse.Parse(name, src)
337 if err != nil {
338 return err
339 }
340 op, err := ev.Compile(n, name, src)
341 if err != nil {
342 return err
343 }
344 return ev.Eval(op, name, src)
345 }
346
347 func readFileUTF8(fname string) (string, error) {
348 bytes, err := ioutil.ReadFile(fname)
349 if err != nil {
350 return "", err
351 }
352 if !utf8.Valid(bytes) {
353 return "", fmt.Errorf("%s: source is not valid UTF-8", fname)
354 }
355 return string(bytes), nil
356 }
357
358 // Source evaluates the content of a file.
359 func (ev *Evaler) Source(fname string) error {
360 src, err := readFileUTF8(fname)
361 if err != nil {
362 return err
363 }
364 return ev.SourceText(fname, src)
365 }
366
367 // ErrStoreUnconnected is thrown by ResolveVar when a shared: variable needs to
368 // be resolved but the store is not connected.
369 var ErrStoreUnconnected = errors.New("store unconnected")
370
371 // ResolveVar resolves a variable. When the variable cannot be found, nil is
372 // returned.
373 func (ec *EvalCtx) ResolveVar(ns, name string) Variable {
374 switch ns {
375 case "local":
376 return ec.getLocal(name)
377 case "up":
378 return ec.up[name]
379 case "builtin":
380 return ec.Builtin[name]
381 case "":
382 if v := ec.getLocal(name); v != nil {
383 return v
384 }
385 if v, ok := ec.up[name]; ok {
386 return v
387 }
388 return ec.Builtin[name]
389 case "e":
390 if strings.HasPrefix(name, FnPrefix) {
391 return NewRoVariable(ExternalCmd{name[len(FnPrefix):]})
392 }
393 case "E":
394 return envVariable{name}
395 case "shared":
396 if ec.Daemon == nil {
397 throw(ErrStoreUnconnected)
398 }
399 return sharedVariable{ec.Daemon, name}
400 default:
401 if ns, ok := ec.Modules[ns]; ok {
402 return ns[name]
403 }
404 }
405 return nil
406 }
407
408 // getLocal finds the named local variable.
409 func (ec *EvalCtx) getLocal(name string) Variable {
410 i, err := strconv.Atoi(name)
411 if err == nil {
412 // Logger.Println("positional variable", i)
413 // Logger.Printf("EvalCtx=%p, args=%v", ec, ec.positionals)
414 if i < 0 {
415 i += len(ec.positionals)
416 }
417 if i < 0 || i >= len(ec.positionals) {
418 // Logger.Print("out of range")
419 return nil
420 }
421 // Logger.Print("found")
422 return NewRoVariable(ec.positionals[i])
423 }
424 return ec.local[name]
425 }
426
427 var ErrMoreThanOneRest = errors.New("more than one @ lvalue")
428
429 // IterateInputs calls the passed function for each input element.
430 func (ec *EvalCtx) IterateInputs(f func(Value)) {
431 var w sync.WaitGroup
432 inputs := make(chan Value)
433
434 w.Add(2)
435 go func() {
436 linesToChan(ec.ports[0].File, inputs)
437 w.Done()
438 }()
439 go func() {
440 for v := range ec.ports[0].Chan {
441 inputs <- v
442 }
443 w.Done()
444 }()
445 go func() {
446 w.Wait()
447 close(inputs)
448 }()
449
450 for v := range inputs {
451 f(v)
452 }
453 }
454
455 func linesToChan(r io.Reader, ch chan<- Value) {
456 filein := bufio.NewReader(r)
457 for {
458 line, err := filein.ReadString('\n')
459 if line != "" {
460 ch <- String(strings.TrimSuffix(line, "\n"))
461 }
462 if err != nil {
463 if err != io.EOF {
464 logger.Println("error on reading:", err)
465 }
466 break
467 }
468 }
469 }
470
471 // OutputChan returns a channel onto which output can be written.
472 func (ec *EvalCtx) OutputChan() chan<- Value {
473 return ec.ports[1].Chan
474 }
475
476 // OutputFile returns a file onto which output can be written.
477 func (ec *EvalCtx) OutputFile() *os.File {
478 return ec.ports[1].File
479 }
0 package eval
1
2 import (
3 "errors"
4 "os"
5 "reflect"
6 "strconv"
7 "syscall"
8 "testing"
9
10 "github.com/elves/elvish/daemon/api"
11 "github.com/elves/elvish/parse"
12 "github.com/elves/elvish/util"
13 )
14
15 func TestBuiltinPid(t *testing.T) {
16 pid := strconv.Itoa(syscall.Getpid())
17 builtinPid := ToString(makeBuiltinNamespace(nil)["pid"].Get())
18 if builtinPid != pid {
19 t.Errorf(`ev.builtin["pid"] = %v, want %v`, builtinPid, pid)
20 }
21 }
22
23 var errAny = errors.New("")
24
25 type more struct {
26 wantBytesOut []byte
27 wantError error
28 }
29
30 var noout = []Value{}
31 var nomore more
32
33 var evalTests = []struct {
34 text string
35 wantOut []Value
36 more
37 }{
38 // Chunks.
39 // Empty chunk
40 {"", []Value{}, nomore},
41 // Outputs of pipelines in a chunk are concatenated
42 {"put x; put y; put z", strs("x", "y", "z"), nomore},
43 // A failed pipeline cause the whole chunk to fail
44 {"put a; e:false; put b", strs("a"), more{wantError: errAny}},
45
46 // Pipelines.
47 // Pure byte pipeline
48 {`echo "Albert\nAllan\nAlbraham\nBerlin" | sed s/l/1/g | grep e`,
49 []Value{}, more{wantBytesOut: []byte("A1bert\nBer1in\n")}},
50 // Pure channel pipeline
51 {`put 233 42 19 | each [x]{+ $x 10}`, strs("243", "52", "29"), nomore},
52 // TODO: Add a useful hybrid pipeline sample
53
54 // List element assignment
55 // {"li=[foo bar]; li[0]=233; put $@li", strs("233", "bar"), nomore},
56 // Map element assignment
57 {"di=[&k=v]; di[k]=lorem; di[k2]=ipsum; put $di[k] $di[k2]",
58 strs("lorem", "ipsum"), nomore},
59 {"d=[&a=[&b=v]]; put $d[a][b]; d[a][b]=u; put $d[a][b]",
60 strs("v", "u"), nomore},
61 // Multi-assignments.
62 {"{a,b}=`put a b`; put $a $b", strs("a", "b"), nomore},
63 {"@a=`put a b`; put $@a", strs("a", "b"), nomore},
64 {"{a,@b}=`put a b c`; put $@b", strs("b", "c"), nomore},
65 // {"di=[&]; di[a b]=`put a b`; put $di[a] $di[b]", strs("a", "b"), nomore},
66 // Temporary assignment.
67 {"a=alice b=bob; {a,@b}=(put amy ben) put $a $@b; put $a $b",
68 strs("amy", "ben", "alice", "bob"), nomore},
69 // Spacey assignment.
70 {"a @b = 2 3 foo; put $a $b[1]", strs("2", "foo"), nomore},
71 // Spacey assignment with temporary assignment
72 {"x = 1; x=2 y = (+ 1 $x); put $x $y", strs("1", "3"), nomore},
73
74 // Control structures.
75 // if
76 {"if true { put then }", strs("then"), nomore},
77 {"if $false { put then } else { put else }", strs("else"), nomore},
78 {"if $false { put 1 } elif $false { put 2 } else { put 3 }",
79 strs("3"), nomore},
80 {"if $false { put 2 } elif true { put 2 } else { put 3 }",
81 strs("2"), nomore},
82 // try
83 {"try { nop } except { put bad } else { put good }", strs("good"), nomore},
84 {"try { e:false } except - { put bad } else { put good }", strs("bad"), nomore},
85 // while
86 {"x=0; while (< $x 4) { put $x; x=(+ $x 1) }",
87 strs("0", "1", "2", "3"), nomore},
88 // for
89 {"for x [tempora mores] { put 'O '$x }",
90 strs("O tempora", "O mores"), nomore},
91 // break
92 {"for x [a] { break } else { put $x }", noout, nomore},
93 // else
94 {"for x [a] { put $x } else { put $x }", strs("a"), nomore},
95 // continue
96 {"for x [a b] { put $x; continue; put $x; }", strs("a", "b"), nomore},
97
98 // Redirections.
99 {"f=`mktemp elvXXXXXX`; echo 233 > $f; cat < $f; rm $f", noout,
100 more{wantBytesOut: []byte("233\n")}},
101 // Redirections from File object.
102 {`fname=(mktemp elvXXXXXX); echo haha > $fname;
103 f=(fopen $fname); cat <$f; fclose $f; rm $fname`, noout,
104 more{wantBytesOut: []byte("haha\n")}},
105 // Redirections from Pipe object.
106 {`p=(pipe); echo haha > $p; pwclose $p; cat < $p; prclose $p`, noout,
107 more{wantBytesOut: []byte("haha\n")}},
108
109 // Compounding.
110 {"put {fi,elvi}sh{1.0,1.1}",
111 strs("fish1.0", "fish1.1", "elvish1.0", "elvish1.1"), nomore},
112
113 // List, map and indexing
114 {"echo [a b c] [&key=value] | each put",
115 strs("[a b c] [&key=value]"), nomore},
116 {"put [a b c][2]", strs("c"), nomore},
117 {"put [;a;b c][2][0]", strs("b"), nomore},
118 {"put [&key=value][key]", strs("value"), nomore},
119
120 // String literal
121 {`put 'such \"''literal'`, strs(`such \"'literal`), nomore},
122 {`put "much \n\033[31;1m$cool\033[m"`,
123 strs("much \n\033[31;1m$cool\033[m"), nomore},
124
125 // Output capture
126 {"put (put lorem ipsum)", strs("lorem", "ipsum"), nomore},
127
128 // Exception capture
129 {"bool ?(nop); bool ?(e:false)", bools(true, false), nomore},
130
131 // Variable and compounding
132 {"x='SHELL'\nput 'WOW, SUCH '$x', MUCH COOL'\n",
133 strs("WOW, SUCH SHELL, MUCH COOL"), nomore},
134 // Splicing
135 {"x=[elvish rules]; put $@x", strs("elvish", "rules"), nomore},
136
137 // Wildcard.
138 {"put /*", strs(util.FullNames("/")...), nomore},
139 // XXX assumes there is no /a/b/nonexistent*
140 {"put /a/b/nonexistent*", noout, more{wantError: ErrWildcardNoMatch}},
141 {"put /a/b/nonexistent*[nomatch-ok]", noout, nomore},
142
143 // Tilde.
144 {"h=$E:HOME; E:HOME=/foo; put ~ ~/src; E:HOME=$h",
145 strs("/foo", "/foo/src"), nomore},
146
147 // Closure
148 // Basics
149 {"[]{ }", noout, nomore},
150 {"[x]{put $x} foo", strs("foo"), nomore},
151 // Variable capture
152 {"x=lorem; []{x=ipsum}; put $x", strs("ipsum"), nomore},
153 {"x=lorem; []{ put $x; x=ipsum }; put $x",
154 strs("lorem", "ipsum"), nomore},
155 // Shadowing
156 {"x=ipsum; []{ local:x=lorem; put $x }; put $x",
157 strs("lorem", "ipsum"), nomore},
158 // Shadowing by argument
159 {"x=ipsum; [x]{ put $x; x=BAD } lorem; put $x",
160 strs("lorem", "ipsum"), nomore},
161 // Closure captures new local variables every time
162 {`fn f []{ x=0; put []{x=(+ $x 1)} []{put $x} }
163 {inc1,put1}=(f); $put1; $inc1; $put1
164 {inc2,put2}=(f); $put2; $inc2; $put2`,
165 strs("0", "1", "0", "1"), nomore},
166 // Positional variables.
167 {`{ put $1 } lorem ipsum`, strs("ipsum"), nomore},
168 // Positional variables in the up: namespace.
169 {`{ { put $up:0 } in } out`, strs("out"), nomore},
170
171 // fn.
172 {"fn f [x]{ put x=$x'.' }; f lorem; f ipsum",
173 strs("x=lorem.", "x=ipsum."), nomore},
174 // return.
175 {"fn f []{ put a; return; put b }; f", strs("a"), nomore},
176
177 // rest args and $args.
178 {"[x @xs]{ put $x $xs $args } a b c",
179 []Value{String("a"),
180 NewList(String("b"), String("c")),
181 NewList(String("a"), String("b"), String("c"))}, nomore},
182 // $args.
183 {"{ put $args } lorem ipsum",
184 []Value{NewList(String("lorem"), String("ipsum"))}, nomore},
185
186 // Namespaces
187 // Pseudo-namespaces local: and up:
188 {"x=lorem; []{local:x=ipsum; put $up:x $local:x}",
189 strs("lorem", "ipsum"), nomore},
190 {"x=lorem; []{up:x=ipsum; put $x}; put $x",
191 strs("ipsum", "ipsum"), nomore},
192 // Pseudo-namespace E:
193 {"E:FOO=lorem; put $E:FOO", strs("lorem"), nomore},
194 {"del E:FOO; put $E:FOO", strs(""), nomore},
195 // TODO: Test module namespace
196
197 // Builtin functions
198 // -----------------
199
200 {"kind-of bare 'str' [] [&] []{ }",
201 strs("string", "string", "list", "map", "fn"), nomore},
202
203 {`put foo bar`, strs("foo", "bar"), nomore},
204 {`explode [foo bar]`, strs("foo", "bar"), nomore},
205
206 {`print [foo bar]`, noout, more{wantBytesOut: []byte("[foo bar]")}},
207 {`echo [foo bar]`, noout, more{wantBytesOut: []byte("[foo bar]\n")}},
208 {`pprint [foo bar]`, noout, more{wantBytesOut: []byte("[\n foo\n bar\n]\n")}},
209
210 {`print "a\nb" | slurp`, strs("a\nb"), nomore},
211 {`print "a\nb" | from-lines`, strs("a", "b"), nomore},
212 {`print "a\nb\n" | from-lines`, strs("a", "b"), nomore},
213 {`echo '{"k": "v", "a": [1, 2]}' '"foo"' | from-json`, []Value{
214 NewMap(map[Value]Value{
215 String("k"): String("v"),
216 String("a"): NewList(strs("1", "2")...)}),
217 String("foo"),
218 }, nomore},
219 {`echo 'invalid' | from-json`, noout, more{wantError: errAny}},
220
221 {`put "l\norem" ipsum | to-lines`, noout,
222 more{wantBytesOut: []byte("l\norem\nipsum\n")}},
223 {`put [&k=v &a=[1 2]] foo | to-json`, noout,
224 more{wantBytesOut: []byte(`{"a":["1","2"],"k":"v"}
225 "foo"
226 `)}},
227
228 {`joins : [/usr /bin /tmp]`, strs("/usr:/bin:/tmp"), nomore},
229 {`splits &sep=: /usr:/bin:/tmp`, strs("/usr", "/bin", "/tmp"), nomore},
230 {`has-prefix golang go`, bools(true), nomore},
231 {`has-prefix golang x`, bools(false), nomore},
232 {`has-suffix golang x`, bools(false), nomore},
233
234 {`==s haha haha`, bools(true), nomore},
235 {`==s 10 10.0`, bools(false), nomore},
236 {`<s a b`, bools(true), nomore},
237 {`<s 2 10`, bools(false), nomore},
238
239 {`fail haha`, noout, more{wantError: errAny}},
240 {`return`, noout, more{wantError: Return}},
241
242 {`f=(constantly foo); $f; $f`, strs("foo", "foo"), nomore},
243 {`(constantly foo) bad`, noout, more{wantError: errAny}},
244 {`put 1 233 | each put`, strs("1", "233"), nomore},
245 {`echo "1\n233" | each put`, strs("1", "233"), nomore},
246 {`each put [1 233]`, strs("1", "233"), nomore},
247 {`range 10 | each { if (== $0 4) { break }; put $0 }`, strs("0", "1", "2", "3"), nomore},
248 {`range 10 | each { if (== $0 4) { fail haha }; put $0 }`, strs("0", "1", "2", "3"), more{wantError: errAny}},
249 {`repeat 4 foo`, strs("foo", "foo", "foo", "foo"), nomore},
250 // TODO: test peach
251
252 {`range 3`, strs("0", "1", "2"), nomore},
253 {`range 1 3`, strs("1", "2"), nomore},
254 {`range 0 10 &step=3`, strs("0", "3", "6", "9"), nomore},
255 {`range 100 | take 2`, strs("0", "1"), nomore},
256 {`range 100 | count`, strs("100"), nomore},
257 {`count [(range 100)]`, strs("100"), nomore},
258
259 {`echo " ax by cz \n11\t22 33" | eawk { put $args[-1] }`,
260 strs("cz", "33"), nomore},
261
262 {`path-base a/b/c.png`, strs("c.png"), nomore},
263
264 // TODO test more edge cases
265 {"+ 233100 233", strs("233333"), nomore},
266 {"- 233333 233100", strs("233"), nomore},
267 {"- 233", strs("-233"), nomore},
268 {"* 353 661", strs("233333"), nomore},
269 {"/ 233333 353", strs("661"), nomore},
270 {"/ 1 0", strs("+Inf"), nomore},
271 {"^ 16 2", strs("256"), nomore},
272 {"% 23 7", strs("2"), nomore},
273
274 {`== 1 1.0`, bools(true), nomore},
275 {`== 10 0xa`, bools(true), nomore},
276 {`== a a`, noout, more{wantError: errAny}},
277 {`> 0x10 1`, bools(true), nomore},
278
279 {`is 1 1`, bools(true), nomore},
280 {`is [] []`, bools(true), nomore},
281 {`is [1] [1]`, bools(false), nomore},
282 {`eq 1 1`, bools(true), nomore},
283 {`eq [] []`, bools(true), nomore},
284
285 {`ord a`, strs("0x61"), nomore},
286 {`base 16 42 233`, strs("2a", "e9"), nomore},
287 {`wcswidth 你好`, strs("4"), nomore},
288 }
289
290 func strs(ss ...string) []Value {
291 vs := make([]Value, len(ss))
292 for i, s := range ss {
293 vs[i] = String(s)
294 }
295 return vs
296 }
297
298 func bools(bs ...bool) []Value {
299 vs := make([]Value, len(bs))
300 for i, b := range bs {
301 vs[i] = Bool(b)
302 }
303 return vs
304 }
305
306 func mustParseAndCompile(t *testing.T, ev *Evaler, name, text string) Op {
307 n, err := parse.Parse(name, text)
308 if err != nil {
309 t.Fatalf("Parse(%q) error: %s", text, err)
310 }
311 op, err := ev.Compile(n, name, text)
312 if err != nil {
313 t.Fatalf("Compile(Parse(%q)) error: %s", text, err)
314 }
315 return op
316 }
317
318 func evalAndCollect(t *testing.T, texts []string, chsize int) ([]Value, []byte, error) {
319 name := "<eval test>"
320 ev := NewEvaler(api.NewClient("/invalid"), nil, "", nil)
321
322 // Collect byte output
323 outBytes := []byte{}
324 pr, pw, _ := os.Pipe()
325 bytesDone := make(chan struct{})
326 go func() {
327 for {
328 var buf [64]byte
329 nr, err := pr.Read(buf[:])
330 outBytes = append(outBytes, buf[:nr]...)
331 if err != nil {
332 break
333 }
334 }
335 close(bytesDone)
336 }()
337
338 // Channel output
339 outs := []Value{}
340
341 // Eval error. Only that of the last text is saved.
342 var ex error
343
344 for _, text := range texts {
345 op := mustParseAndCompile(t, ev, name, text)
346
347 outCh := make(chan Value, chsize)
348 outDone := make(chan struct{})
349 go func() {
350 for v := range outCh {
351 outs = append(outs, v)
352 }
353 close(outDone)
354 }()
355
356 ports := []*Port{
357 {File: os.Stdin, Chan: ClosedChan},
358 {File: pw, Chan: outCh},
359 {File: os.Stderr, Chan: BlackholeChan},
360 }
361
362 ex = ev.eval(op, ports, name, text)
363 close(outCh)
364 <-outDone
365 }
366
367 pw.Close()
368 <-bytesDone
369 pr.Close()
370
371 return outs, outBytes, ex
372 }
373
374 func TestEval(t *testing.T) {
375 for _, tt := range evalTests {
376 // fmt.Printf("eval %q\n", tt.text)
377
378 out, bytesOut, err := evalAndCollect(t, []string{tt.text}, len(tt.wantOut))
379
380 good := true
381 errorf := func(format string, args ...interface{}) {
382 if good {
383 good = false
384 t.Errorf("eval(%q) fails:", tt.text)
385 }
386 t.Errorf(format, args...)
387 }
388
389 if !reflect.DeepEqual(tt.wantOut, out) {
390 errorf("got out=%v, want %v", out, tt.wantOut)
391 }
392 if string(bytesOut) != string(tt.wantBytesOut) {
393 errorf("got bytesOut=%q, want %q", bytesOut, tt.wantBytesOut)
394 }
395 // Check exception cause. We accept errAny as a "wildcard" for all non-nil
396 // errors.
397 if err == nil {
398 if tt.wantError != nil {
399 errorf("got err=nil, want %v", tt.wantError)
400 }
401 } else {
402 exc := err.(*Exception)
403 if !(tt.wantError == errAny || reflect.DeepEqual(tt.wantError, exc.Cause)) {
404 errorf("got err=%v, want %v", err, tt.wantError)
405 }
406 }
407 if !good {
408 t.Errorf("--------------")
409 }
410 }
411 }
412
413 func TestMultipleEval(t *testing.T) {
414 texts := []string{"x=hello", "put $x"}
415 outs, _, err := evalAndCollect(t, texts, 1)
416 wanted := strs("hello")
417 if err != nil {
418 t.Errorf("eval %s => %v, want nil", texts, err)
419 }
420 if !reflect.DeepEqual(outs, wanted) {
421 t.Errorf("eval %s outputs %v, want %v", texts, outs, wanted)
422 }
423 }
0 package eval
1
2 import (
3 "bytes"
4 "fmt"
5 "strconv"
6 "strings"
7 "syscall"
8
9 "github.com/elves/elvish/parse"
10 "github.com/elves/elvish/util"
11 )
12
13 // Exception represents an elvish exception. It is both a Value accessible to
14 // elvishscript, and the type of error returned by public facing evaluation
15 // methods like (*Evaler)PEval.
16 type Exception struct {
17 Cause error
18 Traceback *util.SourceContext
19 }
20
21 // OK is a pointer to the zero value of Exception, representing the absence of
22 // exception.
23 var OK = &Exception{}
24
25 func (exc *Exception) Error() string {
26 return exc.Cause.Error()
27 }
28
29 func (exc *Exception) Pprint(indent string) string {
30 buf := new(bytes.Buffer)
31 // Error message
32 var msg string
33 if pprinter, ok := exc.Cause.(util.Pprinter); ok {
34 msg = pprinter.Pprint(indent)
35 } else {
36 msg = "\033[31;1m" + exc.Cause.Error() + "\033[m"
37 }
38 fmt.Fprintf(buf, "Exception: %s\n", msg)
39 buf.WriteString(indent + "Traceback:")
40
41 for tb := exc.Traceback; tb != nil; tb = tb.Next {
42 buf.WriteString("\n" + indent + " ")
43 tb.Pprint(buf, indent+" ")
44 }
45
46 if pipeExcs, ok := exc.Cause.(PipelineError); ok {
47 buf.WriteString("\n" + indent + "Caused by:")
48 for _, e := range pipeExcs.Errors {
49 if e == OK {
50 continue
51 }
52 buf.WriteString("\n" + indent + " " + e.Pprint(indent+" "))
53 }
54 }
55
56 return buf.String()
57 }
58
59 func (exc *Exception) Kind() string {
60 return "exception"
61 }
62
63 func (exc *Exception) Repr(indent int) string {
64 if exc.Cause == nil {
65 return "$ok"
66 }
67 if r, ok := exc.Cause.(Reprer); ok {
68 return r.Repr(indent)
69 }
70 return "?(fail " + parse.Quote(exc.Cause.Error()) + ")"
71 }
72
73 func (exc *Exception) Bool() bool {
74 return exc.Cause == nil
75 }
76
77 // PipelineError represents the errors of pipelines, in which multiple commands
78 // may error.
79 type PipelineError struct {
80 Errors []*Exception
81 }
82
83 func (pe PipelineError) Repr(indent int) string {
84 // TODO Make a more generalized ListReprBuilder and use it here.
85 b := new(bytes.Buffer)
86 b.WriteString("?(multi-error")
87 elemIndent := indent + len("?(multi-error ")
88 for _, e := range pe.Errors {
89 if indent > 0 {
90 b.WriteString("\n" + strings.Repeat(" ", elemIndent))
91 } else {
92 b.WriteString(" ")
93 }
94 b.WriteString(e.Repr(elemIndent))
95 }
96 b.WriteString(")")
97 return b.String()
98 }
99
100 func (pe PipelineError) Error() string {
101 b := new(bytes.Buffer)
102 b.WriteString("(")
103 for i, e := range pe.Errors {
104 if i > 0 {
105 b.WriteString(" | ")
106 }
107 if e == nil || e.Cause == nil {
108 b.WriteString("<nil>")
109 } else {
110 b.WriteString(e.Error())
111 }
112 }
113 b.WriteString(")")
114 return b.String()
115 }
116
117 // ComposeExceptionsFromPipeline takes a slice of Exception pointers and
118 // composes a suitable error. If all elements of the slice are either nil or OK,
119 // a nil is returned. If there is exactly non-nil non-OK Exception, it is
120 // returned. Otherwise, a PipelineError built from the slice is returned, with
121 // nil items turned into OK's for easier access from elvishscript.
122 func ComposeExceptionsFromPipeline(excs []*Exception) error {
123 newexcs := make([]*Exception, len(excs))
124 notOK, lastNotOK := 0, 0
125 for i, e := range excs {
126 if e == nil {
127 newexcs[i] = OK
128 } else {
129 newexcs[i] = e
130 if e.Cause != nil {
131 notOK++
132 lastNotOK = i
133 }
134 }
135 }
136 switch notOK {
137 case 0:
138 return nil
139 case 1:
140 return newexcs[lastNotOK]
141 default:
142 return PipelineError{newexcs}
143 }
144 }
145
146 // Flow is a special type of error used for control flows.
147 type Flow uint
148
149 // Control flows.
150 const (
151 Return Flow = iota
152 Break
153 Continue
154 )
155
156 var flowNames = [...]string{
157 "return", "break", "continue",
158 }
159
160 func (f Flow) Repr(int) string {
161 return "?(" + f.Error() + ")"
162 }
163
164 func (f Flow) Error() string {
165 if f >= Flow(len(flowNames)) {
166 return fmt.Sprintf("!(BAD FLOW: %v)", f)
167 }
168 return flowNames[f]
169 }
170
171 func (f Flow) Pprint(string) string {
172 return "\033[33;1m" + f.Error() + "\033[m"
173 }
174
175 // ExternalCmdExit contains the exit status of external commands. If the
176 // command was stopped rather than terminated, the Pid field contains the pid
177 // of the process.
178 type ExternalCmdExit struct {
179 syscall.WaitStatus
180 CmdName string
181 Pid int
182 }
183
184 func NewExternalCmdExit(name string, ws syscall.WaitStatus, pid int) error {
185 if ws.Exited() && ws.ExitStatus() == 0 {
186 return nil
187 }
188 if !ws.Stopped() {
189 pid = 0
190 }
191 return ExternalCmdExit{ws, name, pid}
192 }
193
194 func FakeExternalCmdExit(name string, exit int, sig syscall.Signal) ExternalCmdExit {
195 return ExternalCmdExit{syscall.WaitStatus(exit<<8 + int(sig)), name, 0}
196 }
197
198 func (exit ExternalCmdExit) Error() string {
199 ws := exit.WaitStatus
200 quotedName := parse.Quote(exit.CmdName)
201 switch {
202 case ws.Exited():
203 return quotedName + " exited with " + strconv.Itoa(ws.ExitStatus())
204 case ws.Signaled():
205 msg := quotedName + " killed by signal " + ws.Signal().String()
206 if ws.CoreDump() {
207 msg += " (core dumped)"
208 }
209 return msg
210 case ws.Stopped():
211 msg := quotedName + " stopped by signal " + fmt.Sprintf("%s (pid=%d)", ws.StopSignal(), exit.Pid)
212 trap := ws.TrapCause()
213 if trap != -1 {
214 msg += fmt.Sprintf(" (trapped %v)", trap)
215 }
216 return msg
217 default:
218 return fmt.Sprint(quotedName, " has unknown WaitStatus ", ws)
219 }
220 }
0 package eval
1
2 import (
3 "errors"
4 "fmt"
5 "os"
6 "syscall"
7
8 "github.com/elves/elvish/parse"
9 "github.com/elves/elvish/util"
10 )
11
12 // FdNil is a special impossible fd value used for "close fd" in
13 // syscall.ProcAttr.Files.
14 const fdNil uintptr = ^uintptr(0)
15
16 var (
17 ErrExternalCmdOpts = errors.New("external commands don't accept elvish options")
18 ErrCdNoArg = errors.New("implicit cd accepts no arguments")
19 )
20
21 // ExternalCmd is an external command.
22 type ExternalCmd struct {
23 Name string
24 }
25
26 func (ExternalCmd) Kind() string {
27 return "fn"
28 }
29
30 func (e ExternalCmd) Repr(int) string {
31 return "<external " + parse.Quote(e.Name) + ">"
32 }
33
34 // Call calls an external command.
35 func (e ExternalCmd) Call(ec *EvalCtx, argVals []Value, opts map[string]Value) {
36 if len(opts) > 0 {
37 throw(ErrExternalCmdOpts)
38 }
39 if util.DontSearch(e.Name) {
40 stat, err := os.Stat(e.Name)
41 if err == nil && stat.IsDir() {
42 // implicit cd
43 if len(argVals) > 0 {
44 throw(ErrCdNoArg)
45 }
46 cdInner(e.Name, ec)
47 return
48 }
49 }
50
51 files := make([]uintptr, len(ec.ports))
52 for i, port := range ec.ports {
53 if port == nil || port.File == nil {
54 files[i] = fdNil
55 } else {
56 files[i] = port.File.Fd()
57 }
58 }
59
60 args := make([]string, len(argVals)+1)
61 for i, a := range argVals {
62 // NOTE Maybe we should enfore string arguments instead of coercing all
63 // args into string
64 args[i+1] = ToString(a)
65 }
66
67 sys := syscall.SysProcAttr{Setpgid: ec.background}
68 attr := syscall.ProcAttr{Env: os.Environ(), Files: files[:], Sys: &sys}
69
70 path, err := ec.Search(e.Name)
71 if err != nil {
72 throw(err)
73 }
74
75 args[0] = path
76 pid, err := syscall.ForkExec(path, args, &attr)
77 if err != nil {
78 throw(errors.New("forkExec: " + err.Error()))
79 }
80
81 var ws syscall.WaitStatus
82 _, err = syscall.Wait4(pid, &ws, syscall.WUNTRACED, nil)
83
84 if err != nil {
85 throw(fmt.Errorf("wait: %s", err.Error()))
86 } else {
87 maybeThrow(NewExternalCmdExit(e.Name, ws, pid))
88 }
89 }
0 package eval
1
2 import (
3 "fmt"
4 "os"
5
6 "github.com/elves/elvish/parse"
7 )
8
9 type File struct {
10 inner *os.File
11 }
12
13 var _ Value = File{}
14
15 func (File) Kind() string {
16 return "file"
17 }
18
19 func (f File) Repr(int) string {
20 return fmt.Sprintf("<file{%s %p}>", parse.Quote(f.inner.Name()), f.inner)
21 }
0 #!/usr/bin/env elvish
1
2 out = ./embedded_modules.go
3
4 {
5 echo "package eval"
6 echo "var embeddedModules = map[string]string{"
7
8 for f [*.elv] {
9 print '"'$f[:-4]'": `'
10 cat $f | sed 's/`/`+"`"+`/g'
11 echo '`,'
12 }
13
14 echo "}"
15 } > $out
16
17 gofmt -w $out
0 package eval
1
2 import (
3 "errors"
4 "fmt"
5 "strings"
6 "unicode"
7
8 "github.com/elves/elvish/glob"
9 "github.com/elves/elvish/parse"
10 )
11
12 // GlobPattern is en ephemeral Value generated when evaluating tilde and
13 // wildcards.
14 type GlobPattern struct {
15 glob.Pattern
16 Flags GlobFlag
17 Buts []string
18 }
19
20 type GlobFlag uint
21
22 const (
23 NoMatchOK GlobFlag = 1 << iota
24 )
25
26 func (f GlobFlag) Has(g GlobFlag) bool {
27 return (f & g) == g
28 }
29
30 var (
31 _ Value = GlobPattern{}
32 _ Indexer = GlobPattern{}
33 )
34
35 var (
36 ErrMustFollowWildcard = errors.New("must follow wildcard")
37 ErrModifierMustBeString = errors.New("modifier must be string")
38 ErrWildcardNoMatch = errors.New("wildcard has no match")
39 )
40
41 var runeMatchers = map[string]func(rune) bool{
42 "control": unicode.IsControl,
43 "digit": unicode.IsDigit,
44 "graphic": unicode.IsGraphic,
45 "letter": unicode.IsLetter,
46 "lower": unicode.IsDigit,
47 "mark": unicode.IsMark,
48 "number": unicode.IsNumber,
49 "print": unicode.IsPrint,
50 "punct": unicode.IsPunct,
51 "space": unicode.IsSpace,
52 "symbol": unicode.IsSymbol,
53 "title": unicode.IsTitle,
54 "upper": unicode.IsUpper,
55 }
56
57 func (GlobPattern) Kind() string {
58 return "glob-pattern"
59 }
60
61 func (gp GlobPattern) Repr(int) string {
62 return fmt.Sprintf("<GlobPattern%v>", gp)
63 }
64
65 func (gp GlobPattern) Index(modifiers []Value) []Value {
66 for _, value := range modifiers {
67 modifierv, ok := value.(String)
68 if !ok {
69 throw(ErrModifierMustBeString)
70 }
71 modifier := string(modifierv)
72 switch {
73 case modifier == "nomatch-ok":
74 gp.Flags |= NoMatchOK
75 case strings.HasPrefix(modifier, "but:"):
76 gp.Buts = append(gp.Buts, modifier[len("but:"):])
77 case modifier == "match-hidden":
78 lastSeg := gp.mustGetLastWildSeg()
79 gp.Segments[len(gp.Segments)-1] = glob.Wild{
80 lastSeg.Type, true, lastSeg.Matchers,
81 }
82 default:
83 if matcher, ok := runeMatchers[modifier]; ok {
84 gp.addMatcher(matcher)
85 } else if strings.HasPrefix(modifier, "set:") {
86 set := modifier[len("set:"):]
87 gp.addMatcher(func(r rune) bool {
88 return strings.ContainsRune(set, r)
89 })
90 } else if strings.HasPrefix(modifier, "range:") {
91 rangeExpr := modifier[len("range:"):]
92 badRangeExpr := fmt.Errorf("bad range modifier: %s", parse.Quote(rangeExpr))
93 runes := []rune(rangeExpr)
94 if len(runes) != 3 {
95 throw(badRangeExpr)
96 }
97 from, sep, to := runes[0], runes[1], runes[2]
98 switch sep {
99 case '-':
100 gp.addMatcher(func(r rune) bool {
101 return from <= r && r <= to
102 })
103 case '~':
104 gp.addMatcher(func(r rune) bool {
105 return from <= r && r < to
106 })
107 default:
108 throw(badRangeExpr)
109 }
110 } else {
111 throw(fmt.Errorf("unknown modifier %s", modifierv.Repr(NoPretty)))
112 }
113 }
114 }
115 return []Value{gp}
116 }
117
118 func (gp *GlobPattern) mustGetLastWildSeg() glob.Wild {
119 if len(gp.Segments) == 0 {
120 throw(ErrBadGlobPattern)
121 }
122 if !glob.IsWild(gp.Segments[len(gp.Segments)-1]) {
123 throw(ErrMustFollowWildcard)
124 }
125 return gp.Segments[len(gp.Segments)-1].(glob.Wild)
126 }
127
128 func (gp *GlobPattern) addMatcher(matcher func(rune) bool) {
129 lastSeg := gp.mustGetLastWildSeg()
130 gp.Segments[len(gp.Segments)-1] = glob.Wild{
131 lastSeg.Type, lastSeg.MatchHidden,
132 append(lastSeg.Matchers, matcher),
133 }
134 }
135
136 func (gp *GlobPattern) append(segs ...glob.Segment) {
137 gp.Segments = append(gp.Segments, segs...)
138 }
139
140 func wildcardToSegment(s string) (glob.Segment, error) {
141 switch s {
142 case "*":
143 return glob.Wild{glob.Star, false, nil}, nil
144 case "**":
145 return glob.Wild{glob.StarStar, false, nil}, nil
146 case "?":
147 return glob.Wild{glob.Question, false, nil}, nil
148 default:
149 return nil, fmt.Errorf("bad wildcard: %q", s)
150 }
151 }
152
153 func stringToSegments(s string) []glob.Segment {
154 segs := []glob.Segment{}
155 for i := 0; i < len(s); {
156 j := i
157 for ; j < len(s) && s[j] != '/'; j++ {
158 }
159 if j > i {
160 segs = append(segs, glob.Literal{s[i:j]})
161 }
162 if j < len(s) {
163 for ; j < len(s) && s[j] == '/'; j++ {
164 }
165 segs = append(segs, glob.Slash{})
166 i = j
167 } else {
168 break
169 }
170 }
171 return segs
172 }
173
174 func doGlob(gp GlobPattern, abort <-chan struct{}) []Value {
175 but := make(map[string]struct{})
176 for _, s := range gp.Buts {
177 but[s] = struct{}{}
178 }
179
180 vs := make([]Value, 0)
181 if !gp.Glob(func(name string) bool {
182 select {
183 case <-abort:
184 logger.Println("glob aborted")
185 return false
186 default:
187 }
188 if _, b := but[name]; !b {
189 vs = append(vs, String(name))
190 }
191 return true
192 }) {
193 throw(ErrInterrupted)
194 }
195 if len(vs) == 0 && !gp.Flags.Has(NoMatchOK) {
196 throw(ErrWildcardNoMatch)
197 }
198 return vs
199 }
0 package eval
1
2 import (
3 "bytes"
4 "encoding/json"
5 "errors"
6 "strconv"
7 "strings"
8
9 "github.com/xiaq/persistent/vector"
10 )
11
12 // Error definitions.
13 var (
14 // ErrNeedIntIndex = errors.New("need integer index")
15 ErrBadIndex = errors.New("bad index")
16 ErrIndexOutOfRange = errors.New("index out of range")
17 )
18
19 type ListLike interface {
20 Lener
21 Iterable
22 IndexOneer
23 }
24
25 // List is a list of Value's.
26 type List struct {
27 inner vector.Vector
28 }
29
30 var _ ListLike = List{}
31
32 // NewList creates a new List.
33 func NewList(vs ...Value) List {
34 vec := vector.Empty
35 for _, v := range vs {
36 vec = vec.Cons(v)
37 }
38 return List{vec}
39 }
40
41 func NewListFromVector(vec vector.Vector) List {
42 return List{vec}
43 }
44
45 func (List) Kind() string {
46 return "list"
47 }
48
49 func (l List) Repr(indent int) string {
50 var b ListReprBuilder
51 b.Indent = indent
52 for it := l.inner.Iterator(); it.HasElem(); it.Next() {
53 v := it.Elem().(Value)
54 b.WriteElem(v.Repr(indent + 1))
55 }
56 return b.String()
57 }
58
59 func (l List) MarshalJSON() ([]byte, error) {
60 var buf bytes.Buffer
61 encoder := json.NewEncoder(&buf)
62 buf.WriteByte('[')
63 first := true
64 for it := l.inner.Iterator(); it.HasElem(); it.Next() {
65 if first {
66 first = false
67 } else {
68 buf.WriteByte(',')
69 }
70 err := encoder.Encode(it.Elem())
71 if err != nil {
72 return nil, err
73 }
74 }
75 buf.WriteByte(']')
76 return buf.Bytes(), nil
77 }
78
79 func (l List) Len() int {
80 return l.inner.Len()
81 }
82
83 func (l List) Iterate(f func(Value) bool) {
84 for it := l.inner.Iterator(); it.HasElem(); it.Next() {
85 v := it.Elem().(Value)
86 if !f(v) {
87 break
88 }
89 }
90 }
91
92 func (l List) IndexOne(idx Value) Value {
93 slice, i, j := ParseAndFixListIndex(ToString(idx), l.Len())
94 if slice {
95 return List{l.inner.SubVector(i, j)}
96 }
97 return l.inner.Nth(i).(Value)
98 }
99
100 // ParseAndFixListIndex parses a list index and returns whether the index is a
101 // slice and "real" (-1 becomes n-1) indicies. It throws errors when the index
102 // is invalid or out of range.
103 func ParseAndFixListIndex(s string, n int) (bool, int, int) {
104 slice, i, j := parseListIndex(s, n)
105 if i < 0 {
106 i += n
107 }
108 if j < 0 {
109 j += n
110 }
111 if i < 0 || i >= n || (slice && (j < 0 || j > n || i > j)) {
112 throw(ErrIndexOutOfRange)
113 }
114 return slice, i, j
115 }
116
117 // ListIndex = Number |
118 // Number ':' Number
119 func parseListIndex(s string, n int) (slice bool, i int, j int) {
120 atoi := func(a string) int {
121 i, err := strconv.Atoi(a)
122 if err != nil {
123 if err.(*strconv.NumError).Err == strconv.ErrRange {
124 throw(ErrIndexOutOfRange)
125 } else {
126 throw(ErrBadIndex)
127 }
128 }
129 return i
130 }
131
132 colon := strings.IndexRune(s, ':')
133 if colon == -1 {
134 // A single number
135 return false, atoi(s), 0
136 }
137 if s[:colon] == "" {
138 i = 0
139 } else {
140 i = atoi(s[:colon])
141 }
142 if s[colon+1:] == "" {
143 j = n
144 } else {
145 j = atoi(s[colon+1:])
146 }
147 // Two numbers
148 return true, i, j
149 }
150
151 // ListReprBuilder helps to build Repr of list-like Values.
152 type ListReprBuilder struct {
153 Indent int
154 buf bytes.Buffer
155 }
156
157 func (b *ListReprBuilder) WriteElem(v string) {
158 if b.buf.Len() == 0 {
159 b.buf.WriteByte('[')
160 }
161 if b.Indent >= 0 {
162 // Pretty printing.
163 //
164 // Add a newline and indent+1 spaces, so that the
165 // starting & lines up with the first pair.
166 b.buf.WriteString("\n" + strings.Repeat(" ", b.Indent+1))
167 } else if b.buf.Len() > 1 {
168 b.buf.WriteByte(' ')
169 }
170 b.buf.WriteString(v)
171 }
172
173 func (b *ListReprBuilder) String() string {
174 if b.buf.Len() == 0 {
175 return "[]"
176 }
177 if b.Indent >= 0 {
178 b.buf.WriteString("\n" + strings.Repeat(" ", b.Indent))
179 }
180 b.buf.WriteByte(']')
181 return b.buf.String()
182 }
0 package eval
1
2 import (
3 "testing"
4
5 "github.com/elves/elvish/util"
6 )
7
8 var parseAndFixListIndexTests = []struct {
9 name string
10 // input
11 expr string
12 len int
13 // output
14 shouldPanic, isSlice bool
15 begin, end int
16 }{
17 {
18 name: "stringIndex",
19 expr: "a", len: 0,
20 shouldPanic: true,
21 },
22 {
23 name: "floatIndex",
24 expr: "1.0", len: 0,
25 shouldPanic: true,
26 },
27 {
28 name: "emptyZeroIndex",
29 expr: "0", len: 0,
30 shouldPanic: true,
31 },
32 {
33 name: "emptyPosIndex",
34 expr: "1", len: 0,
35 shouldPanic: true,
36 },
37 {
38 name: "emptyNegIndex",
39 expr: "-1", len: 0,
40 shouldPanic: true,
41 },
42 {
43 name: "emptySliceAbbrevBoth",
44 expr: ":", len: 0,
45 shouldPanic: true,
46 },
47 {
48 name: "i<-n",
49 expr: "-2", len: 1,
50 shouldPanic: true,
51 },
52 {
53 name: "i=-n",
54 expr: "-1", len: 1,
55 begin: 0, end: 0,
56 },
57 {
58 name: "-n<i<0",
59 expr: "-1", len: 2,
60 begin: 1, end: 0,
61 },
62 {
63 name: "i=0",
64 expr: "0", len: 2,
65 begin: 0, end: 0,
66 },
67 {
68 name: "0<i<n",
69 expr: "1", len: 2,
70 begin: 1, end: 0,
71 },
72 {
73 name: "i=n",
74 expr: "1", len: 1,
75 shouldPanic: true,
76 },
77 {
78 name: "i>n",
79 expr: "2", len: 1,
80 shouldPanic: true,
81 },
82 {
83 name: "sliceAbbrevBoth",
84 expr: ":", len: 1,
85 isSlice: true, begin: 0, end: 1,
86 },
87 {
88 name: "sliceAbbrevBegin",
89 expr: ":1", len: 1,
90 isSlice: true, begin: 0, end: 1,
91 },
92 {
93 name: "sliceAbbrevEnd",
94 expr: "0:", len: 1,
95 isSlice: true, begin: 0, end: 1,
96 },
97 {
98 name: "sliceNegEnd",
99 expr: "0:-1", len: 1,
100 isSlice: true, begin: 0, end: 0,
101 },
102 {
103 name: "sliceBeginEqualEnd",
104 expr: "1:1", len: 2,
105 isSlice: true, begin: 1, end: 1,
106 },
107 {
108 name: "sliceBeginAboveEnd",
109 expr: "1:0", len: 2,
110 shouldPanic: true,
111 },
112 }
113
114 func TestParseAndFixListIndex(t *testing.T) {
115 checkEqual := func(name, value string, want, got interface{}) {
116 if want != got {
117 t.Errorf("%s value: [%s] want: [%v] got: [%v]",
118 name, value, want, got)
119 }
120 }
121
122 for _, item := range parseAndFixListIndexTests {
123 var (
124 isSlice bool
125 begin, end int
126 )
127
128 if err := util.PCall(func() {
129 isSlice, begin, end = ParseAndFixListIndex(item.expr, item.len)
130 }); err != nil {
131 checkEqual(item.name, "shouldPanic", item.shouldPanic, err != nil)
132 continue
133 }
134 checkEqual(item.name, "isSlice", item.isSlice, isSlice)
135 checkEqual(item.name, "begin", item.begin, begin)
136 checkEqual(item.name, "end", item.end, end)
137 }
138
139 }
0 package eval
1
2 import (
3 "encoding/json"
4 "errors"
5 "strings"
6 )
7
8 // Map is a map from string to Value.
9 type Map struct {
10 inner *map[Value]Value
11 }
12
13 type HasKeyer interface {
14 HasKey(k Value) bool
15 }
16
17 type MapLike interface {
18 Lener
19 IndexOneer
20 HasKeyer
21 IterateKeyer
22 }
23
24 var _ MapLike = Map{}
25
26 // NewMap creates a new Map.
27 func NewMap(inner map[Value]Value) Map {
28 return Map{&inner}
29 }
30
31 func (Map) Kind() string {
32 return "map"
33 }
34
35 func (m Map) MarshalJSON() ([]byte, error) {
36 // XXX Not the most efficient way.
37 mm := map[string]Value{}
38 for k, v := range *m.inner {
39 mm[ToString(k)] = v
40 }
41 return json.Marshal(mm)
42 }
43
44 func (m Map) Repr(indent int) string {
45 var builder MapReprBuilder
46 builder.Indent = indent
47 for k, v := range *m.inner {
48 builder.WritePair(k.Repr(indent+1), indent+2, v.Repr(indent+2))
49 }
50 return builder.String()
51 }
52
53 func (m Map) Len() int {
54 return len(*m.inner)
55 }
56
57 func (m Map) IndexOne(idx Value) Value {
58 v, ok := (*m.inner)[idx]
59 if !ok {
60 throw(errors.New("no such key: " + idx.Repr(NoPretty)))
61 }
62 return v
63 }
64
65 func (m Map) IterateKey(f func(Value) bool) {
66 for k := range *m.inner {
67 cont := f(k)
68 if !cont {
69 break
70 }
71 }
72 }
73
74 func (m Map) HasKey(k Value) bool {
75 _, ok := (*m.inner)[k]
76 return ok
77 }
78
79 func (m Map) IndexSet(idx Value, v Value) {
80 (*m.inner)[idx] = v
81 }
82
83 // MapReprBuilder helps building the Repr of a Map. It is also useful for
84 // implementing other Map-like values. The zero value of a MapReprBuilder is
85 // ready to use.
86 type MapReprBuilder struct {
87 ListReprBuilder
88 }
89
90 func (b *MapReprBuilder) WritePair(k string, indent int, v string) {
91 if indent > 0 {
92 b.WriteElem("&" + k + "=\n" + strings.Repeat(" ", indent) + v)
93 } else {
94 b.WriteElem("&" + k + "=" + v)
95 }
96 }
97
98 func (b *MapReprBuilder) String() string {
99 if b.buf.Len() == 0 {
100 return "[&]"
101 }
102 return b.ListReprBuilder.String()
103 }
0 package eval
1
2 import (
3 "errors"
4
5 "github.com/elves/elvish/parse"
6 )
7
8 // MapStringString implements MapLike for map[string]string.
9 type MapStringString map[string]string
10
11 var (
12 _ MapLike = MapStringString(nil)
13 _ IndexSetter = MapStringString(nil)
14 )
15
16 var (
17 ErrValueMustBeString = errors.New("index must be string")
18 )
19
20 func (MapStringString) Kind() string {
21 return "map"
22 }
23
24 func (m MapStringString) Repr(indent int) string {
25 var builder MapReprBuilder
26 builder.Indent = indent
27 for k, v := range m {
28 builder.WritePair(parse.Quote(k), indent+2, parse.Quote(v))
29 }
30 return builder.String()
31 }
32
33 func (m MapStringString) Len() int {
34 return len(m)
35 }
36
37 func (m MapStringString) IndexOne(idx Value) Value {
38 i, ok := idx.(String)
39 if !ok {
40 throw(ErrIndexMustBeString)
41 }
42 v, ok := m[string(i)]
43 if !ok {
44 throw(errors.New("no such key: " + i.Repr(NoPretty)))
45 }
46 return String(v)
47 }
48
49 func (m MapStringString) IterateKey(f func(Value) bool) {
50 for k := range m {
51 cont := f(String(k))
52 if !cont {
53 break
54 }
55 }
56 }
57
58 func (m MapStringString) HasKey(idx Value) bool {
59 if i, ok := idx.(String); ok {
60 if _, ok := m[string(i)]; ok {
61 return true
62 }
63 }
64 return false
65 }
66
67 func (m MapStringString) IndexSet(idx Value, val Value) {
68 i, ok := idx.(String)
69 if !ok {
70 throw(ErrIndexMustBeString)
71 }
72 v, ok := val.(String)
73 if !ok {
74 throw(ErrValueMustBeString)
75 }
76 m[string(i)] = string(v)
77 }
0 package eval
1
2 import (
3 "strconv"
4
5 "github.com/elves/elvish/parse"
6 )
7
8 type muster struct {
9 ec *EvalCtx
10 what string
11 begin, end int
12 vs []Value
13 }
14
15 func (m *muster) error(want, gotfmt string, gotargs ...interface{}) {
16 m.ec.errorpf(m.begin, m.end, "%s must be %s; got "+gotfmt,
17 append([]interface{}{m.what, want}, gotargs...)...)
18 }
19
20 func (ec *EvalCtx) must(vs []Value, what string, begin, end int) *muster {
21 return &muster{ec, what, begin, end, vs}
22 }
23
24 func (m *muster) mustLen(l int) {
25 if len(m.vs) != l {
26 if l == 1 {
27 m.error("1 value", "%d", len(m.vs))
28 } else {
29 m.error(strconv.Itoa(l)+" values", "%d", len(m.vs))
30 }
31 }
32 }
33
34 func (m *muster) mustOne() Value {
35 m.mustLen(1)
36 return m.vs[0]
37 }
38
39 func (m *muster) zerothMustStr() String {
40 v := m.vs[0]
41 s, ok := v.(String)
42 if !ok {
43 m.error("a string", "%s (type %s)", v.Repr(NoPretty), v.Kind())
44 }
45 return s
46 }
47
48 func (m *muster) mustOneStr() String {
49 m.mustLen(1)
50 return m.zerothMustStr()
51 }
52
53 func (m *muster) zerothMustInt() int {
54 s := m.zerothMustStr()
55 i, err := strconv.Atoi(string(s))
56 if err != nil {
57 m.error("an integer", "%s", s)
58 }
59 return i
60 }
61
62 func (m *muster) mustOneInt() int {
63 m.mustLen(1)
64 return m.zerothMustInt()
65 }
66
67 func (m *muster) zerothMustNonNegativeInt() int {
68 i := m.zerothMustInt()
69 if i < 0 {
70 m.error("non-negative", "%d", i)
71 }
72 return i
73 }
74
75 func (m *muster) mustOneNonNegativeInt() int {
76 m.mustLen(1)
77 return m.zerothMustNonNegativeInt()
78 }
79
80 func onePrimary(cn *parse.Compound) *parse.Primary {
81 if len(cn.Indexings) == 1 && len(cn.Indexings[0].Indicies) == 0 {
82 return cn.Indexings[0].Head
83 }
84 return nil
85 }
86
87 func oneString(cn *parse.Compound) (string, bool) {
88 pn := onePrimary(cn)
89 if pn != nil {
90 switch pn.Type {
91 case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted:
92 return pn.Value, true
93 }
94 }
95 return "", false
96 }
97
98 func mustPrimary(cp *compiler, cn *parse.Compound, msg string) *parse.Primary {
99 p := onePrimary(cn)
100 if p == nil {
101 cp.errorpf(cn.Begin(), cn.End(), msg)
102 }
103 return p
104 }
105
106 // mustString musts that a Compound contains exactly one Primary of type
107 // Variable.
108 func mustString(cp *compiler, cn *parse.Compound, msg string) string {
109 s, ok := oneString(cn)
110 if !ok {
111 cp.errorpf(cn.Begin(), cn.End(), msg)
112 }
113 return s
114 }
0 package eval
1
2 import (
3 "fmt"
4 "os"
5 )
6
7 type Pipe struct {
8 r, w *os.File
9 }
10
11 var _ Value = Pipe{}
12
13 func (Pipe) Kind() string {
14 return "pipe"
15 }
16
17 func (p Pipe) Repr(int) string {
18 return fmt.Sprintf("<pipe{%v %v}>", p.r.Fd(), p.w.Fd())
19 }
0 package eval
1
2 import "os"
3
4 // Port conveys data stream. It always consists of a byte band and a channel band.
5 type Port struct {
6 File *os.File
7 Chan chan Value
8 CloseFile bool
9 CloseChan bool
10 }
11
12 // Fork returns a copy of a Port with the Close* flags unset.
13 func (p *Port) Fork() *Port {
14 return &Port{p.File, p.Chan, false, false}
15 }
16
17 // Close closes a Port.
18 func (p *Port) Close() {
19 if p == nil {
20 return
21 }
22 if p.CloseFile {
23 p.File.Close()
24 }
25 if p.CloseChan {
26 // Logger.Printf("closing channel %v", p.Chan)
27 close(p.Chan)
28 }
29 }
30
31 // ClosePorts closes a list of Ports.
32 func ClosePorts(ports []*Port) {
33 for _, port := range ports {
34 // Logger.Printf("closing port %d", i)
35 port.Close()
36 }
37 }
38
39 var (
40 // ClosedChan is a closed channel, suitable for use as placeholder channel input.
41 ClosedChan = make(chan Value)
42 // BlackholeChan is channel writes onto which disappear, suitable for use as
43 // placeholder channel output.
44 BlackholeChan = make(chan Value)
45 // DevNull is /dev/null.
46 DevNull *os.File
47 // DevNullClosedInput is a port made up from DevNull and ClosedChan,
48 // suitable as placeholder input port.
49 DevNullClosedChan *Port
50 )
51
52 func init() {
53 close(ClosedChan)
54 go func() {
55 for range BlackholeChan {
56 }
57 }()
58
59 var err error
60 DevNull, err = os.Open("/dev/null")
61 if err != nil {
62 os.Stderr.WriteString("cannot open /dev/null, shell might not function normally")
63 }
64 DevNullClosedChan = &Port{File: DevNull, Chan: ClosedChan}
65 }
0 package eval
1
2 import (
3 "os"
4
5 "github.com/elves/elvish/daemon/api"
6 )
7
8 // PwdVariable is a variable whose value always reflects the current working
9 // directory. Setting it changes the current working directory.
10 type PwdVariable struct {
11 daemon *api.Client
12 }
13
14 var _ Variable = PwdVariable{}
15
16 func (PwdVariable) Get() Value {
17 pwd, err := os.Getwd()
18 maybeThrow(err)
19 return String(pwd)
20 }
21
22 func (pwd PwdVariable) Set(v Value) {
23 path, ok := v.(String)
24 if !ok {
25 throw(ErrPathMustBeString)
26 }
27 err := Chdir(string(path), pwd.daemon)
28 maybeThrow(err)
29 }
0 package re
1
2 func makeMatch(low, high int, submatches []int) {
3 }
0 package re
1
2 import (
3 "fmt"
4 "regexp"
5 "strconv"
6
7 "github.com/elves/elvish/eval"
8 "github.com/elves/elvish/util"
9 "github.com/xiaq/persistent/vector"
10 )
11
12 func Namespace() eval.Namespace {
13 ns := eval.Namespace{}
14 eval.AddBuiltinFns(ns, fns...)
15 return ns
16 }
17
18 var fns = []*eval.BuiltinFn{
19 {"quote", eval.WrapStringToString(regexp.QuoteMeta)},
20 {"match", match},
21 {"find", find},
22 {"replace", replace},
23 {"split", split},
24 }
25
26 func match(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
27 out := ec.OutputChan()
28 var (
29 argPattern eval.String
30 argSource eval.String
31 optPOSIX eval.Bool
32 )
33 eval.ScanArgs(args, &argPattern, &argSource)
34 eval.ScanOpts(opts, eval.Opt{"posix", &optPOSIX, eval.Bool(false)})
35
36 pattern := makePattern(argPattern, optPOSIX, eval.Bool(false))
37 matched := pattern.MatchString(string(argSource))
38 out <- eval.Bool(matched)
39 }
40
41 var (
42 matchFields = []string{"text", "start", "end", "groups"}
43 submatchFields = []string{"text", "start", "end"}
44 )
45
46 func find(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
47 out := ec.OutputChan()
48 var (
49 argPattern eval.String
50 argSource eval.String
51 optPOSIX eval.Bool
52 optLongest eval.Bool
53 optMax int
54 )
55 eval.ScanArgs(args, &argPattern, &argSource)
56 eval.ScanOpts(opts,
57 eval.Opt{"posix", &optPOSIX, eval.Bool(false)},
58 eval.Opt{"longest", &optLongest, eval.Bool(false)},
59 eval.Opt{"max", &optMax, eval.String("-1")})
60
61 pattern := makePattern(argPattern, optPOSIX, optLongest)
62
63 matches := pattern.FindAllSubmatchIndex([]byte(argSource), optMax)
64 for _, match := range matches {
65 start, end := match[0], match[1]
66 groups := vector.Empty
67 for i := 0; i < len(match); i += 2 {
68 start, end := match[i], match[i+1]
69 groups = groups.Cons(&eval.Struct{submatchFields, []eval.Variable{
70 eval.NewRoVariable(argSource[start:end]),
71 eval.NewRoVariable(eval.String(strconv.Itoa(start))),
72 eval.NewRoVariable(eval.String(strconv.Itoa(end))),
73 }})
74 }
75 out <- &eval.Struct{matchFields, []eval.Variable{
76 eval.NewRoVariable(argSource[start:end]),
77 eval.NewRoVariable(eval.String(strconv.Itoa(start))),
78 eval.NewRoVariable(eval.String(strconv.Itoa(end))),
79 eval.NewRoVariable(eval.NewListFromVector(groups)),
80 }}
81 }
82 }
83
84 func replace(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
85 out := ec.OutputChan()
86 var (
87 argPattern eval.String
88 argRepl eval.Value
89 argSource eval.String
90 optPOSIX eval.Bool
91 optLongest eval.Bool
92 optLiteral eval.Bool
93 )
94 eval.ScanArgs(args, &argPattern, &argRepl, &argSource)
95 eval.ScanOpts(opts,
96 eval.Opt{"posix", &optPOSIX, eval.Bool(false)},
97 eval.Opt{"longest", &optLongest, eval.Bool(false)},
98 eval.Opt{"literal", &optLiteral, eval.Bool(false)})
99
100 pattern := makePattern(argPattern, optPOSIX, optLongest)
101
102 var result string
103 if optLiteral {
104 repl, ok := argRepl.(eval.String)
105 if !ok {
106 throwf("replacement must be string when literal is set, got %s",
107 argRepl.Kind())
108 }
109 result = pattern.ReplaceAllLiteralString(string(argSource), string(repl))
110 } else {
111 switch repl := argRepl.(type) {
112 case eval.String:
113 result = pattern.ReplaceAllString(string(argSource), string(repl))
114 case eval.CallableValue:
115 replFunc := func(s string) string {
116 values, err := ec.PCaptureOutput(repl,
117 []eval.Value{eval.String(s)}, eval.NoOpts)
118 maybeThrow(err)
119 if len(values) != 1 {
120 throwf("replacement function must output exactly one value, got %d", len(values))
121 }
122 output, ok := values[0].(eval.String)
123 if !ok {
124 throwf("replacement function must output one string, got %s", values[0].Kind())
125 }
126 return string(output)
127 }
128 result = pattern.ReplaceAllStringFunc(string(argSource), replFunc)
129 default:
130 throwf("replacement must be string or function, got %s",
131 argRepl.Kind())
132 }
133 }
134 out <- eval.String(result)
135 }
136
137 func split(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
138 out := ec.OutputChan()
139 var (
140 argPattern eval.String
141 argSource eval.String
142 optPOSIX eval.Bool
143 optLongest eval.Bool
144 optMax int
145 )
146 eval.ScanArgs(args, &argPattern, &argSource)
147 eval.ScanOpts(opts,
148 eval.Opt{"posix", &optPOSIX, eval.Bool(false)},
149 eval.Opt{"longest", &optLongest, eval.Bool(false)},
150 eval.Opt{"max", &optMax, eval.String("-1")})
151
152 pattern := makePattern(argPattern, optPOSIX, optLongest)
153
154 pieces := pattern.Split(string(argSource), optMax)
155 for _, piece := range pieces {
156 out <- eval.String(piece)
157 }
158 }
159
160 func makePattern(argPattern eval.String,
161 optPOSIX, optLongest eval.Bool) *regexp.Regexp {
162
163 var (
164 pattern *regexp.Regexp
165 err error
166 )
167 if optPOSIX {
168 pattern, err = regexp.CompilePOSIX(string(argPattern))
169 } else {
170 pattern, err = regexp.Compile(string(argPattern))
171 }
172 maybeThrow(err)
173 if optLongest {
174 pattern.Longest()
175 }
176 return pattern
177 }
178
179 func throwf(format string, args ...interface{}) {
180 util.Throw(fmt.Errorf(format, args...))
181 }
182
183 func maybeThrow(err error) {
184 if err != nil {
185 util.Throw(err)
186 }
187 }
0 fn bind-mode [m k f]{
1 edit:binding[$m][$k] = $f
2 }
3
4 fn bind [k f]{
5 bind-mode insert $k $f
6 }
7
8 bind Ctrl-A $edit:&move-dot-sol
9 bind Ctrl-B $edit:&move-dot-left
10 bind Ctrl-D {
11 if (> (count $edit:current-command) 0) {
12 edit:kill-rune-right
13 } else {
14 edit:return-eof
15 }
16 }
17 bind Ctrl-E $edit:&move-dot-eol
18 bind Ctrl-F $edit:&move-dot-right
19 bind Ctrl-H $edit:&kill-rune-left
20 bind Ctrl-L { clear > /dev/tty }
21 bind Ctrl-N $edit:&end-of-history
22 # TODO: ^O
23 bind Ctrl-P $edit:history:&start
24 # TODO: ^S ^T ^X family ^Y ^_
25 bind Alt-b $edit:&move-dot-left-word
26 # TODO Alt-c Alt-d
27 bind Alt-f $edit:&move-dot-right-word
28 # TODO Alt-l Alt-r Alt-u
29
30 # Ctrl-N and Ctrl-L occupied by readline binding, bind to Alt- instead.
31 bind Alt-n $edit:nav:&start
32 bind Alt-l $edit:loc:&start
33
34 bind-mode completion Ctrl-B $edit:compl:&left
35 bind-mode completion Ctrl-F $edit:compl:&right
36 bind-mode completion Ctrl-N $edit:compl:&down
37 bind-mode completion Ctrl-P $edit:compl:&up
38 bind-mode completion Alt-f $edit:compl:&trigger-filter
39
40 bind-mode navigation Ctrl-B $edit:nav:&left
41 bind-mode navigation Ctrl-F $edit:nav:&right
42 bind-mode navigation Ctrl-N $edit:nav:&down
43 bind-mode navigation Ctrl-P $edit:nav:&up
44 bind-mode navigation Alt-f $edit:nav:&trigger-filter
45
46 bind-mode history Ctrl-N $edit:history:&down-or-quit
47 bind-mode history Ctrl-P $edit:history:&up
48 bind-mode history Ctrl-G $edit:insert:&start
49
50 # Binding for the listing "super mode".
51 bind-mode listing Ctrl-N $edit:listing:&down
52 bind-mode listing Ctrl-P $edit:listing:&up
53 bind-mode listing Ctrl-V $edit:listing:&page-down
54 bind-mode listing Alt-v $edit:listing:&page-up
55 bind-mode listing Ctrl-G $edit:insert:&start
56
57 bind-mode histlist Alt-g $edit:histlist:&toggle-case-sensitivity
58 bind-mode histlist Alt-d $edit:histlist:&toggle-dedup
0 package eval
1
2 import (
3 "fmt"
4
5 "github.com/elves/elvish/parse"
6 "github.com/elves/elvish/util"
7 )
8
9 // Search tries to resolve an external command and return the full (possibly
10 // relative) path.
11 func (ev *Evaler) Search(exe string) (string, error) {
12 path, err := util.Search(ev.searchPaths(), exe)
13 if err != nil {
14 return "", fmt.Errorf("search %s: %s", parse.Quote(exe), err.Error())
15 }
16 return path, nil
17 }
18
19 // EachExternal calls f for each name that can resolve to an external
20 // command.
21 func (ev *Evaler) EachExternal(f func(string)) {
22 util.EachExecutable(ev.searchPaths(), f)
23 }
0 package eval
1
2 import "github.com/elves/elvish/daemon/api"
3
4 type sharedVariable struct {
5 store *api.Client
6 name string
7 }
8
9 func (sv sharedVariable) Set(val Value) {
10 err := sv.store.SetSharedVar(sv.name, ToString(val))
11 maybeThrow(err)
12 }
13
14 func (sv sharedVariable) Get() Value {
15 value, err := sv.store.SharedVar(sv.name)
16 maybeThrow(err)
17 return String(value)
18 }
0 package eval
1
2 import (
3 "unicode/utf8"
4
5 "github.com/elves/elvish/parse"
6 )
7
8 // String is just a string.
9 type String string
10
11 var (
12 _ Value = String("")
13 _ ListLike = String("")
14 )
15
16 func (String) Kind() string {
17 return "string"
18 }
19
20 func (s String) Repr(int) string {
21 return quote(string(s))
22 }
23
24 func (s String) String() string {
25 return string(s)
26 }
27
28 func (s String) Len() int {
29 return len(string(s))
30 }
31
32 func (s String) IndexOne(idx Value) Value {
33 slice, i, j := ParseAndFixListIndex(ToString(idx), len(s))
34 var r rune
35 if r, _ = utf8.DecodeRuneInString(string(s[i:])); r == utf8.RuneError {
36 throw(ErrBadIndex)
37 }
38 if slice {
39 if r, _ := utf8.DecodeLastRuneInString(string(s[:j])); r == utf8.RuneError {
40 throw(ErrBadIndex)
41 }
42 return String(s[i:j])
43 }
44 return String(r)
45 }
46
47 func (s String) Iterate(f func(v Value) bool) {
48 for _, r := range s {
49 b := f(String(string(r)))
50 if !b {
51 break
52 }
53 }
54 }
55
56 // Call resolves a command name to either a Fn variable or external command and
57 // calls it.
58 func (s String) Call(ec *EvalCtx, args []Value, opts map[string]Value) {
59 resolve(string(s), ec).Call(ec, args, opts)
60 }
61
62 func resolve(s string, ec *EvalCtx) CallableValue {
63 // Try variable
64 explode, ns, name := ParseAndFixVariable(string(s))
65 if !explode {
66 if v := ec.ResolveVar(ns, FnPrefix+name); v != nil {
67 if caller, ok := v.Get().(CallableValue); ok {
68 return caller
69 }
70 }
71 }
72
73 // External command
74 return ExternalCmd{string(s)}
75 }
76
77 // ToString converts a Value to String. When the Value type implements
78 // String(), it is used. Otherwise Repr(NoPretty) is used.
79 func ToString(v Value) string {
80 if s, ok := v.(Stringer); ok {
81 return s.String()
82 }
83 return v.Repr(NoPretty)
84 }
85
86 func quote(s string) string {
87 return parse.Quote(s)
88 }
0 package eval
1
2 import (
3 "errors"
4 "fmt"
5
6 "github.com/elves/elvish/parse"
7 )
8
9 var (
10 ErrIndexMustBeString = errors.New("index must be string")
11 )
12
13 // Struct is like a Map with fixed keys.
14 type Struct struct {
15 FieldNames []string
16 Fields []Variable
17 }
18
19 var (
20 _ Value = (*Struct)(nil)
21 _ MapLike = (*Struct)(nil)
22 )
23
24 func (*Struct) Kind() string {
25 return "map"
26 }
27
28 func (s *Struct) Repr(indent int) string {
29 var builder MapReprBuilder
30 builder.Indent = indent
31 for i, name := range s.FieldNames {
32 builder.WritePair(parse.Quote(name), indent+2, s.Fields[i].Get().Repr(indent+2))
33 }
34 return builder.String()
35 }
36
37 func (s *Struct) Len() int {
38 return len(s.FieldNames)
39 }
40
41 func (s *Struct) IndexOne(idx Value) Value {
42 return s.index(idx).Get()
43 }
44
45 func (s *Struct) IterateKey(f func(Value) bool) {
46 for _, field := range s.FieldNames {
47 cont := f(String(field))
48 if !cont {
49 break
50 }
51 }
52 }
53
54 func (s *Struct) HasKey(k Value) bool {
55 index, ok := k.(String)
56 if !ok {
57 return false
58 }
59 for _, name := range s.FieldNames {
60 if string(index) == name {
61 return true
62 }
63 }
64 return false
65 }
66
67 func (s *Struct) IndexSet(idx Value, v Value) {
68 s.index(idx).Set(v)
69 }
70
71 func (s *Struct) index(idx Value) Variable {
72 index, ok := idx.(String)
73 if !ok {
74 throw(ErrIndexMustBeString)
75 }
76 for i, name := range s.FieldNames {
77 if string(index) == name {
78 return s.Fields[i]
79 }
80 }
81 throw(fmt.Errorf("no such field: %s", index.Repr(NoPretty)))
82 panic("unreachable")
83 }
0 package eval
1
2 import (
3 "errors"
4 "fmt"
5 "os"
6 "strings"
7
8 "github.com/elves/elvish/parse"
9 "github.com/elves/elvish/util"
10 )
11
12 func throw(e error) {
13 util.Throw(e)
14 }
15
16 func throwf(format string, args ...interface{}) {
17 util.Throw(fmt.Errorf(format, args...))
18 }
19
20 func maybeThrow(err error) {
21 if err != nil {
22 util.Throw(err)
23 }
24 }
25
26 func mustGetHome(uname string) string {
27 dir, err := util.GetHome(uname)
28 if err != nil {
29 throw(err)
30 }
31 return dir
32 }
33
34 // ParseAndFixVariable parses a variable name. It "fixes" $@ to $@args.
35 func ParseAndFixVariable(qname string) (explode bool, ns string, name string) {
36 explode, ns, name = ParseVariable(qname)
37 if explode && ns == "" && name == "" {
38 name = "args"
39 }
40 return explode, ns, name
41 }
42
43 func ParseVariable(text string) (explode bool, ns string, name string) {
44 explodePart, qname := ParseVariableSplice(text)
45 nsPart, name := ParseVariableQName(qname)
46 ns = nsPart
47 if len(ns) > 0 {
48 ns = ns[:len(ns)-1]
49 }
50 return explodePart != "", ns, name
51 }
52
53 func ParseVariableSplice(text string) (explode, qname string) {
54 if strings.HasPrefix(text, "@") {
55 return "@", text[1:]
56 }
57 return "", text
58 }
59
60 func ParseVariableQName(qname string) (ns, name string) {
61 i := strings.LastIndexByte(qname, ':')
62 if i == -1 {
63 return "", qname
64 }
65 return qname[:i+1], qname[i+1:]
66 }
67
68 func MakeVariableName(explode bool, ns string, name string) string {
69 prefix := ""
70 if explode {
71 prefix = "@"
72 }
73 if ns != "" {
74 prefix += ns + ":"
75 }
76 return prefix + name
77 }
78
79 func makeFlag(m parse.RedirMode) int {
80 switch m {
81 case parse.Read:
82 return os.O_RDONLY
83 case parse.Write:
84 return os.O_WRONLY | os.O_CREATE | os.O_TRUNC
85 case parse.ReadWrite:
86 return os.O_RDWR | os.O_CREATE
87 case parse.Append:
88 return os.O_WRONLY | os.O_CREATE | os.O_APPEND
89 default:
90 // XXX should report parser bug
91 panic("bad RedirMode; parser bug")
92 }
93 }
94
95 var (
96 ErrNoArgAccepted = errors.New("no argument accepted")
97 ErrNoOptAccepted = errors.New("no option accepted")
98 )
99
100 func TakeNoArg(args []Value) {
101 if len(args) > 0 {
102 throw(ErrNoArgAccepted)
103 }
104 }
105
106 func TakeNoOpt(opts map[string]Value) {
107 if len(opts) > 0 {
108 throw(ErrNoOptAccepted)
109 }
110 }
0 package eval
1
2 import "errors"
3
4 var (
5 errShouldBeList = errors.New("should be list")
6 errShouldBeMap = errors.New("should be map")
7 errShouldBeFn = errors.New("should be function")
8 errShouldBeBool = errors.New("should be bool")
9 )
10
11 func ShouldBeList(v Value) error {
12 if _, ok := v.(List); !ok {
13 return errShouldBeList
14 }
15 return nil
16 }
17
18 func ShouldBeMap(v Value) error {
19 if _, ok := v.(Map); !ok {
20 return errShouldBeMap
21 }
22 return nil
23 }
24
25 func ShouldBeFn(v Value) error {
26 if _, ok := v.(Callable); !ok {
27 return errShouldBeFn
28 }
29 return nil
30 }
31
32 func ShouldBeBool(v Value) error {
33 if _, ok := v.(Bool); !ok {
34 return errShouldBeBool
35 }
36 return nil
37 }
0 package eval
1
2 import (
3 "errors"
4 "fmt"
5 "math/big"
6 "reflect"
7
8 "github.com/elves/elvish/util"
9 )
10
11 // Definitions for Value interfaces, some simple Value types and some common
12 // Value helpers.
13
14 const (
15 NoPretty = util.MinInt
16 )
17
18 // Value is an elvish value.
19 type Value interface {
20 Kinder
21 Reprer
22 }
23
24 // Kinder is anything with kind string.
25 type Kinder interface {
26 Kind() string
27 }
28
29 // Reprer is anything with a Repr method.
30 type Reprer interface {
31 // Repr returns a string that represents a Value. The string either be a
32 // literal of that Value that is preferably deep-equal to it (like `[a b c]`
33 // for a list), or a string enclosed in "<>" containing the kind and
34 // identity of the Value(like `<fn 0xdeadcafe>`).
35 //
36 // If indent is at least 0, it should be pretty-printed with the current
37 // indentation level of indent; the indent of the first line has already
38 // been written and shall not be written in Repr. The returned string
39 // should never contain a trailing newline.
40 Repr(indent int) string
41 }
42
43 // Booler is anything that can be converted to a bool.
44 type Booler interface {
45 Bool() bool
46 }
47
48 // Stringer is anything that can be converted to a string.
49 type Stringer interface {
50 String() string
51 }
52
53 // Lener is anything with a length.
54 type Lener interface {
55 Len() int
56 }
57
58 // Iterable is anything that can be iterated.
59 type Iterable interface {
60 Iterate(func(Value) bool)
61 }
62
63 type IterableValue interface {
64 Iterable
65 Value
66 }
67
68 func collectFromIterable(it Iterable) []Value {
69 var vs []Value
70 if lener, ok := it.(Lener); ok {
71 vs = make([]Value, 0, lener.Len())
72 }
73 it.Iterate(func(v Value) bool {
74 vs = append(vs, v)
75 return true
76 })
77 return vs
78 }
79
80 // IterateKeyer is anything with keys that can be iterated.
81 type IterateKeyer interface {
82 IterateKey(func(Value) bool)
83 }
84
85 var (
86 NoArgs = []Value{}
87 NoOpts = map[string]Value{}
88 )
89
90 // Callable is anything may be called on an evalCtx with a list of Value's.
91 type Callable interface {
92 Call(ec *EvalCtx, args []Value, opts map[string]Value)
93 }
94
95 type CallableValue interface {
96 Value
97 Callable
98 }
99
100 func mustFn(v Value) Callable {
101 fn, ok := v.(Callable)
102 if !ok {
103 throw(fmt.Errorf("a %s is not callable", v.Kind()))
104 }
105 return fn
106 }
107
108 // Indexer is anything that can be indexed by Values and yields Values.
109 type Indexer interface {
110 Index(idx []Value) []Value
111 }
112
113 // IndexOneer is anything that can be indexed by one Value and yields one Value.
114 type IndexOneer interface {
115 IndexOne(idx Value) Value
116 }
117
118 // IndexSetter is a Value whose elements can be get as well as set.
119 type IndexSetter interface {
120 IndexOneer
121 IndexSet(idx Value, v Value)
122 }
123
124 func mustIndexer(v Value, ec *EvalCtx) Indexer {
125 indexer, ok := getIndexer(v, ec)
126 if !ok {
127 throw(fmt.Errorf("a %s is not indexable", v.Kind()))
128 }
129 return indexer
130 }
131
132 // getIndexer adapts a Value to an Indexer if there is an adapter. It adapts a
133 // Fn if ec is not nil.
134 func getIndexer(v Value, ec *EvalCtx) (Indexer, bool) {
135 if indexer, ok := v.(Indexer); ok {
136 return indexer, true
137 }
138 if indexOneer, ok := v.(IndexOneer); ok {
139 return IndexOneerIndexer{indexOneer}, true
140 }
141 return nil, false
142 }
143
144 // IndexOneerIndexer adapts an IndexOneer to an Indexer by calling all the
145 // indicies on the IndexOner and collect the results.
146 type IndexOneerIndexer struct {
147 IndexOneer
148 }
149
150 func (ioi IndexOneerIndexer) Index(vs []Value) []Value {
151 results := make([]Value, len(vs))
152 for i, v := range vs {
153 results[i] = ioi.IndexOneer.IndexOne(v)
154 }
155 return results
156 }
157
158 // Error definitions.
159 var (
160 ErrOnlyStrOrRat = errors.New("only str or rat may be converted to rat")
161 )
162
163 // Bool represents truthness.
164 type Bool bool
165
166 func (Bool) Kind() string {
167 return "bool"
168 }
169
170 func (b Bool) Repr(int) string {
171 if b {
172 return "$true"
173 }
174 return "$false"
175 }
176
177 func (b Bool) Bool() bool {
178 return bool(b)
179 }
180
181 // ToBool converts a Value to bool. When the Value type implements Bool(), it
182 // is used. Otherwise it is considered true.
183 func ToBool(v Value) bool {
184 if b, ok := v.(Booler); ok {
185 return b.Bool()
186 }
187 return true
188 }
189
190 // Rat is a rational number.
191 type Rat struct {
192 b *big.Rat
193 }
194
195 func (Rat) Kind() string {
196 return "string"
197 }
198
199 func (r Rat) Repr(int) string {
200 return "(rat " + r.String() + ")"
201 }
202
203 func (r Rat) String() string {
204 if r.b.IsInt() {
205 return r.b.Num().String()
206 }
207 return r.b.String()
208 }
209
210 // ToRat converts a Value to rat. A str can be converted to a rat if it can be
211 // parsed. A rat is returned as-is. Other types of values cannot be converted.
212 func ToRat(v Value) (Rat, error) {
213 switch v := v.(type) {
214 case Rat:
215 return v, nil
216 case String:
217 r := big.Rat{}
218 _, err := fmt.Sscanln(string(v), &r)
219 if err != nil {
220 return Rat{}, fmt.Errorf("%s cannot be parsed as rat", v.Repr(NoPretty))
221 }
222 return Rat{&r}, nil
223 default:
224 return Rat{}, ErrOnlyStrOrRat
225 }
226 }
227
228 // FromJSONInterface converts a interface{} that results from json.Unmarshal to
229 // a Value.
230 func FromJSONInterface(v interface{}) Value {
231 if v == nil {
232 // TODO Use a more appropriate type
233 return String("")
234 }
235 switch v.(type) {
236 case bool:
237 return Bool(v.(bool))
238 case float64, string:
239 // TODO Use a numeric type for float64
240 return String(fmt.Sprint(v))
241 case []interface{}:
242 a := v.([]interface{})
243 vs := make([]Value, len(a))
244 for i, v := range a {
245 vs[i] = FromJSONInterface(v)
246 }
247 return NewList(vs...)
248 case map[string]interface{}:
249 m := v.(map[string]interface{})
250 mv := make(map[Value]Value)
251 for k, v := range m {
252 mv[String(k)] = FromJSONInterface(v)
253 }
254 return Map{&mv}
255 default:
256 throw(fmt.Errorf("unexpected json type: %T", v))
257 return nil // not reached
258 }
259 }
260
261 // DeepEq compares two Value's deeply.
262 func DeepEq(a, b Value) bool {
263 return reflect.DeepEqual(a, b)
264 }
0 package eval
1
2 import (
3 "errors"
4 "reflect"
5 "testing"
6
7 "github.com/elves/elvish/glob"
8 )
9
10 var reprTests = []struct {
11 v Value
12 want string
13 }{
14 {String("233"), "233"},
15 {String("a\nb"), `"a\nb"`},
16 {String("foo bar"), "'foo bar'"},
17 {String("a\x00b"), `"a\x00b"`},
18 {Bool(true), "$true"},
19 {Bool(false), "$false"},
20 {&Exception{nil, nil}, "$ok"},
21 {&Exception{errors.New("foo bar"), nil}, "?(fail 'foo bar')"},
22 {&Exception{
23 PipelineError{[]*Exception{{nil, nil}, {errors.New("lorem"), nil}}}, nil},
24 "?(multi-error $ok ?(fail lorem))"},
25 {&Exception{Return, nil}, "?(return)"},
26 {NewList(), "[]"},
27 {NewList(String("bash"), Bool(false)), "[bash $false]"},
28 {Map{&map[Value]Value{}}, "[&]"},
29 {Map{&map[Value]Value{&Exception{nil, nil}: String("elvish")}}, "[&$ok=elvish]"},
30 // TODO: test maps of more elements
31 }
32
33 func TestRepr(t *testing.T) {
34 for _, test := range reprTests {
35 repr := test.v.Repr(NoPretty)
36 if repr != test.want {
37 t.Errorf("Repr = %s, want %s", repr, test.want)
38 }
39 }
40 }
41
42 var stringToSegmentsTests = []struct {
43 s string
44 want []glob.Segment
45 }{
46 {"", []glob.Segment{}},
47 {"a", []glob.Segment{glob.Literal{"a"}}},
48 {"/a", []glob.Segment{glob.Slash{}, glob.Literal{"a"}}},
49 {"a/", []glob.Segment{glob.Literal{"a"}, glob.Slash{}}},
50 {"/a/", []glob.Segment{glob.Slash{}, glob.Literal{"a"}, glob.Slash{}}},
51 {"a//b", []glob.Segment{glob.Literal{"a"}, glob.Slash{}, glob.Literal{"b"}}},
52 }
53
54 func TestStringToSegments(t *testing.T) {
55 for _, tc := range stringToSegmentsTests {
56 segs := stringToSegments(tc.s)
57 if !reflect.DeepEqual(segs, tc.want) {
58 t.Errorf("stringToSegments(%q) => %v, want %v", tc.s, segs, tc.want)
59 }
60 }
61 }
0 package eval
1
2 import (
3 "errors"
4 "os"
5 )
6
7 var (
8 ErrRoCannotBeSet = errors.New("read-only variable; cannot be set")
9 )
10
11 // Variable represents an elvish variable.
12 type Variable interface {
13 Set(v Value)
14 Get() Value
15 }
16
17 type ptrVariable struct {
18 valuePtr *Value
19 validator func(Value) error
20 }
21
22 type invalidValueError struct {
23 inner error
24 }
25
26 func (err invalidValueError) Error() string {
27 return "invalid value: " + err.inner.Error()
28 }
29
30 func NewPtrVariable(v Value) Variable {
31 return NewPtrVariableWithValidator(v, nil)
32 }
33
34 func NewPtrVariableWithValidator(v Value, vld func(Value) error) Variable {
35 return ptrVariable{&v, vld}
36 }
37
38 func (iv ptrVariable) Set(val Value) {
39 if iv.validator != nil {
40 if err := iv.validator(val); err != nil {
41 throw(invalidValueError{err})
42 }
43 }
44 *iv.valuePtr = val
45 }
46
47 func (iv ptrVariable) Get() Value {
48 return *iv.valuePtr
49 }
50
51 type roVariable struct {
52 value Value
53 }
54
55 func NewRoVariable(v Value) Variable {
56 return roVariable{v}
57 }
58
59 func (rv roVariable) Set(val Value) {
60 throw(ErrRoCannotBeSet)
61 }
62
63 func (rv roVariable) Get() Value {
64 return rv.value
65 }
66
67 type cbVariable struct {
68 set func(Value)
69 get func() Value
70 }
71
72 // MakeVariableFromCallback makes a variable from a set callback and a get
73 // callback.
74 func MakeVariableFromCallback(set func(Value), get func() Value) Variable {
75 return &cbVariable{set, get}
76 }
77
78 func (cv *cbVariable) Set(val Value) {
79 cv.set(val)
80 }
81
82 func (cv *cbVariable) Get() Value {
83 return cv.get()
84 }
85
86 type roCbVariable func() Value
87
88 // MakeRoVariableFromCallback makes a read-only variable from a get callback.
89 func MakeRoVariableFromCallback(get func() Value) Variable {
90 return roCbVariable(get)
91 }
92
93 func (cv roCbVariable) Set(Value) {
94 throw(ErrRoCannotBeSet)
95 }
96
97 func (cv roCbVariable) Get() Value {
98 return cv()
99 }
100
101 // elemVariable is an element of a IndexSetter.
102 type elemVariable struct {
103 container IndexSetter
104 index Value
105 }
106
107 func (ev elemVariable) Set(val Value) {
108 ev.container.IndexSet(ev.index, val)
109 }
110
111 func (ev elemVariable) Get() Value {
112 return ev.container.IndexOne(ev.index)
113 }
114
115 type envVariable struct {
116 name string
117 }
118
119 func (ev envVariable) Set(val Value) {
120 os.Setenv(ev.name, ToString(val))
121 }
122
123 func (ev envVariable) Get() Value {
124 return String(os.Getenv(ev.name))
125 }
0 package eval
1
2 import (
3 "os"
4 "testing"
5
6 "github.com/elves/elvish/util"
7 )
8
9 func TestPtrVariable(t *testing.T) {
10 v := NewPtrVariable(Bool(true))
11 if v.Get() != Bool(true) {
12 t.Errorf("PtrVariable.Get doesn't return initial value")
13 }
14 v.Set(String("233"))
15 if v.Get() != String("233") {
16 t.Errorf("PtrVariable.Get doesn't return altered value")
17 }
18
19 v = NewPtrVariableWithValidator(Bool(true), ShouldBeBool)
20 if util.DoesntThrow(func() { v.Set(String("233")) }) {
21 t.Errorf("PtrVariable.Set doesn't error when setting incompatible value")
22 }
23 }
24
25 func TestRoVariable(t *testing.T) {
26 v := NewRoVariable(String("haha"))
27 if v.Get() != String("haha") {
28 t.Errorf("RoVariable.Get doesn't return initial value")
29 }
30 if util.DoesntThrow(func() { v.Set(String("lala")) }) {
31 t.Errorf("RoVariable.Set doesn't error")
32 }
33 }
34
35 func TestCbVariable(t *testing.T) {
36 getCalled := false
37 get := func() Value {
38 getCalled = true
39 return String("cb")
40 }
41 var setCalledWith Value
42 set := func(v Value) {
43 setCalledWith = v
44 }
45
46 v := MakeVariableFromCallback(set, get)
47 if v.Get() != String("cb") {
48 t.Errorf("cbVariable doesn't return value from callback")
49 }
50 if !getCalled {
51 t.Errorf("cbVariable doesn't call callback")
52 }
53 v.Set(String("setting"))
54 if setCalledWith != String("setting") {
55 t.Errorf("cbVariable.Set doesn't call setter with value")
56 }
57 }
58
59 func TestRoCbVariable(t *testing.T) {
60 getCalled := false
61 get := func() Value {
62 getCalled = true
63 return String("cb")
64 }
65 v := MakeRoVariableFromCallback(get)
66 if v.Get() != String("cb") {
67 t.Errorf("roCbVariable doesn't return value from callback")
68 }
69 if !getCalled {
70 t.Errorf("roCbVariable doesn't call callback")
71 }
72 if util.DoesntThrow(func() { v.Set(String("lala")) }) {
73 t.Errorf("roCbVariable.Set doesn't error")
74 }
75 }
76
77 func TestEnvVariable(t *testing.T) {
78 name := "elvish_test"
79 v := envVariable{name}
80 os.Setenv(name, "foo")
81 if v.Get() != String("foo") {
82 t.Errorf("envVariable.Get doesn't return env value")
83 }
84 v.Set(String("bar"))
85 if os.Getenv(name) != "bar" {
86 t.Errorf("envVariable.Set doesn't alter env value")
87 }
88 }
0 // getopt is a command-line argument parser. It tries to cover all common
1 // styles of option syntaxes, and provides context information when given a
2 // partial input. It is useful for writing completion engines and wrapper
3 // programs.
4 //
5 // If you are looking for an option parser for your go programm, consider using
6 // the flag package in the standard library instead.
7 package getopt
8
9 //go:generate stringer -type=Config,HasArg,ContextType -output=string.go
10
11 import "strings"
12
13 // Getopt specifies the syntax of command-line arguments.
14 type Getopt struct {
15 Options []*Option
16 Config Config
17 }
18
19 // Config configurates the parsing behavior.
20 type Config uint
21
22 const (
23 // DoubleDashTerminatesOptions indicates that all elements after an argument
24 // "--" are treated as arguments.
25 DoubleDashTerminatesOptions Config = 1 << iota
26 // FirstArgTerminatesOptions indicates that all elements after the first
27 // argument are treated as arguments.
28 FirstArgTerminatesOptions
29 // LongOnly indicates that long options may be started by either one or two
30 // dashes, and short options are not allowed. Should replicate the behavior
31 // of getopt_long_only and the
32 // flag package of the Go standard library.
33 LongOnly
34 // GNUGetoptLong is a configuration that should replicate the behavior of
35 // GNU getopt_long.
36 GNUGetoptLong = DoubleDashTerminatesOptions
37 // POSIXGetopt is a configuration that should replicate the behavior of
38 // POSIX getopt.
39 POSIXGetopt = DoubleDashTerminatesOptions | FirstArgTerminatesOptions
40 )
41
42 // HasAll tests whether a configuration has all specified flags set.
43 func (conf Config) HasAll(flags Config) bool {
44 return (conf & flags) == flags
45 }
46
47 // Option is a command-line option.
48 type Option struct {
49 // Short option. Set to 0 for long-only.
50 Short rune
51 // Long option. Set to "" for short-only.
52 Long string
53 // Whether the option takes an argument, and whether it is required.
54 HasArg HasArg
55 }
56
57 // HasArg indicates whether an option takes an argument, and whether it is
58 // required.
59 type HasArg uint
60
61 const (
62 // NoArgument indicates that an option takes no argument.
63 NoArgument HasArg = iota
64 // RequiredArgument indicates that an option must take an argument. The
65 // argument can come either directly after a short option (-oarg), after a
66 // long option followed by an equal sign (--long=arg), or as a subsequent
67 // argument after the option (-o arg, --long arg).
68 RequiredArgument
69 // OptionalArgument indicates that an option takes an optional argument.
70 // The argument can come either directly after a short option (-oarg) or
71 // after a long option followed by an equal sign (--long=arg).
72 OptionalArgument
73 )
74
75 // ParsedOption represents a parsed option.
76 type ParsedOption struct {
77 Option *Option
78 Long bool
79 Argument string
80 }
81
82 // Context indicates what may come after the supplied argument list.
83 type Context struct {
84 // The nature of the context.
85 Type ContextType
86 // Current option, with a likely incomplete Argument. Non-nil when Type is
87 // OptionArgument.
88 Option *ParsedOption
89 // Current partial long option name or argument. Non-empty when Type is
90 // LongOption or Argument.
91 Text string
92 }
93
94 // ContextType encodes what may be appended to the last element of the argument
95 // list.
96 type ContextType uint
97
98 const (
99 // NewOptionOrArgument indicates that the last element may be either a new
100 // option or a new argument. Returned when it is an empty string.
101 NewOptionOrArgument ContextType = iota
102 // NewOption indicates that the last element must be new option, short or
103 // long. Returned when it is "-".
104 NewOption
105 // NewLongOption indicates that the last element must be a new long option.
106 // Returned when it is "--".
107 NewLongOption
108 // LongOption indicates that the last element is a long option, but not its
109 // argument. The partial name of the long option is stored in Context.Text.
110 LongOption
111 // ChainShortOption indicates that a new short option may be chained.
112 // Returned when the last element consists of a chain of options that take
113 // no arguments.
114 ChainShortOption
115 // OptionArgument indicates that the last element list must be an argument
116 // to an option. The option in question is stored in Context.Option.
117 OptionArgument
118 // Argument indicates that the last element is an argument. The partial
119 // argument is stored in Context.Text.
120 Argument
121 )
122
123 func (g *Getopt) findShort(r rune) *Option {
124 for _, opt := range g.Options {
125 if r == opt.Short {
126 return opt
127 }
128 }
129 return nil
130 }
131
132 // parseShort parse short options, without the leading dash. It returns the
133 // parsed options and whether an argument is still to be seen.
134 func (g *Getopt) parseShort(s string) ([]*ParsedOption, bool) {
135 var opts []*ParsedOption
136 var needArg bool
137 for i, r := range s {
138 opt := g.findShort(r)
139 if opt != nil {
140 if opt.HasArg == NoArgument {
141 opts = append(opts, &ParsedOption{opt, false, ""})
142 continue
143 } else {
144 parsed := &ParsedOption{opt, false, s[i+len(string(r)):]}
145 opts = append(opts, parsed)
146 needArg = parsed.Argument == "" && opt.HasArg == RequiredArgument
147 break
148 }
149 }
150 // Unknown option, treat as taking an optional argument
151 parsed := &ParsedOption{
152 &Option{r, "", OptionalArgument}, false, s[i+len(string(r)):]}
153 opts = append(opts, parsed)
154 break
155 }
156 return opts, needArg
157 }
158
159 // parseLong parse a long option, without the leading dashes. It returns the
160 // parsed option and whether an argument is still to be seen.
161 func (g *Getopt) parseLong(s string) (*ParsedOption, bool) {
162 eq := strings.IndexRune(s, '=')
163 for _, opt := range g.Options {
164 if s == opt.Long {
165 return &ParsedOption{opt, true, ""}, opt.HasArg == RequiredArgument
166 } else if eq != -1 && s[:eq] == opt.Long {
167 return &ParsedOption{opt, true, s[eq+1:]}, false
168 }
169 }
170 // Unknown option, treat as taking an optional argument
171 if eq == -1 {
172 return &ParsedOption{&Option{0, s, OptionalArgument}, true, ""}, false
173 }
174 return &ParsedOption{&Option{0, s[:eq], OptionalArgument}, true, s[eq+1:]}, false
175 }
176
177 // Parse parses an argument list.
178 func (g *Getopt) Parse(elems []string) ([]*ParsedOption, []string, *Context) {
179 var (
180 opts []*ParsedOption
181 args []string
182 // Non-nil only when the last element was an option with required
183 // argument, but the argument has not been seen.
184 opt *ParsedOption
185 // True if an option terminator has been seen. The criteria of option
186 // terminators is determined by the configuration.
187 noopt bool
188 )
189 var elem string
190 hasPrefix := func(p string) bool { return strings.HasPrefix(elem, p) }
191 for _, elem = range elems[:len(elems)-1] {
192 if opt != nil {
193 opt.Argument = elem
194 opts = append(opts, opt)
195 opt = nil
196 } else if noopt {
197 args = append(args, elem)
198 } else if g.Config.HasAll(DoubleDashTerminatesOptions) && elem == "--" {
199 noopt = true
200 } else if hasPrefix("--") {
201 newopt, needArg := g.parseLong(elem[2:])
202 if needArg {
203 opt = newopt
204 } else {
205 opts = append(opts, newopt)
206 }
207 } else if hasPrefix("-") {
208 if g.Config.HasAll(LongOnly) {
209 newopt, needArg := g.parseLong(elem[1:])
210 if needArg {
211 opt = newopt
212 } else {
213 opts = append(opts, newopt)
214 }
215 } else {
216 newopts, needArg := g.parseShort(elem[1:])
217 if needArg {
218 opts = append(opts, newopts[:len(newopts)-1]...)
219 opt = newopts[len(newopts)-1]
220 } else {
221 opts = append(opts, newopts...)
222 }
223 }
224 } else {
225 args = append(args, elem)
226 if g.Config.HasAll(FirstArgTerminatesOptions) {
227 noopt = true
228 }
229 }
230 }
231 elem = elems[len(elems)-1]
232 ctx := &Context{}
233 if opt != nil {
234 opt.Argument = elem
235 ctx.Type, ctx.Option = OptionArgument, opt
236 } else if noopt {
237 ctx.Type, ctx.Text = Argument, elem
238 } else if elem == "" {
239 ctx.Type = NewOptionOrArgument
240 } else if elem == "-" {
241 ctx.Type = NewOption
242 } else if elem == "--" {
243 ctx.Type = NewLongOption
244 } else if hasPrefix("--") {
245 if strings.IndexRune(elem, '=') == -1 {
246 ctx.Type, ctx.Text = LongOption, elem[2:]
247 } else {
248 newopt, _ := g.parseLong(elem[2:])
249 ctx.Type, ctx.Option = OptionArgument, newopt
250 }
251 } else if hasPrefix("-") {
252 if g.Config.HasAll(LongOnly) {
253 if strings.IndexRune(elem, '=') == -1 {
254 ctx.Type, ctx.Text = LongOption, elem[1:]
255 } else {
256 newopt, _ := g.parseLong(elem[1:])
257 ctx.Type, ctx.Option = OptionArgument, newopt
258 }
259 } else {
260 newopts, _ := g.parseShort(elem[1:])
261 if newopts[len(newopts)-1].Option.HasArg == NoArgument {
262 opts = append(opts, newopts...)
263 ctx.Type = ChainShortOption
264 } else {
265 opts = append(opts, newopts[:len(newopts)-1]...)
266 ctx.Type, ctx.Option = OptionArgument, newopts[len(newopts)-1]
267 }
268 }
269 } else {
270 ctx.Type, ctx.Text = Argument, elem
271 }
272 return opts, args, ctx
273 }
0 package getopt
1
2 import (
3 "reflect"
4 "testing"
5 )
6
7 var options = []*Option{
8 {'a', "all", NoArgument},
9 {'o', "option", RequiredArgument},
10 {'n', "number", OptionalArgument},
11 }
12
13 var cases = []struct {
14 config Config
15 elems []string
16 wantOpts []*ParsedOption
17 wantArgs []string
18 wantCtx *Context
19 }{
20 // NoArgument, short option.
21 {0, []string{"-a", ""},
22 []*ParsedOption{{options[0], false, ""}},
23 nil, &Context{Type: NewOptionOrArgument}},
24 // NoArgument, long option.
25 {0, []string{"--all", ""},
26 []*ParsedOption{{options[0], true, ""}},
27 nil, &Context{Type: NewOptionOrArgument}},
28
29 // RequiredArgument, argument following the option directly
30 {0, []string{"-oname=elvish", ""},
31 []*ParsedOption{{options[1], false, "name=elvish"}},
32 nil, &Context{Type: NewOptionOrArgument}},
33 // RequiredArgument, argument in next element
34 {0, []string{"-o", "name=elvish", ""},
35 []*ParsedOption{{options[1], false, "name=elvish"}},
36 nil, &Context{Type: NewOptionOrArgument}},
37 // RequiredArgument, long option, argument following the option directly
38 {0, []string{"--option=name=elvish", ""},
39 []*ParsedOption{{options[1], true, "name=elvish"}},
40 nil, &Context{Type: NewOptionOrArgument}},
41 // RequiredArgument, long option, argument in next element
42 {0, []string{"--option", "name=elvish", ""},
43 []*ParsedOption{{options[1], true, "name=elvish"}},
44 nil, &Context{Type: NewOptionOrArgument}},
45
46 // OptionalArgument, with argument
47 {0, []string{"-n1", ""},
48 []*ParsedOption{{options[2], false, "1"}},
49 nil, &Context{Type: NewOptionOrArgument}},
50 // OptionalArgument, without argument
51 {0, []string{"-n", ""},
52 []*ParsedOption{{options[2], false, ""}},
53 nil, &Context{Type: NewOptionOrArgument}},
54
55 // DoubleDashTerminatesOptions
56 {DoubleDashTerminatesOptions, []string{"-a", "--", "-o", ""},
57 []*ParsedOption{{options[0], false, ""}},
58 []string{"-o"}, &Context{Type: Argument}},
59 // FirstArgTerminatesOptions
60 {FirstArgTerminatesOptions, []string{"-a", "x", "-o", ""},
61 []*ParsedOption{{options[0], false, ""}},
62 []string{"x", "-o"}, &Context{Type: Argument}},
63 // LongOnly
64 {LongOnly, []string{"-all", ""},
65 []*ParsedOption{{options[0], true, ""}},
66 nil, &Context{Type: NewOptionOrArgument}},
67
68 // NewOption
69 {0, []string{"-"}, nil, nil, &Context{Type: NewOption}},
70 // NewLongOption
71 {0, []string{"--"}, nil, nil, &Context{Type: NewLongOption}},
72 // LongOption
73 {0, []string{"--all"}, nil, nil,
74 &Context{Type: LongOption, Text: "all"}},
75 // LongOption, LongOnly
76 {LongOnly, []string{"-all"}, nil, nil,
77 &Context{Type: LongOption, Text: "all"}},
78 // ChainShortOption
79 {0, []string{"-a"},
80 []*ParsedOption{{options[0], false, ""}}, nil,
81 &Context{Type: ChainShortOption}},
82 // OptionArgument, short option, same element
83 {0, []string{"-o"}, nil, nil,
84 &Context{Type: OptionArgument,
85 Option: &ParsedOption{options[1], false, ""}}},
86 // OptionArgument, short option, separate element
87 {0, []string{"-o", ""}, nil, nil,
88 &Context{Type: OptionArgument,
89 Option: &ParsedOption{options[1], false, ""}}},
90 // OptionArgument, long option, same element
91 {0, []string{"--option="}, nil, nil,
92 &Context{Type: OptionArgument,
93 Option: &ParsedOption{options[1], true, ""}}},
94 // OptionArgument, long option, separate element
95 {0, []string{"--option", ""}, nil, nil,
96 &Context{Type: OptionArgument,
97 Option: &ParsedOption{options[1], true, ""}}},
98 // OptionArgument, long only, same element
99 {LongOnly, []string{"-option="}, nil, nil,
100 &Context{Type: OptionArgument,
101 Option: &ParsedOption{options[1], true, ""}}},
102 // OptionArgument, long only, separate element
103 {LongOnly, []string{"-option", ""}, nil, nil,
104 &Context{Type: OptionArgument,
105 Option: &ParsedOption{options[1], true, ""}}},
106 // Argument
107 {0, []string{"x"}, nil, nil,
108 &Context{Type: Argument, Text: "x"}},
109
110 // Unknown short option, same element
111 {0, []string{"-x"}, nil, nil,
112 &Context{
113 Type: OptionArgument,
114 Option: &ParsedOption{
115 &Option{'x', "", OptionalArgument}, false, ""}}},
116 // Unknown short option, separate element
117 {0, []string{"-x", ""},
118 []*ParsedOption{{
119 &Option{'x', "", OptionalArgument}, false, ""}},
120 nil,
121 &Context{Type: NewOptionOrArgument}},
122
123 // Unknown long option
124 {0, []string{"--unknown", ""},
125 []*ParsedOption{{
126 &Option{0, "unknown", OptionalArgument}, true, ""}},
127 nil,
128 &Context{Type: NewOptionOrArgument}},
129 // Unknown long option, with argument
130 {0, []string{"--unknown=value", ""},
131 []*ParsedOption{{
132 &Option{0, "unknown", OptionalArgument}, true, "value"}},
133 nil,
134 &Context{Type: NewOptionOrArgument}},
135
136 // Unknown long option, LongOnly
137 {LongOnly, []string{"-unknown", ""},
138 []*ParsedOption{{
139 &Option{0, "unknown", OptionalArgument}, true, ""}},
140 nil,
141 &Context{Type: NewOptionOrArgument}},
142 // Unknown long option, with argument
143 {LongOnly, []string{"-unknown=value", ""},
144 []*ParsedOption{{
145 &Option{0, "unknown", OptionalArgument}, true, "value"}},
146 nil,
147 &Context{Type: NewOptionOrArgument}},
148 }
149
150 func TestGetopt(t *testing.T) {
151 for _, tc := range cases {
152 g := &Getopt{options, tc.config}
153 opts, args, ctx := g.Parse(tc.elems)
154 shouldEqual := func(name string, got, want interface{}) {
155 if !reflect.DeepEqual(got, want) {
156 t.Errorf("Parse(%#v) (config = %v)\ngot %s = %v, want %v",
157 tc.elems, tc.config, name, got, want)
158 }
159 }
160 shouldEqual("opts", opts, tc.wantOpts)
161 shouldEqual("args", args, tc.wantArgs)
162 shouldEqual("ctx", ctx, tc.wantCtx)
163 }
164 }
0 // Code generated by "stringer -type=Config,HasArg,ContextType -output=string.go"; DO NOT EDIT
1
2 package getopt
3
4 import "fmt"
5
6 const (
7 _Config_name_0 = "DoubleDashTerminatesOptionsFirstArgTerminatesOptions"
8 _Config_name_1 = "LongOnly"
9 )
10
11 var (
12 _Config_index_0 = [...]uint8{0, 27, 52}
13 _Config_index_1 = [...]uint8{0, 8}
14 )
15
16 func (i Config) String() string {
17 switch {
18 case 1 <= i && i <= 2:
19 i -= 1
20 return _Config_name_0[_Config_index_0[i]:_Config_index_0[i+1]]
21 case i == 4:
22 return _Config_name_1
23 default:
24 return fmt.Sprintf("Config(%d)", i)
25 }
26 }
27
28 const _HasArg_name = "NoArgumentRequiredArgumentOptionalArgument"
29
30 var _HasArg_index = [...]uint8{0, 10, 26, 42}
31
32 func (i HasArg) String() string {
33 if i >= HasArg(len(_HasArg_index)-1) {
34 return fmt.Sprintf("HasArg(%d)", i)
35 }
36 return _HasArg_name[_HasArg_index[i]:_HasArg_index[i+1]]
37 }
38
39 const _ContextType_name = "NewOptionOrArgumentNewOptionNewLongOptionLongOptionChainShortOptionOptionArgumentArgument"
40
41 var _ContextType_index = [...]uint8{0, 19, 28, 41, 51, 67, 81, 89}
42
43 func (i ContextType) String() string {
44 if i >= ContextType(len(_ContextType_index)-1) {
45 return fmt.Sprintf("ContextType(%d)", i)
46 }
47 return _ContextType_name[_ContextType_index[i]:_ContextType_index[i+1]]
48 }
0 // Package glob implements globbing for elvish.
1 package glob
2
3 import (
4 "io/ioutil"
5 "os"
6 "unicode/utf8"
7 )
8
9 // Glob returns a list of file names satisfying the given pattern.
10 func Glob(p string, cb func(string) bool) bool {
11 return Parse(p).Glob(cb)
12 }
13
14 // Glob returns a list of file names satisfying the Pattern.
15 func (p Pattern) Glob(cb func(string) bool) bool {
16 segs := p.Segments
17 dir := ""
18 if len(segs) > 0 && IsSlash(segs[0]) {
19 segs = segs[1:]
20 dir = "/"
21 }
22
23 if p.DirOverride != "" {
24 dir = p.DirOverride
25 }
26
27 return glob(segs, dir, cb)
28 }
29
30 func glob(segs []Segment, dir string, cb func(string) bool) bool {
31 // Consume the non-wildcard prefix. This is required so that "." and "..",
32 // which doesn't appear in the result of ReadDir, can appear as standalone
33 // path components in the pattern.
34 for len(segs) > 0 && IsLiteral(segs[0]) {
35 seg0 := segs[0].(Literal).Data
36 var path string
37 switch dir {
38 case "":
39 path = seg0
40 case "/":
41 path = "/" + seg0
42 default:
43 path = dir + "/" + seg0
44 }
45 if len(segs) == 1 {
46 // A lone literal. Generate it if the named file exists, and return.
47 if _, err := os.Stat(path); err == nil {
48 return cb(path)
49 }
50 return true
51 } else if IsSlash(segs[1]) {
52 // A lone literal followed by a slash. Change the directory if it
53 // exists, otherwise return.
54 if info, err := os.Stat(path); err == nil && info.IsDir() {
55 dir = path
56 } else {
57 return true
58 }
59 segs = segs[2:]
60 } else {
61 break
62 }
63 }
64
65 // Empty segment, resulting from a trailing slash. Generate the starting
66 // directory.
67 if len(segs) == 0 {
68 return cb(dir + "/")
69 }
70
71 var prefix string
72 if dir == "" {
73 prefix = ""
74 dir = "."
75 } else if dir == "/" {
76 prefix = "/"
77 } else {
78 // dir never has a trailing slash unless it is /.
79 prefix = dir + "/"
80 }
81
82 i := -1
83 nexti := func() {
84 for i++; i < len(segs); i++ {
85 if IsSlash(segs[i]) || IsWild1(segs[i], StarStar) {
86 break
87 }
88 }
89 }
90 nexti()
91
92 infos, err := ioutil.ReadDir(dir)
93 if err != nil {
94 // XXX Silently drop the error
95 return true
96 }
97
98 // Enumerate the position of the first slash.
99 for i < len(segs) {
100 slash := IsSlash(segs[i])
101 var first, rest []Segment
102 if slash {
103 // segs = x/y. Match dir with x, recurse on y.
104 first, rest = segs[:i], segs[i+1:]
105 } else {
106 // segs = x**y. Match dir with x*, recurse on **y.
107 first, rest = segs[:i+1], segs[i:]
108 }
109
110 for _, info := range infos {
111 name := info.Name()
112 if match(first, name) && info.IsDir() {
113 if !glob(rest, prefix+name, cb) {
114 return false
115 }
116 }
117 }
118
119 if slash {
120 // First slash cannot appear later than a slash in the pattern.
121 return true
122 }
123 nexti()
124 }
125
126 // If we reach here, it is possible to have no slashes at all.
127 for _, info := range infos {
128 name := info.Name()
129 if match(segs, name) {
130 if !cb(prefix + name) {
131 return false
132 }
133 }
134 }
135 return true
136 }
137
138 // match matches a name against segments. It treats StarStar segments as they
139 // are Star segments. The segments may not contain Slash'es.
140 func match(segs []Segment, name string) bool {
141 if len(segs) == 0 {
142 return name == ""
143 }
144 // If the name start with "." and the first segment is a Wild, only match
145 // when MatchHidden is true.
146 if len(name) > 0 && name[0] == '.' && IsWild(segs[0]) {
147 seg := segs[0].(Wild)
148 if !seg.MatchHidden {
149 return false
150 }
151 }
152 segs:
153 for len(segs) > 0 {
154 // Find a chunk. A chunk is a run of Literal and Question, with an
155 // optional leading Star.
156 var i int
157 for i = 1; i < len(segs); i++ {
158 if IsWild2(segs[i], Star, StarStar) {
159 break
160 }
161 }
162
163 chunk := segs[:i]
164 startsWithStar := IsWild2(chunk[0], Star, StarStar)
165 var startingStar Wild
166 if startsWithStar {
167 startingStar = chunk[0].(Wild)
168 chunk = chunk[1:]
169 }
170 segs = segs[i:]
171
172 // NOTE A quick path when len(segs) == 0 can be implemented: match
173 // backwards.
174
175 // Match at the current position. If this is the last chunk, we need to
176 // make sure name is exhausted by the matching.
177 ok, rest := matchChunk(chunk, name)
178 if ok && (rest == "" || len(segs) > 0) {
179 name = rest
180 continue
181 }
182
183 if startsWithStar {
184 // NOTE An optimization is to make the upper bound not len(names),
185 // but rather len(names) - LB(# bytes segs can match)
186 for i, r := range name {
187 j := i + len(string(r))
188 // Match name[:j] with the starting *, and the rest with chunk.
189 if !startingStar.Match(r) {
190 break
191 }
192 ok, rest := matchChunk(chunk, name[j:])
193 if ok && (rest == "" || len(segs) > 0) {
194 name = rest
195 continue segs
196 }
197 }
198 }
199 return false
200 }
201 return name == ""
202 }
203
204 // matchChunk returns whether a chunk matches a prefix of name. If succeeded, it
205 // also returns the remaining part of name.
206 func matchChunk(chunk []Segment, name string) (bool, string) {
207 for _, seg := range chunk {
208 if name == "" {
209 return false, ""
210 }
211 switch seg := seg.(type) {
212 case Literal:
213 n := len(seg.Data)
214 if len(name) < n || name[:n] != seg.Data {
215 return false, ""
216 }
217 name = name[n:]
218 case Wild:
219 if seg.Type == Question {
220 r, n := utf8.DecodeRuneInString(name)
221 if !seg.Match(r) {
222 return false, ""
223 }
224 name = name[n:]
225 } else {
226 panic("chunk has non-question wild segment")
227 }
228 default:
229 panic("chunk has non-literal non-wild segment")
230 }
231 }
232 return true, name
233 }
0 package glob
1
2 import (
3 "os"
4 "reflect"
5 "sort"
6 "testing"
7
8 "github.com/elves/elvish/util"
9 )
10
11 var (
12 mkdirs = []string{"a", "b", "c", "d1", "d1/e", "d1/e/f", "d1/e/f/g",
13 "d2", "d2/e", "d2/e/f", "d2/e/f/g"}
14 mkdirDots = []string{".el"}
15 creates = []string{"a/X", "a/Y", "b/X", "c/Y",
16 "dX", "dXY",
17 "lorem", "ipsum",
18 "d1/e/f/g/X", "d2/e/f/g/X"}
19 createDots = []string{".x", ".el/x"}
20 )
21
22 var globCases = []struct {
23 pattern string
24 want []string
25 }{
26 {"*", []string{"a", "b", "c", "d1", "d2", "dX", "dXY", "lorem", "ipsum"}},
27 {".", []string{"."}},
28 {"./*", []string{"./a", "./b", "./c", "./d1", "./d2", "./dX", "./dXY", "./lorem", "./ipsum"}},
29 {"..", []string{".."}},
30 {"a/..", []string{"a/.."}},
31 {"a/../*", []string{"a/../a", "a/../b", "a/../c", "a/../d1", "a/../d2", "a/../dX", "a/../dXY", "a/../lorem", "a/../ipsum"}},
32 {"*/", []string{"a/", "b/", "c/", "d1/", "d2/"}},
33 {"**", append(mkdirs, creates...)},
34 {"*/X", []string{"a/X", "b/X"}},
35 {"**X", []string{"a/X", "b/X", "dX", "d1/e/f/g/X", "d2/e/f/g/X"}},
36 {"*/*/*", []string{"d1/e/f", "d2/e/f"}},
37 {"l*m", []string{"lorem"}},
38 {"d*", []string{"d1", "d2", "dX", "dXY"}},
39 {"d*/", []string{"d1/", "d2/"}},
40 {"d**", []string{"d1", "d1/e", "d1/e/f", "d1/e/f/g", "d1/e/f/g/X",
41 "d2", "d2/e", "d2/e/f", "d2/e/f/g", "d2/e/f/g/X", "dX", "dXY"}},
42 {"?", []string{"a", "b", "c"}},
43 {"??", []string{"d1", "d2", "dX"}},
44
45 // Nonexistent paths.
46 {"xxxx", []string{}},
47 {"xxxx/*", []string{}},
48 {"a/*/", []string{}},
49
50 // Absolute paths.
51 // NOTE: If / or /usr changes during testing, this case will fail.
52 {"/*", util.FullNames("/")},
53 {"/usr/*", util.FullNames("/usr/")},
54
55 // TODO Test cases against dotfiles.
56 }
57
58 func TestGlob(t *testing.T) {
59 util.InTempDir(func(string) {
60 for _, dir := range append(mkdirs, mkdirDots...) {
61 err := os.Mkdir(dir, 0755)
62 if err != nil {
63 panic(err)
64 }
65 }
66 for _, file := range append(creates, createDots...) {
67 f, err := os.Create(file)
68 if err != nil {
69 panic(err)
70 }
71 f.Close()
72 }
73 for _, tc := range globCases {
74 names := []string{}
75 Glob(tc.pattern, func(name string) bool {
76 names = append(names, name)
77 return true
78 })
79 sort.Strings(names)
80 sort.Strings(tc.want)
81 if !reflect.DeepEqual(names, tc.want) {
82 t.Errorf(`Glob(%q, "") => %v, want %v`, tc.pattern, names, tc.want)
83 }
84 }
85 })
86 }
0 package glob
1
2 import (
3 "bytes"
4 "unicode/utf8"
5 )
6
7 // Parse parses a pattern.
8 func Parse(s string) Pattern {
9 segments := []Segment{}
10 add := func(seg Segment) {
11 segments = append(segments, seg)
12 }
13 p := &parser{s, 0, 0}
14
15 rune:
16 for {
17 r := p.next()
18 switch r {
19 case eof:
20 break rune
21 case '?':
22 add(Wild{Question, false, nil})
23 case '*':
24 n := 1
25 for p.next() == '*' {
26 n++
27 }
28 p.backup()
29 if n == 1 {
30 add(Wild{Star, false, nil})
31 } else {
32 add(Wild{StarStar, false, nil})
33 }
34 case '/':
35 for p.next() == '/' {
36 }
37 p.backup()
38 add(Slash{})
39 default:
40 var literal bytes.Buffer
41 literal:
42 for {
43 switch r {
44 case '?', '*', '/', eof:
45 break literal
46 case '\\':
47 r = p.next()
48 if r == eof {
49 break literal
50 }
51 literal.WriteRune(r)
52 default:
53 literal.WriteRune(r)
54 }
55 r = p.next()
56 }
57 p.backup()
58 add(Literal{literal.String()})
59 }
60 }
61 return Pattern{segments, ""}
62 }
63
64 // XXX Contains duplicate code with parse/parser.go.
65
66 type parser struct {
67 src string
68 pos int
69 overEOF int
70 }
71
72 const eof rune = -1
73
74 func (ps *parser) next() rune {
75 if ps.pos == len(ps.src) {
76 ps.overEOF++
77 return eof
78 }
79 r, s := utf8.DecodeRuneInString(ps.src[ps.pos:])
80 ps.pos += s
81 return r
82 }
83
84 func (ps *parser) backup() {
85 if ps.overEOF > 0 {
86 ps.overEOF--
87 return
88 }
89 _, s := utf8.DecodeLastRuneInString(ps.src[:ps.pos])
90 ps.pos -= s
91 }
0 package glob
1
2 import (
3 "reflect"
4 "testing"
5 )
6
7 var parseCases = []struct {
8 src string
9 want []Segment
10 }{
11 {``, []Segment{}},
12 {`foo`, []Segment{Literal{"foo"}}},
13 {`*foo*bar`, []Segment{
14 Wild{Star, false, nil}, Literal{"foo"},
15 Wild{Star, false, nil}, Literal{"bar"}}},
16 {`foo**bar`, []Segment{
17 Literal{"foo"}, Wild{StarStar, false, nil}, Literal{"bar"}}},
18 {`/usr/a**b/c`, []Segment{
19 Slash{}, Literal{"usr"}, Slash{}, Literal{"a"},
20 Wild{StarStar, false, nil}, Literal{"b"}, Slash{}, Literal{"c"}}},
21 {`??b`, []Segment{
22 Wild{Question, false, nil}, Wild{Question, false, nil}, Literal{"b"}}},
23 // Multiple slashes should be parsed as one.
24 {`//a//b`, []Segment{
25 Slash{}, Literal{"a"}, Slash{}, Literal{"b"}}},
26 // Escaping.
27 {`\*\?b`, []Segment{
28 Literal{"*?b"},
29 }},
30 {`abc\`, []Segment{
31 Literal{"abc"},
32 }},
33 }
34
35 func TestParse(t *testing.T) {
36 for _, tc := range parseCases {
37 p := Parse(tc.src)
38 if !reflect.DeepEqual(p.Segments, tc.want) {
39 t.Errorf("Parse(%q) => %v, want %v", tc.src, p, tc.want)
40 }
41 }
42 }
0 package glob
1
2 // Pattern is a glob pattern.
3 type Pattern struct {
4 Segments []Segment
5 DirOverride string
6 }
7
8 // Segment is the building block of Pattern.
9 type Segment interface {
10 isSegment()
11 }
12
13 // Slash represents a slash "/".
14 type Slash struct{}
15
16 // Literal is a series of non-slash, non-wildcard characters, that is to be
17 // matched literally.
18 type Literal struct {
19 Data string
20 }
21
22 // Wild is a wildcard.
23 type Wild struct {
24 Type WildType
25 MatchHidden bool
26 Matchers []func(rune) bool
27 }
28
29 // WildType is the type of a Wild.
30 type WildType int
31
32 // Values for WildType.
33 const (
34 Question = iota
35 Star
36 StarStar
37 )
38
39 // Match returns whether a rune is within the match set.
40 func (w Wild) Match(r rune) bool {
41 if len(w.Matchers) == 0 {
42 return true
43 }
44 for _, m := range w.Matchers {
45 if m(r) {
46 return true
47 }
48 }
49 return false
50 }
51
52 func (Literal) isSegment() {}
53 func (Slash) isSegment() {}
54 func (Wild) isSegment() {}
55
56 // IsSlash returns whether a Segment is a Slash.
57 func IsSlash(seg Segment) bool {
58 _, ok := seg.(Slash)
59 return ok
60 }
61
62 // IsLiteral returns whether a Segment is a Literal.
63 func IsLiteral(seg Segment) bool {
64 _, ok := seg.(Literal)
65 return ok
66 }
67
68 // IsWild returns whether a Segment is a Wild.
69 func IsWild(seg Segment) bool {
70 _, ok := seg.(Wild)
71 return ok
72 }
73
74 // IsWild1 returns whether a Segment is a Wild and has the specified type.
75 func IsWild1(seg Segment, t WildType) bool {
76 return IsWild(seg) && seg.(Wild).Type == t
77 }
78
79 // IsWild2 returns whether a Segment is a Wild and has one of the two specified
80 // types.
81 func IsWild2(seg Segment, t1, t2 WildType) bool {
82 return IsWild(seg) && (seg.(Wild).Type == t1 || seg.(Wild).Type == t2)
83 }
0 // Elvish is an experimental Unix shell. It tries to incorporate a powerful
1 // programming language with an extensible, friendly user interface.
2 package main
3
4 // This package sets up the basic environment and calls the appropriate
5 // "subprogram", one of the daemon, the terminal interface, or the web
6 // interface.
7
8 import (
9 "errors"
10 "flag"
11 "fmt"
12 "log"
13 "os"
14 "path"
15 "runtime/pprof"
16 "strconv"
17 "syscall"
18 "time"
19
20 "github.com/elves/elvish/daemon"
21 "github.com/elves/elvish/daemon/api"
22 "github.com/elves/elvish/daemon/service"
23 "github.com/elves/elvish/eval"
24 "github.com/elves/elvish/eval/re"
25 "github.com/elves/elvish/shell"
26 "github.com/elves/elvish/store/storedefs"
27 "github.com/elves/elvish/util"
28 "github.com/elves/elvish/web"
29 )
30
31 // defaultPort is the default port on which the web interface runs. The number
32 // is chosen because it resembles "elvi".
33 const defaultWebPort = 3171
34
35 var logger = util.GetLogger("[main] ")
36
37 var (
38 // Flags handled in this package, or common to shell and daemon.
39 help = flag.Bool("help", false, "show usage help and quit")
40
41 logpath = flag.String("log", "", "a file to write debug log to")
42 cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
43 dbpath = flag.String("db", "", "path to the database")
44 sockpath = flag.String("sock", "", "path to the daemon socket")
45
46 isdaemon = flag.Bool("daemon", false, "run daemon instead of shell")
47 isweb = flag.Bool("web", false, "run backend of web interface")
48 webport = flag.Int("port", defaultWebPort, "the port of the web backend")
49
50 // Flags for shell and web.
51 cmd = flag.Bool("c", false, "take first argument as a command to execute")
52
53 // Flags for daemon.
54 forked = flag.Int("forked", 0, "how many times the daemon has forked")
55 binpath = flag.String("bin", "", "path to the elvish binary")
56 logpathprefix = flag.String("logprefix", "", "the prefix for the daemon log file")
57 )
58
59 func usage() {
60 fmt.Println("usage: elvish [flags] [script]")
61 fmt.Println("flags:")
62 flag.PrintDefaults()
63 }
64
65 func main() {
66 // This is needed for defers to be honored.
67 ret := 0
68 defer os.Exit(ret)
69
70 // Parse and check flags.
71 flag.Usage = usage
72 flag.Parse()
73 args := flag.Args()
74 if *help {
75 usage()
76 return
77 }
78 if *isdaemon && len(args) > 0 {
79 // The daemon takes no argument.
80 usage()
81 ret = 2
82 return
83 }
84
85 // Flags common to all sub-programs: log and CPU profile.
86 if *isdaemon {
87 if *forked == 2 && *logpathprefix != "" {
88 // Honor logpathprefix.
89 pid := syscall.Getpid()
90 err := util.SetOutputFile(*logpathprefix + strconv.Itoa(pid))
91 if err != nil {
92 fmt.Fprintln(os.Stderr, err)
93 }
94 } else {
95 util.SetOutputFile("/dev/stderr")
96 }
97 } else if *logpath != "" {
98 err := util.SetOutputFile(*logpath)
99 if err != nil {
100 fmt.Fprintln(os.Stderr, err)
101 }
102 }
103 if *cpuprofile != "" {
104 f, err := os.Create(*cpuprofile)
105 if err != nil {
106 log.Fatal(err)
107 }
108 pprof.StartCPUProfile(f)
109 defer pprof.StopCPUProfile()
110 }
111
112 // Pick a sub-program to run.
113 if *isdaemon {
114 d := daemon.Daemon{
115 Forked: *forked,
116 BinPath: *binpath,
117 DbPath: *dbpath,
118 SockPath: *sockpath,
119 LogPathPrefix: *logpathprefix,
120 }
121 ret = d.Main(service.Serve)
122 } else {
123 // Shell or web. Set up common runtime components.
124 ev, cl := initRuntime()
125 defer func() {
126 err := cl.Close()
127 if err != nil {
128 fmt.Fprintln(os.Stderr, "warning: failed to close connection to daemon:", err)
129 }
130 }()
131
132 if *isweb {
133 if *cmd {
134 fmt.Fprintln(os.Stderr, "-c -web not yet supported")
135 ret = 2
136 return
137 }
138 w := web.NewWeb(ev, *webport)
139 ret = w.Run(args)
140 } else {
141 sh := shell.NewShell(ev, cl, *cmd)
142 ret = sh.Run(args)
143 }
144 }
145 }
146
147 const (
148 daemonWaitOneLoop = 10 * time.Millisecond
149 daemonWaitLoops = 100
150 daemonWaitTotal = daemonWaitOneLoop * daemonWaitLoops
151 )
152
153 func initRuntime() (*eval.Evaler, *api.Client) {
154 var dataDir string
155 var err error
156
157 // Determine data directory.
158 dataDir, err = storedefs.EnsureDataDir()
159 if err != nil {
160 fmt.Fprintln(os.Stderr, "warning: cannot create data directory ~/.elvish")
161 } else {
162 if *dbpath == "" {
163 *dbpath = dataDir + "/db"
164 }
165 }
166
167 // Determine runtime directory.
168 runDir, err := getSecureRunDir()
169 if err != nil {
170 fmt.Fprintln(os.Stderr, "cannot get runtime dir /tmp/elvish-$uid, falling back to data dir ~/.elvish:", err)
171 runDir = dataDir
172 }
173 if *sockpath == "" {
174 *sockpath = runDir + "/sock"
175 }
176
177 toSpawn := &daemon.Daemon{
178 Forked: *forked,
179 BinPath: *binpath,
180 DbPath: *dbpath,
181 SockPath: *sockpath,
182 LogPathPrefix: runDir + "/daemon.log.",
183 }
184 var cl *api.Client
185 if *sockpath != "" && *dbpath != "" {
186 cl = api.NewClient(*sockpath)
187 _, statErr := os.Stat(*sockpath)
188 killed := false
189 if statErr == nil {
190 // Kill the daemon if it is outdated.
191 version, err := cl.Version()
192 if err != nil {
193 fmt.Fprintln(os.Stderr, "warning: socket exists but not responding version RPC:", err)
194 goto spawnDaemonEnd
195 }
196 logger.Printf("daemon serving version %d, want version %d", version, api.Version)
197 if version < api.Version {
198 pid, err := cl.Pid()
199 if err != nil {
200 fmt.Fprintln(os.Stderr, "warning: socket exists but not responding pid RPC:", err)
201 goto spawnDaemonEnd
202 }
203 logger.Printf("killing outdated daemon with pid %d", pid)
204 err = syscall.Kill(pid, syscall.SIGTERM)
205 if err != nil {
206 fmt.Fprintln(os.Stderr, "warning: failed to kill outdated daemon process:", err)
207 goto spawnDaemonEnd
208 }
209 logger.Println("killed outdated daemon")
210 killed = true
211 }
212 }
213 if os.IsNotExist(statErr) || killed {
214 logger.Println("socket does not exists, starting daemon")
215 err := toSpawn.Spawn()
216 if err != nil {
217 fmt.Fprintln(os.Stderr, "warning: cannot start daemon:", err)
218 } else {
219 logger.Println("started daemon")
220 }
221 for i := 0; i < daemonWaitLoops; i++ {
222 _, err := cl.Version()
223 if err == nil {
224 logger.Println("daemon online")
225 goto spawnDaemonEnd
226 } else if i == daemonWaitLoops-1 {
227 fmt.Fprintf(os.Stderr, "cannot connect to daemon after %v: %v\n", daemonWaitTotal, err)
228 cl = nil
229 goto spawnDaemonEnd
230 }
231 time.Sleep(daemonWaitOneLoop)
232 }
233 }
234 }
235 spawnDaemonEnd:
236
237 // TODO(xiaq): This information might belong somewhere else.
238 extraModules := map[string]eval.Namespace{
239 "re": re.Namespace(),
240 }
241 return eval.NewEvaler(cl, toSpawn, dataDir, extraModules), cl
242 }
243
244 var (
245 ErrBadOwner = errors.New("bad owner")
246 ErrBadPermission = errors.New("bad permission")
247 )
248
249 // getSecureRunDir stats /tmp/elvish-$uid, creating it if it doesn't yet exist,
250 // and return the directory name if it has the correct owner and permission.
251 func getSecureRunDir() (string, error) {
252 uid := syscall.Getuid()
253
254 runDir := path.Join(os.TempDir(), fmt.Sprintf("elvish-%d", uid))
255 err := os.MkdirAll(runDir, 0700)
256 if err != nil {
257 return "", fmt.Errorf("mkdir: %v", err)
258 }
259
260 var stat syscall.Stat_t
261 err = syscall.Stat(runDir, &stat)
262 if err != nil {
263 return "", fmt.Errorf("stat: %v", err)
264 }
265
266 if int(stat.Uid) != uid {
267 return "", ErrBadOwner
268 }
269 if stat.Mode&077 != 0 {
270 return "", ErrBadPermission
271 }
272 return runDir, err
273 }
0 package main
1
2 import "testing"
3
4 func TestMain(t *testing.T) {
5 // TODO(xiaq): Add tests.
6 }
0 package parse
1
2 func IsChunk(n Node) bool {
3 _, ok := n.(*Chunk)
4 return ok
5 }
6
7 func GetChunk(n Node) *Chunk {
8 if nn, ok := n.(*Chunk); ok {
9 return nn
10 }
11 return nil
12 }
13
14 func (n *Chunk) addToPipelines(ch *Pipeline) {
15 n.Pipelines = append(n.Pipelines, ch)
16 addChild(n, ch)
17 }
18
19 func ParseChunk(ps *Parser) *Chunk {
20 n := &Chunk{node: node{begin: ps.pos}}
21 n.parse(ps)
22 n.end = ps.pos
23 n.sourceText = ps.src[n.begin:n.end]
24 return n
25 }
26
27 func IsPipeline(n Node) bool {
28 _, ok := n.(*Pipeline)
29 return ok
30 }
31
32 func GetPipeline(n Node) *Pipeline {
33 if nn, ok := n.(*Pipeline); ok {
34 return nn
35 }
36 return nil
37 }
38
39 func (n *Pipeline) addToForms(ch *Form) {
40 n.Forms = append(n.Forms, ch)
41 addChild(n, ch)
42 }
43
44 func ParsePipeline(ps *Parser) *Pipeline {
45 n := &Pipeline{node: node{begin: ps.pos}}
46 n.parse(ps)
47 n.end = ps.pos
48 n.sourceText = ps.src[n.begin:n.end]
49 return n
50 }
51
52 func IsForm(n Node) bool {
53 _, ok := n.(*Form)
54 return ok
55 }
56
57 func GetForm(n Node) *Form {
58 if nn, ok := n.(*Form); ok {
59 return nn
60 }
61 return nil
62 }
63
64 func (n *Form) addToAssignments(ch *Assignment) {
65 n.Assignments = append(n.Assignments, ch)
66 addChild(n, ch)
67 }
68
69 func (n *Form) setHead(ch *Compound) {
70 n.Head = ch
71 addChild(n, ch)
72 }
73
74 func (n *Form) addToVars(ch *Compound) {
75 n.Vars = append(n.Vars, ch)
76 addChild(n, ch)
77 }
78
79 func (n *Form) addToArgs(ch *Compound) {
80 n.Args = append(n.Args, ch)
81 addChild(n, ch)
82 }
83
84 func (n *Form) addToOpts(ch *MapPair) {
85 n.Opts = append(n.Opts, ch)
86 addChild(n, ch)
87 }
88
89 func (n *Form) addToRedirs(ch *Redir) {
90 n.Redirs = append(n.Redirs, ch)
91 addChild(n, ch)
92 }
93
94 func (n *Form) setExitusRedir(ch *ExitusRedir) {
95 n.ExitusRedir = ch
96 addChild(n, ch)
97 }
98
99 func ParseForm(ps *Parser) *Form {
100 n := &Form{node: node{begin: ps.pos}}
101 n.parse(ps)
102 n.end = ps.pos
103 n.sourceText = ps.src[n.begin:n.end]
104 return n
105 }
106
107 func IsAssignment(n Node) bool {
108 _, ok := n.(*Assignment)
109 return ok
110 }
111
112 func GetAssignment(n Node) *Assignment {
113 if nn, ok := n.(*Assignment); ok {
114 return nn
115 }
116 return nil
117 }
118
119 func (n *Assignment) setLeft(ch *Indexing) {
120 n.Left = ch
121 addChild(n, ch)
122 }
123
124 func (n *Assignment) setRight(ch *Compound) {
125 n.Right = ch
126 addChild(n, ch)
127 }
128
129 func ParseAssignment(ps *Parser) *Assignment {
130 n := &Assignment{node: node{begin: ps.pos}}
131 n.parse(ps)
132 n.end = ps.pos
133 n.sourceText = ps.src[n.begin:n.end]
134 return n
135 }
136
137 func IsExitusRedir(n Node) bool {
138 _, ok := n.(*ExitusRedir)
139 return ok
140 }
141
142 func GetExitusRedir(n Node) *ExitusRedir {
143 if nn, ok := n.(*ExitusRedir); ok {
144 return nn
145 }
146 return nil
147 }
148
149 func (n *ExitusRedir) setDest(ch *Compound) {
150 n.Dest = ch
151 addChild(n, ch)
152 }
153
154 func ParseExitusRedir(ps *Parser) *ExitusRedir {
155 n := &ExitusRedir{node: node{begin: ps.pos}}
156 n.parse(ps)
157 n.end = ps.pos
158 n.sourceText = ps.src[n.begin:n.end]
159 return n
160 }
161
162 func IsRedir(n Node) bool {
163 _, ok := n.(*Redir)
164 return ok
165 }
166
167 func GetRedir(n Node) *Redir {
168 if nn, ok := n.(*Redir); ok {
169 return nn
170 }
171 return nil
172 }
173
174 func (n *Redir) setLeft(ch *Compound) {
175 n.Left = ch
176 addChild(n, ch)
177 }
178
179 func (n *Redir) setRight(ch *Compound) {
180 n.Right = ch
181 addChild(n, ch)
182 }
183
184 func ParseRedir(ps *Parser, dest *Compound) *Redir {
185 n := &Redir{node: node{begin: ps.pos}}
186 n.parse(ps, dest)
187 n.end = ps.pos
188 n.sourceText = ps.src[n.begin:n.end]
189 return n
190 }
191
192 func IsCompound(n Node) bool {
193 _, ok := n.(*Compound)
194 return ok
195 }
196
197 func GetCompound(n Node) *Compound {
198 if nn, ok := n.(*Compound); ok {
199 return nn
200 }
201 return nil
202 }
203
204 func (n *Compound) addToIndexings(ch *Indexing) {
205 n.Indexings = append(n.Indexings, ch)
206 addChild(n, ch)
207 }
208
209 func ParseCompound(ps *Parser, head bool) *Compound {
210 n := &Compound{node: node{begin: ps.pos}}
211 n.parse(ps, head)
212 n.end = ps.pos
213 n.sourceText = ps.src[n.begin:n.end]
214 return n
215 }
216
217 func IsIndexing(n Node) bool {
218 _, ok := n.(*Indexing)
219 return ok
220 }
221
222 func GetIndexing(n Node) *Indexing {
223 if nn, ok := n.(*Indexing); ok {
224 return nn
225 }
226 return nil
227 }
228
229 func (n *Indexing) setHead(ch *Primary) {
230 n.Head = ch
231 addChild(n, ch)
232 }
233
234 func (n *Indexing) addToIndicies(ch *Array) {
235 n.Indicies = append(n.Indicies, ch)
236 addChild(n, ch)
237 }
238
239 func ParseIndexing(ps *Parser, head bool) *Indexing {
240 n := &Indexing{node: node{begin: ps.pos}}
241 n.parse(ps, head)
242 n.end = ps.pos
243 n.sourceText = ps.src[n.begin:n.end]
244 return n
245 }
246
247 func IsArray(n Node) bool {
248 _, ok := n.(*Array)
249 return ok
250 }
251
252 func GetArray(n Node) *Array {
253 if nn, ok := n.(*Array); ok {
254 return nn
255 }
256 return nil
257 }
258
259 func (n *Array) addToCompounds(ch *Compound) {
260 n.Compounds = append(n.Compounds, ch)
261 addChild(n, ch)
262 }
263
264 func ParseArray(ps *Parser, allowSemicolon bool) *Array {
265 n := &Array{node: node{begin: ps.pos}}
266 n.parse(ps, allowSemicolon)
267 n.end = ps.pos
268 n.sourceText = ps.src[n.begin:n.end]
269 return n
270 }
271
272 func IsPrimary(n Node) bool {
273 _, ok := n.(*Primary)
274 return ok
275 }
276
277 func GetPrimary(n Node) *Primary {
278 if nn, ok := n.(*Primary); ok {
279 return nn
280 }
281 return nil
282 }
283
284 func (n *Primary) setList(ch *Array) {
285 n.List = ch
286 addChild(n, ch)
287 }
288
289 func (n *Primary) setChunk(ch *Chunk) {
290 n.Chunk = ch
291 addChild(n, ch)
292 }
293
294 func (n *Primary) addToMapPairs(ch *MapPair) {
295 n.MapPairs = append(n.MapPairs, ch)
296 addChild(n, ch)
297 }
298
299 func (n *Primary) addToBraced(ch *Compound) {
300 n.Braced = append(n.Braced, ch)
301 addChild(n, ch)
302 }
303
304 func ParsePrimary(ps *Parser, head bool) *Primary {
305 n := &Primary{node: node{begin: ps.pos}}
306 n.parse(ps, head)
307 n.end = ps.pos
308 n.sourceText = ps.src[n.begin:n.end]
309 return n
310 }
311
312 func IsMapPair(n Node) bool {
313 _, ok := n.(*MapPair)
314 return ok
315 }
316
317 func GetMapPair(n Node) *MapPair {
318 if nn, ok := n.(*MapPair); ok {
319 return nn
320 }
321 return nil
322 }
323
324 func (n *MapPair) setKey(ch *Compound) {
325 n.Key = ch
326 addChild(n, ch)
327 }
328
329 func (n *MapPair) setValue(ch *Compound) {
330 n.Value = ch
331 addChild(n, ch)
332 }
333
334 func ParseMapPair(ps *Parser) *MapPair {
335 n := &MapPair{node: node{begin: ps.pos}}
336 n.parse(ps)
337 n.end = ps.pos
338 n.sourceText = ps.src[n.begin:n.end]
339 return n
340 }
341
342 func IsSep(n Node) bool {
343 _, ok := n.(*Sep)
344 return ok
345 }
346
347 func GetSep(n Node) *Sep {
348 if nn, ok := n.(*Sep); ok {
349 return nn
350 }
351 return nil
352 }
0 #!/usr/bin/python2.7
1 """
2 Generate helper functions for node types.
3
4 For every node type T, it generates the following:
5
6 * A IsT func that determines whether a Node is actually of type *T.
7
8 * A GetT func that takes Node and returns *T. It examines whether the Node is
9 actually of type *T, and if it is, returns it; otherwise it returns nil.
10
11 * For each field F of type *[]U, it generates a addToF method that appends a
12 node to this field and adds it to the children list.
13
14 * For each field F of type *U where U is not a slice, it generates a setF
15 method that sets this field and adds it to the children list.
16
17 * If the type has a parse method that takes a *paser, it genertes a parseT
18 func that takes a *Parser and returns *T. The func creates a new instance of
19 *T, sets its begin field, calls its parse method, and set its end and
20 sourceText fields.
21
22 For example, for the following type:
23
24 type X struct {
25 node
26 F *Y
27 G *[]Z
28 }
29
30 The following boilerplate is generated:
31
32 func IsX(n Node) bool {
33 _, ok := n.(*X)
34 return ok
35 }
36
37 func GetX(n Node) *X {
38 if nn, ok := n.(*X); ok {
39 return nn
40 }
41 return nil
42 }
43
44 func (n *X) setF(ch *Y) {
45 n.F = ch
46 addChild(n, ch)
47 }
48
49 func (n *X) addToG(ch *Z) {
50 n.G = append(n.G, ch)
51 addChild(n, ch)
52 }
53
54 func ParseX(ps *Parser) *X {
55 n := &X{node: node{begin: ps.pos}}
56 n.parse(ps)
57 n.end = ps.pos
58 n.sourceText = ps.src[n.begin:n.end]
59 return n
60 }
61 """
62 import re
63 import os
64
65
66 def put_is(out, typename):
67 print >>out, '''
68 func Is{typename}(n Node) bool {{
69 _, ok := n.(*{typename})
70 return ok
71 }}
72 '''.format(typename=typename)
73
74
75 def put_get(out, typename):
76 print >>out, '''
77 func Get{typename}(n Node) *{typename} {{
78 if nn, ok := n.(*{typename}); ok {{
79 return nn
80 }}
81 return nil
82 }}
83 '''.format(typename=typename)
84
85
86 def put_set(out, parent, field, child):
87 print >>out, '''
88 func (n *{parent}) set{field}(ch *{child}) {{
89 n.{field} = ch
90 addChild(n, ch)
91 }}'''.format(parent=parent, field=field, child=child)
92
93
94 def put_addto(out, parent, field, child):
95 print >>out, '''
96 func (n *{parent}) addTo{field}(ch *{child}) {{
97 n.{field} = append(n.{field}, ch)
98 addChild(n, ch)
99 }}'''.format(parent=parent, field=field, child=child)
100
101
102 def put_parse(out, typename, extraargs):
103 extranames = ', '.join(a.split(' ')[0] for a in extraargs.split(', ')) if extraargs else ''
104 print >>out, '''
105 func Parse{typename}(ps *Parser{extraargs}) *{typename} {{
106 n := &{typename}{{node: node{{begin: ps.pos}}}}
107 n.parse(ps{extranames})
108 n.end = ps.pos
109 n.sourceText = ps.src[n.begin:n.end]
110 return n
111 }}'''.format(typename=typename, extraargs=extraargs, extranames=extranames)
112
113
114 def main():
115 types = []
116 in_type = ''
117 out = open('boilerplate.go', 'w')
118 print >>out, 'package parse'
119 for line in file('parse.go'):
120 if in_type:
121 if line == '}\n':
122 in_type = ''
123 continue
124 m = re.match(r'^\t(\w+(?:, \w+)*) +(\S+)', line)
125 if m:
126 fields = m.group(1).split(', ')
127 typename = m.group(2)
128 if typename.startswith('*'):
129 # Single child
130 [put_set(out, in_type, f, typename[1:]) for f in fields]
131 elif typename.startswith('[]*'):
132 # Children list
133 [put_addto(out, in_type, f, typename[3:]) for f in fields]
134 continue
135 m = re.match(r'^type (.*) struct', line)
136 if m:
137 in_type = m.group(1)
138 put_is(out, in_type)
139 put_get(out, in_type)
140 continue
141 m = re.match(
142 r'^func \(.* \*(.*)\) parse\(ps \*Parser(.*?)\) {$', line)
143 if m:
144 typename, extraargs = m.groups()
145 put_parse(out, typename, extraargs)
146 out.close()
147 os.system('gofmt -w boilerplate.go')
148
149
150 if __name__ == '__main__':
151 main()
0 package parse
1
2 import (
3 "fmt"
4 "reflect"
5 "strings"
6 "unicode"
7 "unicode/utf8"
8 )
9
10 // AST checking utilities. Used in test cases.
11
12 // ast is an AST specification. The name part identifies the type of the Node;
13 // for instance, "Chunk" specifies a Chunk. The fields part is specifies children
14 // to check; see document of fs.
15 //
16 // When a Node contains exactly one child, It can be coalesced with its child
17 // by adding "/ChildName" in the name part. For instance, "Chunk/Pipeline"
18 // specifies a Chunk that contains exactly one Pipeline. In this case, the
19 // fields part specified the children of the Pipeline instead of the Chunk
20 // (which has no additional interesting fields anyway). Multi-level coalescence
21 // like "Chunk/Pipeline/Form" is also allowed.
22 //
23 // The dynamic type of the Node being checked is assumed to be a pointer to a
24 // struct that embeds the "node" struct.
25 type ast struct {
26 name string
27 fields fs
28 }
29
30 // fs specifies fields of a Node to check. For the value of field $f in the
31 // Node ("found value"), fs[$f] ("wanted value") is used to check against it.
32 //
33 // If the key is "text", the SourceText of the Node is checked. It doesn't
34 // involve a found value.
35 //
36 // If the wanted value is nil, the found value is checked against nil.
37 //
38 // If the found value implements Node, then the wanted value must be either an
39 // ast, where the checking algorithm of ast applies, or a string, where the
40 // source text of the found value is checked.
41 //
42 // If the found value is a slice whose elements implement Node, then the wanted
43 // value must be a slice where checking is then done recursively.
44 //
45 // If the found value satisfied none of the above conditions, it is checked
46 // against the wanted value using reflect.DeepEqual.
47 type fs map[string]interface{}
48
49 // checkAST checks an AST against a specification.
50 func checkAST(n Node, want ast) error {
51 wantnames := strings.Split(want.name, "/")
52 // Check coalesced levels
53 for i, wantname := range wantnames {
54 name := reflect.TypeOf(n).Elem().Name()
55 if wantname != name {
56 return fmt.Errorf("want %s, got %s (%s)", wantname, name, summary(n))
57 }
58 if i == len(wantnames)-1 {
59 break
60 }
61 fields := n.Children()
62 if len(fields) != 1 {
63 return fmt.Errorf("want exactly 1 child, got %d (%s)", len(fields), summary(n))
64 }
65 n = fields[0]
66 }
67
68 ntype := reflect.TypeOf(n).Elem()
69 nvalue := reflect.ValueOf(n).Elem()
70
71 for i := 0; i < ntype.NumField(); i++ {
72 fieldname := ntype.Field(i).Name
73 if !exported(fieldname) {
74 // Unexported field
75 continue
76 }
77 got := nvalue.Field(i).Interface()
78 want, ok := want.fields[fieldname]
79 if ok {
80 err := checkField(got, want, "field "+fieldname+" of: "+summary(n))
81 if err != nil {
82 return err
83 }
84 } else {
85 // Not specified. Check if got is a zero value of its type.
86 if !reflect.DeepEqual(got, reflect.Zero(reflect.TypeOf(got)).Interface()) {
87 return fmt.Errorf("want zero, got %v (field %s of: %s)", got, fieldname, summary(n))
88 }
89 }
90 }
91
92 return nil
93 }
94
95 var nodeType = reflect.TypeOf((*Node)(nil)).Elem()
96
97 // checkField checks a field against a field specification.
98 func checkField(got interface{}, want interface{}, ctx string) error {
99 // Want nil.
100 if want == nil {
101 if !reflect.ValueOf(got).IsNil() {
102 return fmt.Errorf("want nil, got %v (%s)", got, ctx)
103 }
104 return nil
105 }
106
107 if got, ok := got.(Node); ok {
108 // Got a Node.
109 return checkNodeInField(got.(Node), want)
110 }
111 tgot := reflect.TypeOf(got)
112 if tgot.Kind() == reflect.Slice && tgot.Elem().Implements(nodeType) {
113 // Got a slice of Nodes.
114 vgot := reflect.ValueOf(got)
115 vwant := reflect.ValueOf(want)
116 if vgot.Len() != vwant.Len() {
117 return fmt.Errorf("want %d, got %d (%s)", vwant.Len(), vgot.Len(), ctx)
118 }
119 for i := 0; i < vgot.Len(); i++ {
120 err := checkNodeInField(vgot.Index(i).Interface().(Node),
121 vwant.Index(i).Interface())
122 if err != nil {
123 return err
124 }
125 }
126 return nil
127 }
128
129 if !reflect.DeepEqual(want, got) {
130 return fmt.Errorf("want %v, got %v (%s)", want, got, ctx)
131 }
132 return nil
133 }
134
135 func checkNodeInField(got Node, want interface{}) error {
136 switch want := want.(type) {
137 case string:
138 text := got.SourceText()
139 if want != text {
140 return fmt.Errorf("want %q, got %q (%s)", want, text, summary(got))
141 }
142 return nil
143 case ast:
144 return checkAST(got, want)
145 default:
146 panic(fmt.Sprintf("bad want type %T (%s)", want, summary(got)))
147 }
148 }
149
150 func exported(name string) bool {
151 r, _ := utf8.DecodeRuneInString(name)
152 return unicode.IsUpper(r)
153 }
0 package parse
1
2 import (
3 "bytes"
4 "fmt"
5
6 "github.com/elves/elvish/util"
7 )
8
9 // ErrorEntry represents one parse error.
10 type ErrorEntry struct {
11 Message string
12 Context util.SourceContext
13 }
14
15 // Error stores multiple ErrorEntry's and can pretty print them.
16 type Error struct {
17 Entries []*ErrorEntry
18 }
19
20 func (pe *Error) Add(msg string, ctx util.SourceContext) {
21 pe.Entries = append(pe.Entries, &ErrorEntry{msg, ctx})
22 }
23
24 func (pe *Error) Error() string {
25 switch len(pe.Entries) {
26 case 0:
27 return "no parse error"
28 case 1:
29 e := pe.Entries[0]
30 return fmt.Sprintf("parse error: %d-%d in %s: %s",
31 e.Context.Begin, e.Context.End, e.Context.Name, e.Message)
32 default:
33 buf := new(bytes.Buffer)
34 // Contexts of parse error entries all have the same name
35 fmt.Fprintf(buf, "multiple parse errors in %s: ", pe.Entries[0].Context.Name)
36 for i, e := range pe.Entries {
37 if i > 0 {
38 fmt.Fprint(buf, "; ")
39 }
40 fmt.Fprintf(buf, "%d-%d: %s", e.Context.Begin, e.Context.End, e.Message)
41 }
42 return buf.String()
43 }
44 }
45
46 func (pe *Error) Pprint(indent string) string {
47 buf := new(bytes.Buffer)
48
49 switch len(pe.Entries) {
50 case 0:
51 return "no parse error"
52 case 1:
53 e := pe.Entries[0]
54 fmt.Fprintf(buf, "Parse error: \033[31;1m%s\033[m\n", e.Message)
55 buf.WriteString(indent + " ")
56 e.Context.Pprint(buf, indent+" ")
57 default:
58 fmt.Fprint(buf, "Multiple parse errors:")
59 for _, e := range pe.Entries {
60 buf.WriteString("\n" + indent + " ")
61 fmt.Fprintf(buf, "\033[31;1m%s\033[m\n", e.Message)
62 buf.WriteString(indent + " ")
63 e.Context.Pprint(buf, indent+" ")
64 }
65 }
66
67 return buf.String()
68 }
0 package parse
1
2 // Node represents a parse tree as well as an AST.
3 type Node interface {
4 n() *node
5 Parent() Node
6 Begin() int
7 End() int
8 SourceText() string
9 Children() []Node
10 }
11
12 type node struct {
13 parent Node
14 begin, end int
15 sourceText string
16 children []Node
17 }
18
19 func (n *node) n() *node {
20 return n
21 }
22
23 func (n *node) Parent() Node {
24 return n.parent
25 }
26
27 func (n *node) Begin() int {
28 return n.begin
29 }
30
31 func (n *node) End() int {
32 return n.end
33 }
34
35 func (n *node) SourceText() string {
36 return n.sourceText
37 }
38
39 func (n *node) Children() []Node {
40 return n.children
41 }
0 // Package parse implements the elvish parser.
1 package parse
2
3 //go:generate ./boilerplate.py
4 //go:generate stringer -type=PrimaryType,RedirMode -output=string.go
5
6 import (
7 "bytes"
8 "errors"
9 "fmt"
10 "unicode"
11 )
12
13 // Parse parses Elvish source. If the error is not nil, it always has type
14 // ParseError.
15 func Parse(srcname, src string) (*Chunk, error) {
16 ps := NewParser(srcname, src)
17 n := ParseChunk(ps)
18 ps.Done()
19 return n, ps.Errors()
20 }
21
22 // Errors.
23 var (
24 errUnexpectedRune = errors.New("unexpected rune")
25 errShouldBeForm = newError("", "form")
26 errBadLHS = errors.New("bad assignment LHS")
27 errDuplicateExitusRedir = newError("duplicate exitus redir")
28 errShouldBeThen = newError("", "then")
29 errShouldBeElifOrElseOrFi = newError("", "elif", "else", "fi")
30 errShouldBeFi = newError("", "fi")
31 errShouldBeTried = newError("", "tried")
32 errShouldBeDo = newError("", "do")
33 errShouldBeDone = newError("", "done")
34 errShouldBeIn = newError("", "in")
35 errShouldBePipelineSep = newError("", "';'", "newline")
36 errShouldBeEnd = newError("", "end")
37 errBadRedirSign = newError("bad redir sign", "'<'", "'>'", "'>>'", "'<>'")
38 errShouldBeFD = newError("", "a composite term representing fd")
39 errShouldBeFilename = newError("", "a composite term representing filename")
40 errShouldBeArray = newError("", "spaced")
41 errStringUnterminated = newError("string not terminated")
42 errInvalidEscape = newError("invalid escape sequence")
43 errInvalidEscapeOct = newError("invalid escape sequence", "octal digit")
44 errInvalidEscapeHex = newError("invalid escape sequence", "hex digit")
45 errInvalidEscapeControl = newError("invalid control sequence", "a rune between @ (0x40) and _(0x5F)")
46 errShouldBePrimary = newError("",
47 "single-quoted string", "double-quoted string", "bareword")
48 errShouldBeVariableName = newError("", "variable name")
49 errShouldBeRBracket = newError("", "']'")
50 errShouldBeRBrace = newError("", "'}'")
51 errShouldBeBraceSepOrRBracket = newError("", "','", "'}'")
52 errShouldBeRParen = newError("", "')'")
53 errShouldBeBackquoteOrLParen = newError("", "'`'", "'('")
54 errShouldBeBackquote = newError("", "'`'")
55 errShouldBeCompound = newError("", "compound")
56 errShouldBeEqual = newError("", "'='")
57 errArgListAllowNoSemicolon = newError("argument list doesn't allow semicolons")
58 )
59
60 // Chunk = { PipelineSep | Space } { Pipeline { PipelineSep | Space } }
61 type Chunk struct {
62 node
63 Pipelines []*Pipeline
64 }
65
66 func (bn *Chunk) parse(ps *Parser) {
67 bn.parseSeps(ps)
68 for startsPipeline(ps.peek()) {
69 bn.addToPipelines(ParsePipeline(ps))
70 if bn.parseSeps(ps) == 0 {
71 break
72 }
73 }
74 }
75
76 func isPipelineSep(r rune) bool {
77 return r == '\n' || r == ';'
78 }
79
80 // parseSeps parses pipeline separators along with whitespaces. It returns the
81 // number of pipeline separators parsed.
82 func (bn *Chunk) parseSeps(ps *Parser) int {
83 nseps := 0
84 for {
85 r := ps.peek()
86 if isPipelineSep(r) {
87 // parse as a Sep
88 parseSep(bn, ps, r)
89 nseps++
90 } else if IsSpace(r) {
91 // parse a run of spaces as a Sep
92 parseSpaces(bn, ps)
93 } else if r == '#' {
94 // parse a comment as a Sep
95 for {
96 r := ps.peek()
97 if r == eof || r == '\n' {
98 break
99 }
100 ps.next()
101 }
102 addSep(bn, ps)
103 nseps++
104 } else {
105 break
106 }
107 }
108 return nseps
109 }
110
111 // Pipeline = Form { '|' Form }
112 type Pipeline struct {
113 node
114 Forms []*Form
115 Background bool
116 }
117
118 func (pn *Pipeline) parse(ps *Parser) {
119 pn.addToForms(ParseForm(ps))
120 for parseSep(pn, ps, '|') {
121 parseSpacesAndNewlines(pn, ps)
122 if !startsForm(ps.peek()) {
123 ps.error(errShouldBeForm)
124 return
125 }
126 pn.addToForms(ParseForm(ps))
127 }
128 parseSpaces(pn, ps)
129 if ps.peek() == '&' {
130 ps.next()
131 addSep(pn, ps)
132 pn.Background = true
133 parseSpaces(pn, ps)
134 }
135 }
136
137 func startsPipeline(r rune) bool {
138 return startsForm(r)
139 }
140
141 // Form = { Space } { { Assignment } { Space } }
142 // { Compound } { Space } { ( Compound | MapPair | Redir | ExitusRedir ) { Space } }
143 type Form struct {
144 node
145 Assignments []*Assignment
146 Head *Compound
147 // Left-hand-sides for the spacey assignment. Right-hand-sides are in Args.
148 Vars []*Compound
149 Args []*Compound
150 Opts []*MapPair
151 Redirs []*Redir
152 ExitusRedir *ExitusRedir
153 }
154
155 func (fn *Form) parse(ps *Parser) {
156 parseSpaces(fn, ps)
157 for fn.tryAssignment(ps) {
158 parseSpaces(fn, ps)
159 }
160
161 // Parse head.
162 if !startsCompound(ps.peek(), true) {
163 if len(fn.Assignments) > 0 {
164 // Assignment-only form.
165 return
166 }
167 // Bad form.
168 ps.error(fmt.Errorf("bad rune at form head: %q", ps.peek()))
169 }
170 fn.setHead(ParseCompound(ps, true))
171 parseSpaces(fn, ps)
172
173 for {
174 r := ps.peek()
175 switch {
176 case r == '&':
177 ps.next()
178 hasMapPair := startsCompound(ps.peek(), false)
179 ps.backup()
180 if !hasMapPair {
181 // background indicator
182 return
183 }
184 fn.addToOpts(ParseMapPair(ps))
185 case startsCompound(r, false):
186 if ps.hasPrefix("?>") {
187 if fn.ExitusRedir != nil {
188 ps.error(errDuplicateExitusRedir)
189 // Parse the duplicate redir anyway.
190 addChild(fn, ParseExitusRedir(ps))
191 } else {
192 fn.setExitusRedir(ParseExitusRedir(ps))
193 }
194 continue
195 }
196 cn := ParseCompound(ps, false)
197 if isRedirSign(ps.peek()) {
198 // Redir
199 fn.addToRedirs(ParseRedir(ps, cn))
200 } else if cn.sourceText == "=" {
201 // Spacey assignment.
202 // Turn the equal sign into a Sep.
203 addChild(fn, NewSep(ps.src, cn.begin, cn.end))
204 // Turn the head and preceding arguments into LHSs.
205 addLHS := func(cn *Compound) {
206 if len(cn.Indexings) == 1 && checkVariableInAssignment(cn.Indexings[0].Head, ps) {
207 fn.Vars = append(fn.Vars, cn)
208 } else {
209 ps.errorp(cn.begin, cn.end, errBadLHS)
210 }
211 }
212 addLHS(fn.Head)
213 fn.Head = nil
214 for _, cn := range fn.Args {
215 addLHS(cn)
216 }
217 fn.Args = nil
218 } else {
219 fn.addToArgs(cn)
220 }
221 case isRedirSign(r):
222 fn.addToRedirs(ParseRedir(ps, nil))
223 default:
224 return
225 }
226 parseSpaces(fn, ps)
227 }
228 }
229
230 // tryAssignment tries to parse an assignment. If succeeded, it adds the parsed
231 // assignment to fn.Assignments and returns true. Otherwise it rewinds the
232 // parser and returns false.
233 func (fn *Form) tryAssignment(ps *Parser) bool {
234 if !startsIndexing(ps.peek(), false) || ps.peek() == '=' {
235 return false
236 }
237
238 pos := ps.pos
239 errorEntries := ps.errors.Entries
240 an := ParseAssignment(ps)
241 // If errors were added, revert
242 if len(ps.errors.Entries) > len(errorEntries) {
243 ps.errors.Entries = errorEntries
244 ps.pos = pos
245 return false
246 }
247 fn.addToAssignments(an)
248 return true
249 }
250
251 func startsForm(r rune) bool {
252 return IsSpace(r) || startsCompound(r, true)
253 }
254
255 // Assignment = Indexing '=' Compound
256 type Assignment struct {
257 node
258 Left *Indexing
259 Right *Compound
260 }
261
262 func (an *Assignment) parse(ps *Parser) {
263 ps.cut('=')
264 an.setLeft(ParseIndexing(ps, false))
265 head := an.Left.Head
266 if !checkVariableInAssignment(head, ps) {
267 ps.errorp(head.Begin(), head.End(), errShouldBeVariableName)
268 }
269 ps.uncut('=')
270
271 if !parseSep(an, ps, '=') {
272 ps.error(errShouldBeEqual)
273 }
274 an.setRight(ParseCompound(ps, false))
275 }
276
277 func checkVariableInAssignment(p *Primary, ps *Parser) bool {
278 if p.Type == Braced {
279 // XXX don't check further inside braced expression
280 return true
281 }
282 if p.Type != Bareword && p.Type != SingleQuoted && p.Type != DoubleQuoted {
283 return false
284 }
285 if p.Value == "" {
286 return false
287 }
288 for _, r := range p.Value {
289 // XXX special case '&' and '@'.
290 if !allowedInVariableName(r) && r != '&' && r != '@' {
291 return false
292 }
293 }
294 return true
295 }
296
297 // ExitusRedir = '?' '>' { Space } Compound
298 type ExitusRedir struct {
299 node
300 Dest *Compound
301 }
302
303 func (ern *ExitusRedir) parse(ps *Parser) {
304 ps.next()
305 ps.next()
306 addSep(ern, ps)
307 parseSpaces(ern, ps)
308 ern.setDest(ParseCompound(ps, false))
309 }
310
311 // Redir = { Compound } { '<'|'>'|'<>'|'>>' } { Space } ( '&'? Compound )
312 type Redir struct {
313 node
314 Left *Compound
315 Mode RedirMode
316 RightIsFd bool
317 Right *Compound
318 }
319
320 func (rn *Redir) parse(ps *Parser, dest *Compound) {
321 // The parsing of the Left part is done in Form.parse.
322 if dest != nil {
323 rn.setLeft(dest)
324 rn.begin = dest.begin
325 }
326
327 begin := ps.pos
328 for isRedirSign(ps.peek()) {
329 ps.next()
330 }
331 sign := ps.src[begin:ps.pos]
332 switch sign {
333 case "<":
334 rn.Mode = Read
335 case ">":
336 rn.Mode = Write
337 case ">>":
338 rn.Mode = Append
339 case "<>":
340 rn.Mode = ReadWrite
341 default:
342 ps.error(errBadRedirSign)
343 }
344 addSep(rn, ps)
345 parseSpaces(rn, ps)
346 if parseSep(rn, ps, '&') {
347 rn.RightIsFd = true
348 }
349 rn.setRight(ParseCompound(ps, false))
350 if len(rn.Right.Indexings) == 0 {
351 if rn.RightIsFd {
352 ps.error(errShouldBeFD)
353 } else {
354 ps.error(errShouldBeFilename)
355 }
356 return
357 }
358 }
359
360 func isRedirSign(r rune) bool {
361 return r == '<' || r == '>'
362 }
363
364 // RedirMode records the mode of an IO redirection.
365 type RedirMode int
366
367 // Possible values for RedirMode.
368 const (
369 BadRedirMode RedirMode = iota
370 Read
371 Write
372 ReadWrite
373 Append
374 )
375
376 // Compound = { Indexing }
377 type Compound struct {
378 node
379 Indexings []*Indexing
380 }
381
382 func (cn *Compound) parse(ps *Parser, head bool) {
383 cn.tilde(ps)
384 for startsIndexing(ps.peek(), head) {
385 cn.addToIndexings(ParseIndexing(ps, head))
386 }
387 }
388
389 // tilde parses a tilde if there is one. It is implemented here instead of
390 // within Primary since a tilde can only appear as the first part of a
391 // Compound. Elsewhere tildes are barewords.
392 func (cn *Compound) tilde(ps *Parser) {
393 if ps.peek() == '~' {
394 ps.next()
395 base := node{nil, ps.pos - 1, ps.pos, "~", nil}
396 pn := &Primary{node: base, Type: Tilde, Value: "~"}
397 in := &Indexing{node: base}
398 in.setHead(pn)
399 cn.addToIndexings(in)
400 }
401 }
402
403 func startsCompound(r rune, head bool) bool {
404 return startsIndexing(r, head)
405 }
406
407 // Indexing = Primary { '[' Array ']' }
408 type Indexing struct {
409 node
410 Head *Primary
411 Indicies []*Array
412 }
413
414 func (in *Indexing) parse(ps *Parser, head bool) {
415 in.setHead(ParsePrimary(ps, head))
416 for parseSep(in, ps, '[') {
417 if !startsArray(ps.peek()) {
418 ps.error(errShouldBeArray)
419 }
420
421 ps.pushCutset()
422 in.addToIndicies(ParseArray(ps, false))
423 ps.popCutset()
424
425 if !parseSep(in, ps, ']') {
426 ps.error(errShouldBeRBracket)
427 return
428 }
429 }
430 }
431
432 func startsIndexing(r rune, head bool) bool {
433 return startsPrimary(r, head)
434 }
435
436 // Array = { Space | '\n' } { Compound { Space | '\n' } }
437 type Array struct {
438 node
439 Compounds []*Compound
440 // When non-empty, records the occurrences of semicolons by the indices of
441 // the compounds they appear before. For instance, [; ; a b; c d;] results
442 // in Semicolons={0 0 2 4}.
443 Semicolons []int
444 }
445
446 func (sn *Array) parse(ps *Parser, allowSemicolon bool) {
447 parseSep := func() {
448 parseSpacesAndNewlines(sn, ps)
449 if allowSemicolon {
450 for parseSep(sn, ps, ';') {
451 sn.Semicolons = append(sn.Semicolons, len(sn.Compounds))
452 }
453 parseSpacesAndNewlines(sn, ps)
454 }
455 }
456
457 parseSep()
458 for startsCompound(ps.peek(), false) {
459 sn.addToCompounds(ParseCompound(ps, false))
460 parseSep()
461 }
462 }
463
464 func IsSpace(r rune) bool {
465 return r == ' ' || r == '\t'
466 }
467
468 func startsArray(r rune) bool {
469 return IsSpaceOrNewline(r) || startsIndexing(r, false)
470 }
471
472 // Primary is the smallest expression unit.
473 type Primary struct {
474 node
475 Type PrimaryType
476 // The unquoted string value. Valid for Bareword, SingleQuoted,
477 // DoubleQuoted, Variable, Wildcard and Tilde.
478 Value string
479 List *Array // Valid for List and Lambda
480 Chunk *Chunk // Valid for OutputCapture, ExitusCapture and Lambda
481 MapPairs []*MapPair // Valid for Map
482 Braced []*Compound // Valid for Braced
483 IsRange []bool // Valid for Braced
484 }
485
486 // PrimaryType is the type of a Primary.
487 type PrimaryType int
488
489 // Possible values for PrimaryType.
490 const (
491 BadPrimary PrimaryType = iota
492 Bareword
493 SingleQuoted
494 DoubleQuoted
495 Variable
496 Wildcard
497 Tilde
498 ExceptionCapture
499 OutputCapture
500 List
501 Lambda
502 Map
503 Braced
504 )
505
506 func (pn *Primary) parse(ps *Parser, head bool) {
507 r := ps.peek()
508 if !startsPrimary(r, head) {
509 ps.error(errShouldBePrimary)
510 return
511 }
512
513 // Try bareword early, since it has precedence over wildcard on *
514 // when head is true.
515 if allowedInBareword(r, head) {
516 pn.bareword(ps, head)
517 return
518 }
519
520 switch r {
521 case '\'':
522 pn.singleQuoted(ps)
523 case '"':
524 pn.doubleQuoted(ps)
525 case '$':
526 pn.variable(ps)
527 case '*':
528 pn.wildcard(ps)
529 case '?':
530 if ps.hasPrefix("?(") {
531 pn.exitusCapture(ps)
532 } else {
533 pn.wildcard(ps)
534 }
535 case '(', '`':
536 pn.outputCapture(ps)
537 case '[':
538 pn.lbracket(ps)
539 case '{':
540 pn.lbrace(ps)
541 default:
542 // Parse an empty bareword.
543 pn.Type = Bareword
544 }
545 }
546
547 func (pn *Primary) singleQuoted(ps *Parser) {
548 pn.Type = SingleQuoted
549 ps.next()
550 var buf bytes.Buffer
551 defer func() { pn.Value = buf.String() }()
552 for {
553 switch r := ps.next(); r {
554 case eof:
555 ps.error(errStringUnterminated)
556 return
557 case '\'':
558 if ps.peek() == '\'' {
559 // Two consecutive single quotes
560 ps.next()
561 buf.WriteByte('\'')
562 } else {
563 // End of string
564 return
565 }
566 default:
567 buf.WriteRune(r)
568 }
569 }
570 }
571
572 func (pn *Primary) doubleQuoted(ps *Parser) {
573 pn.Type = DoubleQuoted
574 ps.next()
575 var buf bytes.Buffer
576 defer func() { pn.Value = buf.String() }()
577 for {
578 switch r := ps.next(); r {
579 case eof:
580 ps.error(errStringUnterminated)
581 return
582 case '"':
583 return
584 case '\\':
585 switch r := ps.next(); r {
586 case 'c', '^':
587 // Control sequence
588 r := ps.next()
589 if r < 0x40 || r >= 0x60 {
590 ps.backup()
591 ps.error(errInvalidEscapeControl)
592 ps.next()
593 }
594 buf.WriteByte(byte(r - 0x40))
595 case 'x', 'u', 'U':
596 var n int
597 switch r {
598 case 'x':
599 n = 2
600 case 'u':
601 n = 4
602 case 'U':
603 n = 8
604 }
605 var rr rune
606 for i := 0; i < n; i++ {
607 d, ok := hexToDigit(ps.next())
608 if !ok {
609 ps.backup()
610 ps.error(errInvalidEscapeHex)
611 break
612 }
613 rr = rr*16 + d
614 }
615 buf.WriteRune(rr)
616 case '0', '1', '2', '3', '4', '5', '6', '7':
617 // 2 more octal digits
618 rr := r - '0'
619 for i := 0; i < 2; i++ {
620 r := ps.next()
621 if r < '0' || r > '7' {
622 ps.backup()
623 ps.error(errInvalidEscapeOct)
624 break
625 }
626 rr = rr*8 + (r - '0')
627 }
628 buf.WriteRune(rr)
629 default:
630 if rr, ok := doubleEscape[r]; ok {
631 buf.WriteRune(rr)
632 } else {
633 ps.backup()
634 ps.error(errInvalidEscape)
635 ps.next()
636 }
637 }
638 default:
639 buf.WriteRune(r)
640 }
641 }
642 }
643
644 // a table for the simple double-quote escape sequences.
645 var doubleEscape = map[rune]rune{
646 // same as golang
647 'a': '\a', 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r',
648 't': '\t', 'v': '\v', '\\': '\\', '"': '"',
649 // additional
650 'e': '\033',
651 }
652
653 var doubleUnescape = map[rune]rune{}
654
655 func init() {
656 for k, v := range doubleEscape {
657 doubleUnescape[v] = k
658 }
659 }
660
661 func hexToDigit(r rune) (rune, bool) {
662 switch {
663 case '0' <= r && r <= '9':
664 return r - '0', true
665 case 'a' <= r && r <= 'f':
666 return r - 'a' + 10, true
667 case 'A' <= r && r <= 'F':
668 return r - 'A' + 10, true
669 default:
670 return -1, false
671 }
672 }
673
674 func (pn *Primary) variable(ps *Parser) {
675 pn.Type = Variable
676 defer func() { pn.Value = ps.src[pn.begin+1 : ps.pos] }()
677 ps.next()
678 // The character of the variable name can be anything.
679 if ps.next() == eof {
680 ps.backup()
681 ps.error(errShouldBeVariableName)
682 ps.next()
683 }
684 for allowedInVariableName(ps.peek()) {
685 ps.next()
686 }
687 }
688
689 // The following are allowed in variable names:
690 // * Anything beyond ASCII that is printable
691 // * Letters and numbers
692 // * The symbols "-_:&"
693 func allowedInVariableName(r rune) bool {
694 return (r >= 0x80 && unicode.IsPrint(r)) ||
695 ('0' <= r && r <= '9') ||
696 ('a' <= r && r <= 'z') ||
697 ('A' <= r && r <= 'Z') ||
698 r == '-' || r == '_' || r == ':' || r == '&'
699 }
700
701 func (pn *Primary) wildcard(ps *Parser) {
702 pn.Type = Wildcard
703 for isWildcard(ps.peek()) {
704 ps.next()
705 }
706 pn.Value = ps.src[pn.begin:ps.pos]
707 }
708
709 func isWildcard(r rune) bool {
710 return r == '*' || r == '?'
711 }
712
713 func (pn *Primary) exitusCapture(ps *Parser) {
714 ps.next()
715 ps.next()
716 addSep(pn, ps)
717
718 pn.Type = ExceptionCapture
719
720 ps.pushCutset()
721 pn.setChunk(ParseChunk(ps))
722 ps.popCutset()
723
724 if !parseSep(pn, ps, ')') {
725 ps.error(errShouldBeRParen)
726 }
727 }
728
729 func (pn *Primary) outputCapture(ps *Parser) {
730 pn.Type = OutputCapture
731
732 var closer rune
733 var shouldBeCloser error
734
735 switch ps.next() {
736 case '(':
737 closer = ')'
738 shouldBeCloser = errShouldBeRParen
739 case '`':
740 closer = '`'
741 shouldBeCloser = errShouldBeBackquote
742 default:
743 ps.backup()
744 ps.error(errShouldBeBackquoteOrLParen)
745 ps.next()
746 return
747 }
748 addSep(pn, ps)
749
750 if closer == '`' {
751 ps.pushCutset(closer)
752 } else {
753 ps.pushCutset()
754 }
755 pn.setChunk(ParseChunk(ps))
756 ps.popCutset()
757
758 if !parseSep(pn, ps, closer) {
759 ps.error(shouldBeCloser)
760 }
761 }
762
763 // List = '[' { Space } Array ']'
764 // Lambda = List '{' Chunk '}'
765 // Map = '[' { Space } '&' { Space } ']'
766 // = '[' { Space } { MapPair { Space } } ']'
767
768 func (pn *Primary) lbracket(ps *Parser) {
769 parseSep(pn, ps, '[')
770 parseSpacesAndNewlines(pn, ps)
771
772 r := ps.peek()
773 ps.pushCutset()
774
775 switch {
776 case r == '&':
777 pn.Type = Map
778 // parseSep(pn, ps, '&')
779 amp := ps.pos
780 ps.next()
781 r := ps.peek()
782 switch {
783 case IsSpace(r), r == ']', r == eof:
784 // '&' { Space } ']': '&' is a sep
785 addSep(pn, ps)
786 parseSpaces(pn, ps)
787 default:
788 // { MapPair { Space } } ']': Wind back
789 ps.pos = amp
790 for ps.peek() == '&' {
791 pn.addToMapPairs(ParseMapPair(ps))
792 parseSpacesAndNewlines(pn, ps)
793 }
794 }
795 ps.popCutset()
796 if !parseSep(pn, ps, ']') {
797 ps.error(errShouldBeRBracket)
798 }
799 default:
800 pn.setList(ParseArray(ps, true))
801 ps.popCutset()
802
803 if !parseSep(pn, ps, ']') {
804 ps.error(errShouldBeRBracket)
805 }
806 if parseSep(pn, ps, '{') {
807 if len(pn.List.Semicolons) > 0 {
808 ps.errorp(pn.List.Begin(), pn.List.End(), errArgListAllowNoSemicolon)
809 }
810 pn.lambda(ps)
811 } else {
812 pn.Type = List
813 }
814 }
815 }
816
817 // lambda parses a lambda expression. The opening brace has been seen.
818 func (pn *Primary) lambda(ps *Parser) {
819 pn.Type = Lambda
820 ps.pushCutset()
821 pn.setChunk(ParseChunk(ps))
822 ps.popCutset()
823 if !parseSep(pn, ps, '}') {
824 ps.error(errShouldBeRBrace)
825 }
826 }
827
828 // Braced = '{' Compound { BracedSep Compounds } '}'
829 // BracedSep = { Space | '\n' } [ ',' ] { Space | '\n' }
830 func (pn *Primary) lbrace(ps *Parser) {
831 parseSep(pn, ps, '{')
832
833 if r := ps.peek(); r == ';' || r == '\n' || IsSpace(r) {
834 pn.lambda(ps)
835 return
836 }
837
838 pn.Type = Braced
839
840 ps.pushCutset()
841 defer ps.popCutset()
842
843 // XXX: The compound can be empty, which allows us to parse {,foo}.
844 // Allowing compounds to be empty can be fragile in other cases.
845 ps.cut(',')
846 pn.addToBraced(ParseCompound(ps, false))
847 ps.uncut(',')
848
849 for isBracedSep(ps.peek()) {
850 parseSpacesAndNewlines(pn, ps)
851 // optional, so ignore the return value
852 parseSep(pn, ps, ',')
853 parseSpacesAndNewlines(pn, ps)
854
855 ps.cut(',')
856 pn.addToBraced(ParseCompound(ps, false))
857 ps.uncut(',')
858 }
859 if !parseSep(pn, ps, '}') {
860 ps.error(errShouldBeBraceSepOrRBracket)
861 }
862 }
863
864 func isBracedSep(r rune) bool {
865 return r == ',' || IsSpaceOrNewline(r)
866 }
867
868 func (pn *Primary) bareword(ps *Parser, head bool) {
869 pn.Type = Bareword
870 defer func() { pn.Value = ps.src[pn.begin:ps.pos] }()
871 for allowedInBareword(ps.peek(), head) {
872 ps.next()
873 }
874 }
875
876 // The following are allowed in barewords:
877 // * Anything allowed in variable names, except &
878 // * The symbols "%+,./=@~!\"
879 // * The symbols "<>*^", if the bareword is in head
880 //
881 // The seemingly weird inclusion of \ is for easier path manipulation in
882 // Windows.
883 func allowedInBareword(r rune, head bool) bool {
884 return (r != '&' && allowedInVariableName(r)) ||
885 r == '%' || r == '+' || r == ',' || r == '.' ||
886 r == '/' || r == '=' || r == '@' || r == '~' || r == '!' || r == '\\' ||
887 (head && (r == '<' || r == '>' || r == '*' || r == '^'))
888 }
889
890 func startsPrimary(r rune, head bool) bool {
891 return r == '\'' || r == '"' || r == '$' || allowedInBareword(r, head) ||
892 r == '?' || r == '*' || r == '(' || r == '`' || r == '[' || r == '{'
893 }
894
895 // MapPair = '&' { Space } Compound { Space } Compound
896 type MapPair struct {
897 node
898 Key, Value *Compound
899 }
900
901 func (mpn *MapPair) parse(ps *Parser) {
902 parseSep(mpn, ps, '&')
903
904 // Parse key part, cutting on '='.
905 ps.cut('=')
906 mpn.setKey(ParseCompound(ps, false))
907 if len(mpn.Key.Indexings) == 0 {
908 ps.error(errShouldBeCompound)
909 }
910 ps.uncut('=')
911
912 if parseSep(mpn, ps, '=') {
913 parseSpacesAndNewlines(mpn, ps)
914 // Parse value part.
915 mpn.setValue(ParseCompound(ps, false))
916 // The value part can be empty.
917 }
918 }
919
920 // Sep is the catch-all node type for leaf nodes that lack internal structures
921 // and semantics, and serve solely for syntactic purposes. The parsing of
922 // separators depend on the Parent node; as such it lacks a genuine parse
923 // method.
924 type Sep struct {
925 node
926 }
927
928 func NewSep(src string, begin, end int) *Sep {
929 return &Sep{node{nil, begin, end, src[begin:end], nil}}
930 }
931
932 func addSep(n Node, ps *Parser) {
933 var begin int
934 ch := n.Children()
935 if len(ch) > 0 {
936 begin = ch[len(ch)-1].End()
937 } else {
938 begin = n.Begin()
939 }
940 addChild(n, NewSep(ps.src, begin, ps.pos))
941 }
942
943 func parseSep(n Node, ps *Parser, sep rune) bool {
944 if ps.peek() == sep {
945 ps.next()
946 addSep(n, ps)
947 return true
948 }
949 return false
950 }
951
952 func parseSpaces(n Node, ps *Parser) {
953 if !IsSpace(ps.peek()) {
954 return
955 }
956 ps.next()
957 for IsSpace(ps.peek()) {
958 ps.next()
959 }
960 addSep(n, ps)
961 }
962
963 func parseSpacesAndNewlines(n Node, ps *Parser) {
964 // TODO parse comments here.
965 if !IsSpaceOrNewline(ps.peek()) {
966 return
967 }
968 ps.next()
969 for IsSpaceOrNewline(ps.peek()) {
970 ps.next()
971 }
972 addSep(n, ps)
973 }
974
975 func IsSpaceOrNewline(r rune) bool {
976 return IsSpace(r) || r == '\n'
977 }
978
979 func addChild(p Node, ch Node) {
980 p.n().children = append(p.n().children, ch)
981 ch.n().parent = p
982 }
0 package parse
1
2 import (
3 "fmt"
4 "os"
5 "testing"
6 )
7
8 func a(c ...interface{}) ast {
9 // Shorthand used for checking Compound and levels beneath.
10 return ast{"Chunk/Pipeline/Form", fs{"Head": "a", "Args": c}}
11 }
12
13 var goodCases = []struct {
14 src string
15 ast ast
16 }{
17 // Chunk
18 // Smoke test.
19 {"a;b;c\n;d", ast{"Chunk", fs{"Pipelines": []string{"a", "b", "c", "d"}}}},
20 // Empty chunk should have Pipelines=nil.
21 {"", ast{"Chunk", fs{"Pipelines": nil}}},
22 // Superfluous newlines and semicolons should not result in empty
23 // pipelines.
24 {" ;\n\n ls \t ;\n", ast{"Chunk", fs{"Pipelines": []string{"ls \t "}}}},
25
26 // Pipeline
27 {"a|b|c|d", ast{
28 "Chunk/Pipeline", fs{"Forms": []string{"a", "b", "c", "d"}}}},
29 // Newlines are allowed after pipes.
30 {"a| \n \n b", ast{
31 "Chunk/Pipeline", fs{"Forms": []string{"a", "b"}}}},
32 // Comments.
33 {"a#haha\nb#lala", ast{
34 "Chunk", fs{"Pipelines": []string{"a", "b"}}}},
35
36 // Form
37 // Smoke test.
38 {"ls x y", ast{"Chunk/Pipeline/Form", fs{
39 "Head": "ls",
40 "Args": []string{"x", "y"}}}},
41 // Assignments.
42 {"k=v k[a][b]=v {a,b[1]}=(ha)", ast{"Chunk/Pipeline/Form", fs{
43 "Assignments": []string{"k=v", "k[a][b]=v", "{a,b[1]}=(ha)"}}}},
44 // Temporary assignment.
45 {"k=v k[a][b]=v a", ast{"Chunk/Pipeline/Form", fs{
46 "Assignments": []string{"k=v", "k[a][b]=v"},
47 "Head": "a"}}},
48 // Spacey assignment.
49 {"k=v a b = c d", ast{"Chunk/Pipeline/Form", fs{
50 "Assignments": []string{"k=v"},
51 "Vars": []string{"a", "b"},
52 "Args": []string{"c", "d"}}}},
53 // Redirections
54 {"a >b", ast{"Chunk/Pipeline/Form", fs{
55 "Head": "a",
56 "Redirs": []ast{
57 {"Redir", fs{"Mode": Write, "Right": "b"}}},
58 }}},
59 // More redirections
60 {"a >>b 2>b 3>&- 4>&1 5<c 6<>d", ast{"Chunk/Pipeline/Form", fs{
61 "Head": "a",
62 "Redirs": []ast{
63 {"Redir", fs{"Mode": Append, "Right": "b"}},
64 {"Redir", fs{"Left": "2", "Mode": Write, "Right": "b"}},
65 {"Redir", fs{"Left": "3", "Mode": Write, "RightIsFd": true, "Right": "-"}},
66 {"Redir", fs{"Left": "4", "Mode": Write, "RightIsFd": true, "Right": "1"}},
67 {"Redir", fs{"Left": "5", "Mode": Read, "Right": "c"}},
68 {"Redir", fs{"Left": "6", "Mode": ReadWrite, "Right": "d"}},
69 },
70 }}},
71 // Exitus redirection
72 {"a ?>$e", ast{"Chunk/Pipeline/Form", fs{
73 "Head": "a",
74 "ExitusRedir": ast{"ExitusRedir", fs{"Dest": "$e"}},
75 }}},
76 // Options (structure of MapPair tested below with map)
77 {"a &a=1 x &b=2", ast{"Chunk/Pipeline/Form", fs{
78 "Head": "a",
79 "Args": []string{"x"},
80 "Opts": []string{"&a=1", "&b=2"},
81 }}},
82
83 // Compound
84 {`a b"foo"?$c*'xyz'`, a(ast{"Compound", fs{
85 "Indexings": []string{"b", `"foo"`, "?", "$c", "*", "'xyz'"}}})},
86
87 // Indexing
88 {"a $b[c][d][\ne\n]", a(ast{"Compound/Indexing", fs{
89 "Head": "$b", "Indicies": []string{"c", "d", "\ne\n"},
90 }})},
91
92 // Primary
93 //
94 // Single quote
95 {"a '''x''y'''", a(ast{"Compound/Indexing/Primary", fs{
96 "Type": SingleQuoted, "Value": "'x'y'",
97 }})},
98 // Double quote
99 {`a "b\^[\x1b\u548c\U0002CE23\123\n\t\\"`,
100 a(ast{"Compound/Indexing/Primary", fs{
101 "Type": DoubleQuoted,
102 "Value": "b\x1b\x1b\u548c\U0002CE23\123\n\t\\",
103 }})},
104 // Wildcard
105 {"a * ?", a(
106 ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "*"}},
107 ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "?"}},
108 )},
109 // Variable
110 {"a $x $&f", a(
111 ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "x"}},
112 ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "&f"}},
113 )},
114 // List
115 {"a [] [ ] [1] [ 2] [3 ] [\n 4 \n5\n 6 7 \n]", a(
116 ast{"Compound/Indexing/Primary", fs{
117 "Type": List,
118 "List": ""}},
119 ast{"Compound/Indexing/Primary", fs{
120 "Type": List,
121 "List": ""}},
122 ast{"Compound/Indexing/Primary", fs{
123 "Type": List,
124 "List": ast{"Array", fs{"Compounds": []string{"1"}}}}},
125 ast{"Compound/Indexing/Primary", fs{
126 "Type": List,
127 "List": ast{"Array", fs{"Compounds": []string{"2"}}}}},
128 ast{"Compound/Indexing/Primary", fs{
129 "Type": List,
130 "List": ast{"Array", fs{"Compounds": []string{"3"}}}}},
131 ast{"Compound/Indexing/Primary", fs{
132 "Type": List,
133 "List": ast{"Array", fs{
134 "Compounds": []string{"4", "5", "6", "7"}}}}},
135 )},
136 // Semicolons in lists
137 {"a [a b;c;d;]", a(
138 ast{"Compound/Indexing/Primary", fs{
139 "Type": List,
140 "List": ast{"Array", fs{
141 "Compounds": []string{"a", "b", "c", "d"},
142 "Semicolons": []int{2, 3, 4}}}}},
143 )},
144 // Map
145 {"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(
146 ast{"Compound/Indexing/Primary", fs{
147 "Type": Map,
148 "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
149 ast{"Compound/Indexing/Primary", fs{
150 "Type": Map,
151 "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
152 ast{"Compound/Indexing/Primary", fs{
153 "Type": Map,
154 "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
155 ast{"Compound/Indexing/Primary", fs{
156 "Type": Map,
157 "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
158 ast{"Compound/Indexing/Primary", fs{
159 "Type": Map,
160 "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
161 ast{"Compound/Indexing/Primary", fs{
162 "Type": Map,
163 "MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
164 ast{"Compound/Indexing/Primary", fs{
165 "Type": Map,
166 "MapPairs": []ast{
167 {"MapPair", fs{"Key": "a", "Value": "b"}},
168 {"MapPair", fs{"Key": "c", "Value": "d"}},
169 {"MapPair", fs{"Key": "e", "Value": "f"}},
170 }}},
171 )},
172 // Empty map
173 {"a [&] [ &] [& ] [ & ]", a(
174 ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
175 ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
176 ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
177 ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
178 )},
179 // Lambda
180 {"a []{} [ ]{ } []{ echo 233 } [ $x $y ]{puts $x $y} { put $1}", a(
181 ast{"Compound/Indexing/Primary", fs{
182 "Type": Lambda, "List": "", "Chunk": "",
183 }},
184 ast{"Compound/Indexing/Primary", fs{
185 "Type": Lambda, "List": "", "Chunk": " ",
186 }},
187 ast{"Compound/Indexing/Primary", fs{
188 "Type": Lambda, "List": "", "Chunk": " echo 233 ",
189 }},
190 ast{"Compound/Indexing/Primary", fs{
191 "Type": Lambda, "List": "$x $y ", "Chunk": "puts $x $y",
192 }},
193 ast{"Compound/Indexing/Primary", fs{
194 "Type": Lambda, "List": nil, "Chunk": " put $1",
195 }},
196 )},
197 // Output capture
198 {"a () (b;c) (c\nd)", a(
199 ast{"Compound/Indexing/Primary", fs{
200 "Type": OutputCapture, "Chunk": ""}},
201 ast{"Compound/Indexing/Primary", fs{
202 "Type": OutputCapture, "Chunk": ast{
203 "Chunk", fs{"Pipelines": []string{"b", "c"}},
204 }}},
205 ast{"Compound/Indexing/Primary", fs{
206 "Type": OutputCapture, "Chunk": ast{
207 "Chunk", fs{"Pipelines": []string{"c", "d"}},
208 }}},
209 )},
210 // Output capture with backquotes
211 {"a `` `b;c` `e>f`", a("``", "`b;c`", "`e>f`")},
212 // Backquotes may be nested with unclosed parens and braces
213 {"a `a (b `c`)` `d [`e`]`", a("`a (b `c`)`", "`d [`e`]`")},
214 // Exitus capture
215 {"a ?() ?(b;c)", a(
216 ast{"Compound/Indexing/Primary", fs{
217 "Type": ExceptionCapture, "Chunk": ""}},
218 ast{"Compound/Indexing/Primary", fs{
219 "Type": ExceptionCapture, "Chunk": "b;c",
220 }})},
221 // Braced
222 {"a {,a,c\ng\n}", a(
223 ast{"Compound/Indexing/Primary", fs{
224 "Type": Braced,
225 "Braced": []string{"", "a", "c", "g", ""}}})},
226 // Tilde
227 {"a ~xiaq/go", a(
228 ast{"Compound", fs{
229 "Indexings": []ast{
230 {"Indexing/Primary", fs{"Type": Tilde, "Value": "~"}},
231 {"Indexing/Primary", fs{"Type": Bareword, "Value": "xiaq/go"}},
232 },
233 }},
234 )},
235 }
236
237 func TestParse(t *testing.T) {
238 for _, tc := range goodCases {
239 bn, err := Parse("[test]", tc.src)
240 if err != nil {
241 t.Errorf("Parse(%q) returns error: %v", tc.src, err)
242 }
243 err = checkParseTree(bn)
244 if err != nil {
245 t.Errorf("Parse(%q) returns bad parse tree: %v", tc.src, err)
246 fmt.Fprintf(os.Stderr, "Parse tree of %q:\n", tc.src)
247 PprintParseTree(bn, os.Stderr)
248 }
249 err = checkAST(bn, tc.ast)
250 if err != nil {
251 t.Errorf("Parse(%q) returns bad AST: %v", tc.src, err)
252 fmt.Fprintf(os.Stderr, "AST of %q:\n", tc.src)
253 PprintAST(bn, os.Stderr)
254 }
255 }
256 }
257
258 // checkParseTree checks whether the parse tree part of a Node is well-formed.
259 func checkParseTree(n Node) error {
260 children := n.Children()
261 if len(children) == 0 {
262 return nil
263 }
264
265 // Parent pointers of all children should point to me.
266 for i, ch := range children {
267 if ch.Parent() != n {
268 return fmt.Errorf("parent of child %d (%s) is wrong: %s", i, summary(ch), summary(n))
269 }
270 }
271
272 // The Begin of the first child should be equal to mine.
273 if children[0].Begin() != n.Begin() {
274 return fmt.Errorf("gap between node and first child: %s", summary(n))
275 }
276 // The End of the last child should be equal to mine.
277 nch := len(children)
278 if children[nch-1].End() != n.End() {
279 return fmt.Errorf("gap between node and last child: %s", summary(n))
280 }
281 // Consecutive children have consecutive position ranges.
282 for i := 0; i < nch-1; i++ {
283 if children[i].End() != children[i+1].Begin() {
284 return fmt.Errorf("gap between child %d and %d of: %s", i, i+1, summary(n))
285 }
286 }
287
288 // Check children recursively.
289 for _, ch := range n.Children() {
290 err := checkParseTree(ch)
291 if err != nil {
292 return err
293 }
294 }
295 return nil
296 }
297
298 var badCases = []struct {
299 src string
300 pos int // expected Begin position of first error
301 }{
302 // Empty form.
303 {"a|", 2},
304 // Unopened parens.
305 {")", 0}, {"]", 0}, {"}", 0},
306 // Unclosed parens.
307 {"a (", 3}, {"a [", 3}, {"a {", 3},
308 // Bogus ampersand.
309 {"a & &", 4}, {"a [&", 4},
310 }
311
312 func TestParseError(t *testing.T) {
313 for _, tc := range badCases {
314 _, err := Parse("[test]", tc.src)
315 if err == nil {
316 t.Errorf("Parse(%q) returns no error", tc.src)
317 continue
318 }
319 posErr0 := err.(*Error).Entries[0]
320 if posErr0.Context.Begin != tc.pos {
321 t.Errorf("Parse(%q) first error begins at %d, want %d. Errors are:%s\n", tc.src, posErr0.Context.Begin, tc.pos, err)
322 }
323 }
324 }
0 package parse
1
2 import (
3 "bytes"
4 "errors"
5 "strings"
6 "unicode/utf8"
7
8 "github.com/elves/elvish/util"
9 )
10
11 // Parser maintains some mutable states of parsing.
12 //
13 // NOTE: The str member is assumed to be valid UF-8.
14 type Parser struct {
15 srcName string
16 src string
17 pos int
18 overEOF int
19 cutsets []map[rune]int
20 errors Error
21 }
22
23 // NewParser creates a new parser from a piece of source text and its name.
24 func NewParser(srcname, src string) *Parser {
25 return &Parser{srcname, src, 0, 0, []map[rune]int{{}}, Error{}}
26 }
27
28 // Done tells the parser that parsing has completed.
29 func (ps *Parser) Done() {
30 if ps.pos != len(ps.src) {
31 ps.error(errUnexpectedRune)
32 }
33 }
34
35 // Errors gets the parsing errors after calling one of the parse* functions. If
36 // the return value is not nil, it is always of type Error.
37 func (ps *Parser) Errors() error {
38 if len(ps.errors.Entries) > 0 {
39 return &ps.errors
40 }
41 return nil
42 }
43
44 // Source returns the source code that is being parsed.
45 func (ps *Parser) Source() string {
46 return ps.src
47 }
48
49 const eof rune = -1
50
51 func (ps *Parser) peek() rune {
52 if ps.pos == len(ps.src) {
53 return eof
54 }
55 r, _ := utf8.DecodeRuneInString(ps.src[ps.pos:])
56 if ps.currentCutset()[r] > 0 {
57 return eof
58 }
59 return r
60 }
61
62 func (ps *Parser) hasPrefix(prefix string) bool {
63 return strings.HasPrefix(ps.src[ps.pos:], prefix)
64 }
65
66 // findWord looks ahead for [a-z]* that is also a valid compound. If the
67 // lookahead fails, it returns an empty string. It is useful for looking for
68 // command leaders.
69 func (ps *Parser) findPossibleLeader() string {
70 rest := ps.src[ps.pos:]
71 i := strings.IndexFunc(rest, func(r rune) bool {
72 return r < 'a' || r > 'z'
73 })
74 if i == -1 {
75 // The whole rest is just one possible leader.
76 return rest
77 }
78 r, _ := utf8.DecodeRuneInString(rest[i:])
79 if startsPrimary(r, false) {
80 return ""
81 }
82 return rest[:i]
83 }
84
85 func (ps *Parser) next() rune {
86 if ps.pos == len(ps.src) {
87 ps.overEOF++
88 return eof
89 }
90 r, s := utf8.DecodeRuneInString(ps.src[ps.pos:])
91 if ps.currentCutset()[r] > 0 {
92 return eof
93 }
94 ps.pos += s
95 return r
96 }
97
98 func (ps *Parser) backup() {
99 if ps.overEOF > 0 {
100 ps.overEOF--
101 return
102 }
103 _, s := utf8.DecodeLastRuneInString(ps.src[:ps.pos])
104 ps.pos -= s
105 }
106
107 func (ps *Parser) advance(c int) {
108 ps.pos += c
109 if ps.pos > len(ps.src) {
110 ps.overEOF = ps.pos - len(ps.src)
111 ps.pos = len(ps.src)
112 }
113 }
114
115 func (ps *Parser) errorp(begin, end int, e error) {
116 ps.errors.Add(e.Error(), util.SourceContext{ps.srcName, ps.src, begin, end, nil})
117 }
118
119 func (ps *Parser) error(e error) {
120 end := ps.pos
121 if end < len(ps.src) {
122 end++
123 }
124 ps.errorp(ps.pos, end, e)
125 }
126
127 func (ps *Parser) pushCutset(rs ...rune) {
128 ps.cutsets = append(ps.cutsets, map[rune]int{})
129 ps.cut(rs...)
130 }
131
132 func (ps *Parser) popCutset() {
133 n := len(ps.cutsets)
134 ps.cutsets[n-1] = nil
135 ps.cutsets = ps.cutsets[:n-1]
136 }
137
138 func (ps *Parser) currentCutset() map[rune]int {
139 return ps.cutsets[len(ps.cutsets)-1]
140 }
141
142 func (ps *Parser) cut(rs ...rune) {
143 cutset := ps.currentCutset()
144 for _, r := range rs {
145 cutset[r]++
146 }
147 }
148
149 func (ps *Parser) uncut(rs ...rune) {
150 cutset := ps.currentCutset()
151 for _, r := range rs {
152 cutset[r]--
153 }
154 }
155
156 func newError(text string, shouldbe ...string) error {
157 if len(shouldbe) == 0 {
158 return errors.New(text)
159 }
160 var buf bytes.Buffer
161 if len(text) > 0 {
162 buf.WriteString(text + ", ")
163 }
164 buf.WriteString("should be " + shouldbe[0])
165 for i, opt := range shouldbe[1:] {
166 if i == len(shouldbe)-2 {
167 buf.WriteString(" or ")
168 } else {
169 buf.WriteString(", ")
170 }
171 buf.WriteString(opt)
172 }
173 return errors.New(buf.String())
174 }
0 package parse
1
2 import (
3 "fmt"
4 "io"
5 "reflect"
6 "strconv"
7 )
8
9 const (
10 maxL int = 10
11 maxR = 10
12 indentInc = 2
13 )
14
15 // PprintAST pretty prints the AST part of a Node.
16 func PprintAST(n Node, wr io.Writer) {
17 pprintAST(n, wr, 0, "")
18 }
19
20 type field struct {
21 name string
22 tag reflect.StructTag
23 value interface{}
24 }
25
26 var zeroValue reflect.Value
27
28 func pprintAST(n Node, wr io.Writer, indent int, leading string) {
29 nodeType := reflect.TypeOf((*Node)(nil)).Elem()
30
31 var childFields, childrenFields, propertyFields []field
32
33 nt := reflect.TypeOf(n).Elem()
34 nv := reflect.ValueOf(n).Elem()
35
36 for i := 0; i < nt.NumField(); i++ {
37 f := nt.Field(i)
38 if f.Anonymous {
39 // embedded node struct, skip
40 continue
41 }
42 ft := f.Type
43 fv := nv.Field(i)
44 if ft.Kind() == reflect.Slice {
45 // list of children
46 if ft.Elem().Implements(nodeType) {
47 childrenFields = append(childrenFields,
48 field{f.Name, f.Tag, fv.Interface()})
49 continue
50 }
51 } else if child, ok := fv.Interface().(Node); ok {
52 // a child node
53 if reflect.Indirect(fv) != zeroValue {
54 childFields = append(childFields,
55 field{f.Name, f.Tag, child})
56 }
57 continue
58 }
59 // a property
60 propertyFields = append(propertyFields,
61 field{f.Name, f.Tag, fv.Interface()})
62 }
63
64 // has only one child and nothing more : coalesce
65 if len(n.Children()) == 1 &&
66 n.Children()[0].SourceText() == n.SourceText() {
67 pprintAST(n.Children()[0], wr, indent, leading+nt.Name()+"/")
68 return
69 }
70 // print heading
71 //b := n.n()
72 //fmt.Fprintf(wr, "%*s%s%s %s %d-%d", indent, "",
73 // wr.leading, nt.Name(), compactQuote(b.source(src)), b.begin, b.end)
74 fmt.Fprintf(wr, "%*s%s%s", indent, "", leading, nt.Name())
75 // print properties
76 for _, pf := range propertyFields {
77 fmtstring := pf.tag.Get("fmt")
78 if len(fmtstring) > 0 {
79 fmt.Fprintf(wr, " %s="+fmtstring, pf.name, pf.value)
80 } else {
81 value := pf.value
82 if s, ok := value.(string); ok {
83 value = compactQuote(s)
84 }
85 fmt.Fprintf(wr, " %s=%v", pf.name, value)
86 }
87 }
88 fmt.Fprint(wr, "\n")
89 // print lone children recursively
90 for _, chf := range childFields {
91 // TODO the name is omitted
92 pprintAST(chf.value.(Node), wr, indent+indentInc, "")
93 }
94 // print children list recursively
95 for _, chf := range childrenFields {
96 children := reflect.ValueOf(chf.value)
97 if children.Len() == 0 {
98 continue
99 }
100 // fmt.Fprintf(wr, "%*s.%s:\n", indent, "", chf.name)
101 for i := 0; i < children.Len(); i++ {
102 n := children.Index(i).Interface().(Node)
103 pprintAST(n, wr, indent+indentInc, "")
104 }
105 }
106 }
107
108 // PprintParseTree pretty prints the parse tree part of a Node.
109 func PprintParseTree(n Node, wr io.Writer) {
110 pprintParseTree(n, wr, 0)
111 }
112
113 func pprintParseTree(n Node, wr io.Writer, indent int) {
114 leading := ""
115 for len(n.Children()) == 1 {
116 leading += reflect.TypeOf(n).Elem().Name() + "/"
117 n = n.Children()[0]
118 }
119 fmt.Fprintf(wr, "%*s%s%s\n", indent, "", leading, summary(n))
120 for _, ch := range n.Children() {
121 pprintParseTree(ch, wr, indent+indentInc)
122 }
123 }
124
125 func summary(n Node) string {
126 return fmt.Sprintf("%s %s %d-%d", reflect.TypeOf(n).Elem().Name(),
127 compactQuote(n.SourceText()), n.Begin(), n.End())
128 }
129
130 func compactQuote(text string) string {
131 if len(text) > maxL+maxR+3 {
132 text = text[0:maxL] + "..." + text[len(text)-maxR:]
133 }
134 return strconv.Quote(text)
135 }
0 package parse
1
2 import (
3 "bytes"
4 "testing"
5 )
6
7 var pprintCases = []struct {
8 src string
9 wantAST string
10 wantParseTree string
11 }{
12 {"ls $x[0]$y[1];echo",
13 `Chunk
14 Pipeline/Form
15 Compound/Indexing/Primary Type=Bareword Value="ls" IsRange=[]
16 Compound
17 Indexing
18 Primary Type=Variable Value="x" IsRange=[]
19 Array/Compound/Indexing/Primary Type=Bareword Value="0" IsRange=[]
20 Indexing
21 Primary Type=Variable Value="y" IsRange=[]
22 Array/Compound/Indexing/Primary Type=Bareword Value="1" IsRange=[]
23 Pipeline/Form/Compound/Indexing/Primary Type=Bareword Value="echo" IsRange=[]
24 `,
25 `Chunk "ls $x[0]$y[1];echo" 0-18
26 Pipeline/Form "ls $x[0]$y[1]" 0-13
27 Compound/Indexing/Primary "ls" 0-2
28 Sep " " 2-3
29 Compound "$x[0]$y[1]" 3-13
30 Indexing "$x[0]" 3-8
31 Primary "$x" 3-5
32 Sep "[" 5-6
33 Array/Compound/Indexing/Primary "0" 6-7
34 Sep "]" 7-8
35 Indexing "$y[1]" 8-13
36 Primary "$y" 8-10
37 Sep "[" 10-11
38 Array/Compound/Indexing/Primary "1" 11-12
39 Sep "]" 12-13
40 Sep ";" 13-14
41 Pipeline/Form/Compound/Indexing/Primary "echo" 14-18
42 `},
43 }
44
45 func TestPprint(t *testing.T) {
46 for _, tc := range pprintCases {
47 n, err := Parse("[test]", tc.src)
48 if err != nil {
49 t.Error(err)
50 }
51 var b bytes.Buffer
52 PprintAST(n, &b)
53 ast := b.String()
54 if b.String() != tc.wantAST {
55 t.Errorf("PprintAST(%q):\n%s\nwant:\n%s", tc.src, ast, tc.wantAST)
56 }
57 b = bytes.Buffer{}
58 PprintParseTree(n, &b)
59 pt := b.String()
60 if pt != tc.wantParseTree {
61 t.Errorf("PprintParseTree(%q):\n%s\nwant:\n%s", tc.src, pt, tc.wantParseTree)
62 }
63 }
64 }
0 package parse
1
2 import (
3 "bytes"
4 "unicode"
5 )
6
7 var QuotingStyles = []struct {
8 Type PrimaryType
9 Quoter string
10 }{
11 {SingleQuoted, "'"},
12 {DoubleQuoted, "\""},
13 }
14
15 // Quote returns a representation of s in elvish syntax. Bareword is tried
16 // first, then single quoted string and finally double quoted string.
17 func Quote(s string) string {
18 s, _ = QuoteAs(s, Bareword)
19 return s
20 }
21
22 // QuoteAs returns a representation of s in elvish syntax, using the syntax
23 // specified by q, which must be one of Bareword, SingleQuoted, or
24 // DoubleQuoted. It returns the quoted string and the actual quoting.
25 func QuoteAs(s string, q PrimaryType) (string, PrimaryType) {
26 if q == DoubleQuoted {
27 // Everything can be quoted using double quotes, return directly.
28 return quoteDouble(s), DoubleQuoted
29 }
30 if s == "" {
31 return "''", SingleQuoted
32 }
33
34 // Keep track of whether it is a valid bareword.
35 bare := s[0] != '~'
36 for _, r := range s {
37 if !unicode.IsPrint(r) {
38 // Contains unprintable character; force double quote.
39 return quoteDouble(s), DoubleQuoted
40 }
41 if !allowedInBareword(r, false) {
42 bare = false
43 }
44 }
45
46 if q == Bareword && bare {
47 return s, Bareword
48 }
49 return quoteSingle(s), SingleQuoted
50 }
51
52 func quoteSingle(s string) string {
53 var buf bytes.Buffer
54 buf.WriteByte('\'')
55 for _, r := range s {
56 buf.WriteRune(r)
57 if r == '\'' {
58 buf.WriteByte('\'')
59 }
60 }
61 buf.WriteByte('\'')
62 return buf.String()
63 }
64
65 func rtohex(r rune, w int) []byte {
66 bytes := make([]byte, w)
67 for i := w - 1; i >= 0; i-- {
68 d := byte(r % 16)
69 r /= 16
70 if d <= 9 {
71 bytes[i] = '0' + d
72 } else {
73 bytes[i] = 'a' + d - 10
74 }
75 }
76 return bytes
77 }
78
79 func quoteDouble(s string) string {
80 var buf bytes.Buffer
81 buf.WriteByte('"')
82 for _, r := range s {
83 if e, ok := doubleUnescape[r]; ok {
84 // Takes care of " and \ as well.
85 buf.WriteByte('\\')
86 buf.WriteRune(e)
87 } else if !unicode.IsPrint(r) {
88 buf.WriteByte('\\')
89 if r <= 0xff {
90 buf.WriteByte('x')
91 buf.Write(rtohex(r, 2))
92 } else if r <= 0xffff {
93 buf.WriteByte('u')
94 buf.Write(rtohex(r, 4))
95 } else {
96 buf.WriteByte('U')
97 buf.Write(rtohex(r, 8))
98 }
99 } else {
100 buf.WriteRune(r)
101 }
102 }
103 buf.WriteByte('"')
104 return buf.String()
105 }
0 package parse
1
2 import "testing"
3
4 var quoteTests = []struct {
5 text, quoted string
6 }{
7 // Empty string is quoted with single quote.
8 {"", `''`},
9 // Bareword when possible.
10 {"x-y,z@h/d", "x-y,z@h/d"},
11 // Single quote when there is special char but no unprintable.
12 {"x$y[]ef'", "'x$y[]ef'''"},
13 // Tilde needs quoting only when appearing at the beginning
14 {"~x", "'~x'"},
15 {"x~", "x~"},
16 // Double quote when there is unprintable char.
17 {"a\nb", `"a\nb"`},
18 {"\x1b\"\\", `"\e\"\\"`},
19 }
20
21 func TestQuote(t *testing.T) {
22 for _, tc := range quoteTests {
23 got := Quote(tc.text)
24 if got != tc.quoted {
25 t.Errorf("Quote(%q) => %s, want %s", tc.text, got, tc.quoted)
26 }
27 }
28 }
0 // Code generated by "stringer -type=PrimaryType,RedirMode -output=string.go"; DO NOT EDIT.
1
2 package parse
3
4 import "fmt"
5
6 const _PrimaryType_name = "BadPrimaryBarewordSingleQuotedDoubleQuotedVariableWildcardTildeExceptionCaptureOutputCaptureListLambdaMapBraced"
7
8 var _PrimaryType_index = [...]uint8{0, 10, 18, 30, 42, 50, 58, 63, 79, 92, 96, 102, 105, 111}
9
10 func (i PrimaryType) String() string {
11 if i < 0 || i >= PrimaryType(len(_PrimaryType_index)-1) {
12 return fmt.Sprintf("PrimaryType(%d)", i)
13 }
14 return _PrimaryType_name[_PrimaryType_index[i]:_PrimaryType_index[i+1]]
15 }
16
17 const _RedirMode_name = "BadRedirModeReadWriteReadWriteAppend"
18
19 var _RedirMode_index = [...]uint8{0, 12, 16, 21, 30, 36}
20
21 func (i RedirMode) String() string {
22 if i < 0 || i >= RedirMode(len(_RedirMode_index)-1) {
23 return fmt.Sprintf("RedirMode(%d)", i)
24 }
25 return _RedirMode_name[_RedirMode_index[i]:_RedirMode_index[i+1]]
26 }
0 // Package shell is the entry point for the terminal interface of Elvish.
1 package shell
2
3 import (
4 "bufio"
5 "fmt"
6 "io"
7 "io/ioutil"
8 "os"
9 "os/signal"
10 "syscall"
11 "time"
12 "unicode/utf8"
13
14 "github.com/elves/elvish/daemon/api"
15 "github.com/elves/elvish/edit"
16 "github.com/elves/elvish/eval"
17 "github.com/elves/elvish/sys"
18 "github.com/elves/elvish/util"
19 )
20
21 var logger = util.GetLogger("[shell] ")
22
23 // Shell keeps flags to the shell.
24 type Shell struct {
25 ev *eval.Evaler
26 daemon *api.Client
27 cmd bool
28 }
29
30 func NewShell(ev *eval.Evaler, daemon *api.Client, cmd bool) *Shell {
31 return &Shell{ev, daemon, cmd}
32 }
33
34 // Run runs Elvish using the default terminal interface. It blocks until Elvish
35 // quites, and returns the exit code.
36 func (sh *Shell) Run(args []string) int {
37 defer rescue()
38
39 handleUsr1AndQuit()
40 logSignals()
41
42 if len(args) > 0 {
43 if len(args) > 1 {
44 fmt.Fprintln(os.Stderr, "passing argument is not yet supported.")
45 return 2
46 }
47 arg := args[0]
48 if sh.cmd {
49 sourceTextAndPrintError(sh.ev, "code from -c", arg)
50 } else {
51 script(sh.ev, arg)
52 }
53 } else if !sys.IsATTY(0) {
54 script(sh.ev, "/dev/stdin")
55 } else {
56 interact(sh.ev, sh.daemon)
57 }
58
59 return 0
60 }
61
62 func rescue() {
63 r := recover()
64 if r != nil {
65 println()
66 fmt.Println(r)
67 print(sys.DumpStack())
68 println("\nexecing recovery shell /bin/sh")
69 syscall.Exec("/bin/sh", []string{"/bin/sh"}, os.Environ())
70 }
71 }
72
73 func script(ev *eval.Evaler, fname string) {
74 if !source(ev, fname, false) {
75 os.Exit(1)
76 }
77 }
78
79 func source(ev *eval.Evaler, fname string, notexistok bool) bool {
80 src, err := readFileUTF8(fname)
81 if err != nil {
82 if notexistok && os.IsNotExist(err) {
83 return true
84 }
85 fmt.Fprintln(os.Stderr, err)
86 return false
87 }
88
89 return sourceTextAndPrintError(ev, fname, src)
90 }
91
92 // sourceTextAndPrintError sources text, prints error if there is any, and
93 // returns whether there was no error.
94 func sourceTextAndPrintError(ev *eval.Evaler, name, src string) bool {
95 err := ev.SourceText(name, src)
96 if err != nil {
97 switch err := err.(type) {
98 case util.Pprinter:
99 fmt.Fprintln(os.Stderr, err.Pprint(""))
100 default:
101 fmt.Fprintf(os.Stderr, "\033[31;1m%s\033[m", err.Error())
102 }
103 return false
104 }
105 return true
106 }
107
108 func readFileUTF8(fname string) (string, error) {
109 bytes, err := ioutil.ReadFile(fname)
110 if err != nil {
111 return "", err
112 }
113 if !utf8.Valid(bytes) {
114 return "", fmt.Errorf("%s: source is not valid UTF-8", fname)
115 }
116 return string(bytes), nil
117 }
118
119 func interact(ev *eval.Evaler, daemon *api.Client) {
120 // Build Editor.
121 sigch := make(chan os.Signal)
122 signal.Notify(sigch)
123 ed := edit.NewEditor(os.Stdin, os.Stderr, sigch, ev, daemon)
124
125 // Source rc.elv.
126 if ev.DataDir != "" {
127 source(ev, ev.DataDir+"/rc.elv", true)
128 }
129
130 // Build readLine function.
131 readLine := func() (string, error) {
132 return ed.ReadLine()
133 }
134
135 cooldown := time.Second
136 usingBasic := false
137 cmdNum := 0
138
139 for {
140 cmdNum++
141 // name := fmt.Sprintf("<tty %d>", cmdNum)
142
143 line, err := readLine()
144
145 if err == io.EOF {
146 break
147 } else if err != nil {
148 fmt.Println("Editor error:", err)
149 if !usingBasic {
150 fmt.Println("Falling back to basic line editor")
151 readLine = basicReadLine
152 usingBasic = true
153 } else {
154 fmt.Println("Don't know what to do, pid is", os.Getpid())
155 fmt.Println("Restarting editor in", cooldown)
156 time.Sleep(cooldown)
157 if cooldown < time.Minute {
158 cooldown *= 2
159 }
160 }
161 continue
162 }
163
164 // No error; reset cooldown.
165 cooldown = time.Second
166
167 sourceTextAndPrintError(ev, "[interactive]", line)
168 }
169 }
170
171 func basicReadLine() (string, error) {
172 stdin := bufio.NewReaderSize(os.Stdin, 0)
173 return stdin.ReadString('\n')
174 }
175
176 func logSignals() {
177 sigs := make(chan os.Signal)
178 signal.Notify(sigs)
179 go func() {
180 for sig := range sigs {
181 logger.Println("signal", sig)
182 }
183 }()
184 }
185
186 func handleUsr1AndQuit() {
187 sigs := make(chan os.Signal)
188 signal.Notify(sigs, syscall.SIGUSR1, syscall.SIGQUIT)
189 go func() {
190 for sig := range sigs {
191 fmt.Print(sys.DumpStack())
192 if sig == syscall.SIGQUIT {
193 os.Exit(3)
194 }
195 }
196 }()
197 }
0 package shell
1
2 import "testing"
3
4 func TestShell(t *testing.T) {
5 // TODO(xiaq): Add tests.
6 }
0 package store
1
2 import (
3 "database/sql"
4
5 "github.com/elves/elvish/store/storedefs"
6 )
7
8 func init() {
9 initDB["initialize command history table"] = func(db *sql.DB) error {
10 _, err := db.Exec(`CREATE TABLE IF NOT EXISTS cmd (content text)`)
11 return err
12 }
13 }
14
15 // NextCmdSeq returns the next sequence number of the command history.
16 func (s *Store) NextCmdSeq() (int, error) {
17 row := s.db.QueryRow(`SELECT ifnull(max(rowid), 0) + 1 FROM cmd`)
18 var seq int
19 err := row.Scan(&seq)
20 return seq, err
21 }
22
23 // AddCmd adds a new command to the command history.
24 func (s *Store) AddCmd(cmd string) (int, error) {
25 r, err := s.db.Exec(`INSERT INTO cmd (content) VALUES(?)`, cmd)
26 if err != nil {
27 return -1, err
28 }
29 i, err := r.LastInsertId()
30 return int(i), err
31 }
32
33 // Cmd queries the command history item with the specified sequence number.
34 func (s *Store) Cmd(seq int) (string, error) {
35 row := s.db.QueryRow(`SELECT content FROM cmd WHERE rowid = ?`, seq)
36 var cmd string
37 err := row.Scan(&cmd)
38 return cmd, err
39 }
40
41 // IterateCmds iterates all the commands in the specified range, and calls the
42 // callback with the content of each command sequentially.
43 func (s *Store) IterateCmds(from, upto int, f func(string) bool) error {
44 rows, err := s.db.Query(`SELECT content FROM cmd WHERE rowid >= ? AND rowid < ?`, from, upto)
45 if err != nil {
46 return err
47 }
48 defer rows.Close()
49 for rows.Next() {
50 var cmd string
51 err = rows.Scan(&cmd)
52 if err != nil {
53 break
54 }
55 if !f(cmd) {
56 break
57 }
58 }
59 return err
60 }
61
62 // Cmds returns the contents of all commands within the specified range.
63 func (s *Store) Cmds(from, upto int) ([]string, error) {
64 var cmds []string
65 err := s.IterateCmds(from, upto, func(cmd string) bool {
66 cmds = append(cmds, cmd)
67 return true
68 })
69 return cmds, err
70 }
71
72 // NextCmd finds the first command after the given sequence number (inclusive)
73 // with the given prefix.
74 func (s *Store) NextCmd(from int, prefix string) (int, string, error) {
75 row := s.db.QueryRow(`SELECT rowid, content FROM cmd WHERE rowid >= ? AND substr(content, 1, ?) = ? ORDER BY rowid asc LIMIT 1`, from, len(prefix), prefix)
76 return convertCmd(row)
77 }
78
79 // PrevCmd finds the last command before the given sequence number (exclusive)
80 // with the given prefix.
81 func (s *Store) PrevCmd(upto int, prefix string) (int, string, error) {
82 var upto64 = int64(upto)
83 if upto < 0 {
84 upto64 = 0x7FFFFFFFFFFFFFFF
85 }
86 row := s.db.QueryRow(`SELECT rowid, content FROM cmd WHERE rowid < ? AND substr(content, 1, ?) = ? ORDER BY rowid DESC LIMIT 1`, upto64, len(prefix), prefix)
87 return convertCmd(row)
88 }
89
90 func convertCmd(row *sql.Row) (seq int, cmd string, err error) {
91 err = row.Scan(&seq, &cmd)
92 if err == sql.ErrNoRows {
93 err = storedefs.ErrNoMatchingCmd
94 }
95 return
96 }
0 package store
1
2 import (
3 "testing"
4
5 "github.com/elves/elvish/store/storedefs"
6 )
7
8 var (
9 cmds = []string{"echo foo", "put bar", "put lorem", "echo bar"}
10 searches = []struct {
11 next bool
12 seq int
13 prefix string
14 wantedSeq int
15 wantedCmd string
16 wantedErr error
17 }{
18 {false, 5, "echo", 4, "echo bar", nil},
19 {false, 5, "put", 3, "put lorem", nil},
20 {false, 4, "echo", 1, "echo foo", nil},
21 {false, 3, "f", 0, "", storedefs.ErrNoMatchingCmd},
22
23 {true, 1, "echo", 1, "echo foo", nil},
24 {true, 1, "put", 2, "put bar", nil},
25 {true, 2, "echo", 4, "echo bar", nil},
26 {true, 4, "put", 0, "", storedefs.ErrNoMatchingCmd},
27 }
28 )
29
30 func TestCmd(t *testing.T) {
31 startSeq, err := tStore.NextCmdSeq()
32 if startSeq != 1 || err != nil {
33 t.Errorf("tStore.NextCmdSeq() => (%v, %v), want (1, nil)",
34 startSeq, err)
35 }
36 for i, cmd := range cmds {
37 wantSeq := startSeq + i
38 seq, err := tStore.AddCmd(cmd)
39 if seq != wantSeq || err != nil {
40 t.Errorf("tStore.AddCmd(%v) => (%v, %v), want (%v, nil)",
41 cmd, seq, err, wantSeq)
42 }
43 }
44 endSeq, err := tStore.NextCmdSeq()
45 wantedEndSeq := startSeq + len(cmds)
46 if endSeq != wantedEndSeq || err != nil {
47 t.Errorf("tStore.NextCmdSeq() => (%v, %v), want (%v, nil)",
48 endSeq, err, wantedEndSeq)
49 }
50 for i, wantedCmd := range cmds {
51 seq := i + startSeq
52 cmd, err := tStore.Cmd(seq)
53 if cmd != wantedCmd || err != nil {
54 t.Errorf("tStore.Cmd(%v) => (%v, %v), want (%v, nil)",
55 seq, cmd, err, wantedCmd)
56 }
57 }
58 for _, tt := range searches {
59 f := tStore.PrevCmd
60 funcname := "tStore.PrevCmd"
61 if tt.next {
62 f = tStore.NextCmd
63 funcname = "tStore.NextCmd"
64 }
65 seq, cmd, err := f(tt.seq, tt.prefix)
66 if seq != tt.wantedSeq || cmd != tt.wantedCmd || err != tt.wantedErr {
67 t.Errorf("%s(%v, %v) => (%v, %v), want (%v, %v)",
68 funcname, tt.seq, tt.prefix,
69 seq, cmd, err,
70 tt.wantedSeq, tt.wantedCmd, tt.wantedErr)
71 }
72 }
73 }
0 package store
1
2 import (
3 "database/sql"
4
5 "github.com/elves/elvish/store/storedefs"
6 )
7
8 const (
9 scoreDecay = 0.986 // roughly 0.5^(1/50)
10 scoreIncrement = 10
11 )
12
13 func init() {
14 initDB["initialize directory history table"] = func(db *sql.DB) error {
15 _, err := db.Exec(`create table if not exists dir (path text unique primary key, score real default 0)`)
16 return err
17 }
18 }
19
20 // AddDir adds a directory to the directory history.
21 func (s *Store) AddDir(d string, incFactor float64) error {
22 return transaction(s.db, func(tx *sql.Tx) error {
23 // Insert when the path does not already exist
24 _, err := tx.Exec("insert or ignore into dir (path) values(?)", d)
25 if err != nil {
26 return err
27 }
28
29 // Decay scores
30 _, err = tx.Exec("update dir set score = score * ?", scoreDecay)
31 if err != nil {
32 return err
33 }
34
35 // Increment score
36 _, err = tx.Exec("update dir set score = score + ? where path = ?", scoreIncrement*incFactor, d)
37 return err
38 })
39 }
40
41 // GetDirs lists all directories in the directory history whose names are not
42 // in the blacklist. The results are ordered by scores in descending order.
43 func (s *Store) GetDirs(blacklist map[string]struct{}) ([]storedefs.Dir, error) {
44 rows, err := s.db.Query(
45 "select path, score from dir order by score desc")
46 if err != nil {
47 return nil, err
48 }
49 return convertDirs(rows, blacklist)
50 }
51
52 func convertDirs(rows *sql.Rows, blacklist map[string]struct{}) ([]storedefs.Dir, error) {
53 var (
54 dir storedefs.Dir
55 dirs []storedefs.Dir
56 )
57
58 for rows.Next() {
59 rows.Scan(&dir.Path, &dir.Score)
60 if _, black := blacklist[dir.Path]; !black {
61 dirs = append(dirs, dir)
62 }
63 }
64 if err := rows.Err(); err != nil {
65 return nil, err
66 }
67 return dirs, nil
68 }
0 package store
1
2 import (
3 "reflect"
4 "testing"
5
6 "github.com/elves/elvish/store/storedefs"
7 )
8
9 var (
10 dirsToAdd = []string{"/usr/local", "/usr", "/usr/bin", "/usr"}
11 black = map[string]struct{}{"/usr/local": {}}
12 wantedDirs = []storedefs.Dir{
13 {"/usr", scoreIncrement*scoreDecay*scoreDecay + scoreIncrement},
14 {"/usr/bin", scoreIncrement * scoreDecay}}
15 )
16
17 func TestDir(t *testing.T) {
18 for _, path := range dirsToAdd {
19 err := tStore.AddDir(path, 1)
20 if err != nil {
21 t.Errorf("tStore.AddDir(%q) => %v, want <nil>", path, err)
22 }
23 }
24
25 dirs, err := tStore.GetDirs(black)
26 if err != nil || !reflect.DeepEqual(dirs, wantedDirs) {
27 t.Errorf(`tStore.ListDirs() => (%v, %v), want (%v, <nil>)`,
28 dirs, err, wantedDirs)
29 }
30 }
0 package store
1
2 import "database/sql"
3
4 // SchemaVersion is the current schema version. It should be bumped every time a
5 // backwards-incompatible change has been made to the schema.
6 const SchemaVersion = 1
7
8 func init() {
9 initDB["record schema version"] = func(db *sql.DB) error {
10 _, err := db.Exec(`CREATE TABLE IF NOT EXISTS schema_version (version integer); INSERT INTO schema_version (version) VALUES(?)`, SchemaVersion)
11 return err
12 }
13 }
14
15 // SchemaUpToDate returns whether the database has the current or newer version
16 // of the schema.
17 func SchemaUpToDate(db *sql.DB) bool {
18 var v int
19 row := db.QueryRow(`SELECT version FROM schema_version`)
20 return row.Scan(&v) == nil && v >= SchemaVersion
21 }
0 package store
1
2 import (
3 "database/sql"
4 "errors"
5 )
6
7 // ErrNoVar is returned by (*Store).GetSharedVar when there is no such variable.
8 var ErrNoVar = errors.New("no such variable")
9
10 func init() {
11 initDB["initialize shared variable table"] = func(db *sql.DB) error {
12 _, err := db.Exec(`CREATE TABLE IF NOT EXISTS shared_var (name text UNIQUE PRIMARY KEY, value text)`)
13 return err
14 }
15 }
16
17 // GetSharedVar gets the value of a shared variable.
18 func (s *Store) GetSharedVar(n string) (string, error) {
19 row := s.db.QueryRow(`SELECT value FROM shared_var WHERE name = ?`, n)
20 var value string
21 err := row.Scan(&value)
22 if err == sql.ErrNoRows {
23 err = ErrNoVar
24 }
25 return value, err
26 }
27
28 // SetSharedVar sets the value of a shared variable.
29 func (s *Store) SetSharedVar(n, v string) error {
30 _, err := s.db.Exec(`INSERT OR REPLACE INTO shared_var (name, value) VALUES (?, ?)`, n, v)
31 return err
32 }
33
34 // DelSharedVar deletes a shared variable.
35 func (s *Store) DelSharedVar(n string) error {
36 _, err := s.db.Exec(`DELETE FROM shared_var WHERE name = ?`, n)
37 return err
38 }
0 package store
1
2 import "testing"
3
4 func TestSharedVar(t *testing.T) {
5 varname := "foo"
6 value1 := "lorem ipsum"
7 value2 := "o mores, o tempora"
8
9 // Getting an nonexistent variable should return ErrNoVar.
10 _, err := tStore.GetSharedVar(varname)
11 if err != ErrNoVar {
12 t.Error("want ErrNoVar, got", err)
13 }
14
15 // Setting a variable for the first time creates it.
16 err = tStore.SetSharedVar(varname, value1)
17 if err != nil {
18 t.Error("want no error, got", err)
19 }
20 v, err := tStore.GetSharedVar(varname)
21 if v != value1 || err != nil {
22 t.Errorf("want %q and no error, got %q and %v", value1, v, err)
23 }
24
25 // Setting an existing variable updates its value.
26 err = tStore.SetSharedVar(varname, value2)
27 if err != nil {
28 t.Error("want no error, got", err)
29 }
30 v, err = tStore.GetSharedVar(varname)
31 if v != value2 || err != nil {
32 t.Errorf("want %q and no error, got %q and %v", value2, v, err)
33 }
34
35 // After deleting a variable, access to it cause ErrNoVar.
36 err = tStore.DelSharedVar(varname)
37 if err != nil {
38 t.Error("want no error, got", err)
39 }
40 _, err = tStore.GetSharedVar(varname)
41 if err != ErrNoVar {
42 t.Error("want ErrNoVar, got", err)
43 }
44 }
0 package store
1
2 import "database/sql"
3
4 func hasColumn(rows *sql.Rows, colname string) (bool, error) {
5 cols, err := rows.Columns()
6 if err != nil {
7 return false, err
8 }
9 for _, col := range cols {
10 if col == colname {
11 return true, nil
12 }
13 }
14 return false, rows.Err()
15 }
16
17 // transaction creates a Tx and calls f on it. It commits or rollbacks the
18 // transaction depending on whether f succeeded.
19 func transaction(db *sql.DB, f func(*sql.Tx) error) error {
20 tx, err := db.Begin()
21 if err != nil {
22 return err
23 }
24 err = f(tx)
25 if err != nil {
26 return tx.Rollback()
27 }
28 return tx.Commit()
29 }
0 // Package store abstracts the persistent storage used by elvish.
1 package store
2
3 import (
4 "database/sql"
5 "fmt"
6 "net/url"
7 "sync"
8
9 "github.com/elves/elvish/util"
10
11 _ "github.com/mattn/go-sqlite3" // enable the "sqlite3" SQL driver
12 )
13
14 var logger = util.GetLogger("[store] ")
15 var initDB = map[string](func(*sql.DB) error){}
16
17 // Store is the permanent storage backend for elvish. It is not thread-safe. In
18 // particular, the store may be closed while another goroutine is still
19 // accessing the store. To prevent bad things from happening, every time the
20 // main goroutine spawns a new goroutine to operate on the store, it should call
21 // Waits.Add(1) in the main goroutine before spawning another goroutine, and
22 // call Waits.Done() in the spawned goroutine after the operation is finished.
23 type Store struct {
24 db *sql.DB
25 // Waits is used for registering outstanding operations on the store.
26 waits sync.WaitGroup
27 }
28
29 // DefaultDB returns the default database for storage.
30 func DefaultDB(dbname string) (*sql.DB, error) {
31 uri := "file:" + url.QueryEscape(dbname) +
32 "?mode=rwc&cache=shared&vfs=unix-dotfile"
33 db, err := sql.Open("sqlite3", uri)
34 if err == nil {
35 db.SetMaxOpenConns(1)
36 }
37 return db, err
38 }
39
40 // NewStore creates a new Store with the default database.
41 func NewStore(dbname string) (*Store, error) {
42 db, err := DefaultDB(dbname)
43 if err != nil {
44 return nil, err
45 }
46 return NewStoreDB(db)
47 }
48
49 // NewStoreDB creates a new Store with a custom database. The database must be
50 // a SQLite database.
51 func NewStoreDB(db *sql.DB) (*Store, error) {
52 logger.Println("initializing store")
53 defer logger.Println("initialized store")
54 st := &Store{db, sync.WaitGroup{}}
55
56 if SchemaUpToDate(db) {
57 logger.Println("DB schema up to date")
58 } else {
59 for name, fn := range initDB {
60 err := fn(db)
61 if err != nil {
62 return nil, fmt.Errorf("failed to %s: %v", name, err)
63 }
64 }
65 }
66
67 return st, nil
68 }
69
70 // Waits returns a WaitGroup used to register outstanding storage requests when
71 // making calls asynchronously.
72 func (s *Store) Waits() *sync.WaitGroup {
73 return &s.waits
74 }
75
76 // Close waits for all outstanding operations to finish, and closes the
77 // database.
78 func (s *Store) Close() error {
79 if s == nil || s.db == nil {
80 return nil
81 }
82 s.waits.Wait()
83 return s.db.Close()
84 }
0 package store
1
2 // This file also sets up the test fixture.
3
4 import (
5 "database/sql"
6 "fmt"
7 "testing"
8
9 "github.com/elves/elvish/store/storedefs"
10 )
11
12 var tStore *Store
13
14 func init() {
15 db, err := sql.Open("sqlite3", ":memory:")
16 if err != nil {
17 panic(fmt.Sprintf("Failed to create in-memory SQLite3 DB: %v", err))
18 }
19 tStore, err = NewStoreDB(db)
20 if err != nil {
21 panic(fmt.Sprintf("Failed to create Store instance: %v", err))
22 }
23 }
24
25 func TestNewStore(t *testing.T) {
26 // XXX(xiaq): Also tests EnsureDataDir
27 dataDir, err := storedefs.EnsureDataDir()
28 if err != nil {
29 t.Errorf("EnsureDataDir() -> (*, %v), want (*, <nil>)", err)
30 }
31
32 _, err = NewStore(dataDir + "/db")
33 if err != nil {
34 t.Errorf("NewStore() -> (*, %v), want (*, <nil>)", err)
35 }
36 }
0 package storedefs
1
2 import (
3 "errors"
4 "os"
5
6 "github.com/elves/elvish/util"
7 )
8
9 // ErrEmptyHOME is the error returned by EnsureDataDir when the environmental
10 // variable HOME is empty.
11 var ErrEmptyHOME = errors.New("environment variable HOME is empty")
12
13 // EnsureDataDir ensures Elvish's data directory exists, creating it if
14 // necessary. It returns the path to the data directory (never with a
15 // trailing slash) and possible error.
16 func EnsureDataDir() (string, error) {
17 home, err := util.GetHome("")
18 if err != nil {
19 return "", err
20 }
21 ddir := home + "/.elvish"
22 return ddir, os.MkdirAll(ddir, 0700)
23 }
0 // Package storedefs contains definitions used by the store package.
1 package storedefs
2
3 import "errors"
4
5 // NoBlacklist is an empty blacklist, to be used in GetDirs.
6 var NoBlacklist = map[string]struct{}{}
7
8 // ErrNoMatchingCmd is the error returned when a LastCmd or FirstCmd query
9 // completes with no result.
10 var ErrNoMatchingCmd = errors.New("no matching command line")
11
12 // Dir is an entry in the directory history.
13 type Dir struct {
14 Path string
15 Score float64
16 }
0 package storedefs
1
2 import "testing"
3
4 func TestStoreDefs(t *testing.T) {
5 // TODO(xiaq): Add tests
6 }
0 package sys
1
2 import "runtime"
3
4 func DumpStack() string {
5 buf := make([]byte, 1024)
6 for runtime.Stack(buf, true) == cap(buf) {
7 buf = make([]byte, cap(buf)*2)
8 }
9 return string(buf)
10 }
0 package sys
1
2 import (
3 "syscall"
4 )
5
6 func Fcntl(fd int, cmd int, arg int) (val int, err error) {
7 r, _, e := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd),
8 uintptr(arg))
9 val = int(r)
10 if e != 0 {
11 err = e
12 }
13 return
14 }
15
16 func GetNonblock(fd int) (bool, error) {
17 r, err := Fcntl(fd, syscall.F_GETFL, 0)
18 return r&syscall.O_NONBLOCK != 0, err
19 }
20
21 func SetNonblock(fd int, nonblock bool) error {
22 r, err := Fcntl(fd, syscall.F_GETFL, 0)
23 if err != nil {
24 return err
25 }
26 if nonblock {
27 r |= syscall.O_NONBLOCK
28 } else {
29 r &^= syscall.O_NONBLOCK
30 }
31 _, err = Fcntl(fd, syscall.F_SETFL, r)
32 return err
33 }
0 package sys
1
2 import (
3 "syscall"
4 "testing"
5 )
6
7 func mustNil(e error) {
8 if e != nil {
9 panic("error is not nil")
10 }
11 }
12
13 func TestGetSetNonblock(t *testing.T) {
14 var p [2]int
15 mustNil(syscall.Pipe(p[:]))
16 for _, b := range []bool{true, false} {
17 if e := SetNonblock(p[0], b); e != nil {
18 t.Errorf("SetNonblock(%v, %v) => %v, want <nil>", p[0], b, e)
19 }
20 if nb, e := GetNonblock(p[0]); nb != b || e != nil {
21 t.Errorf("GetNonblock(%v) => (%v, %v), want (%v, <nil>)", p[0], nb, e, b)
22 }
23 }
24 syscall.Close(p[0])
25 syscall.Close(p[1])
26 if e := SetNonblock(p[0], true); e == nil {
27 t.Errorf("SetNonblock(%v, true) => <nil>, want non-<nil>", p[0])
28 }
29 }
0 package sys
1
2 import (
3 "os"
4 "syscall"
5 )
6
7 // Ioctl wraps the ioctl syscall.
8 func Ioctl(fd int, req int, arg uintptr) error {
9 _, _, e := syscall.Syscall(
10 syscall.SYS_IOCTL, uintptr(fd), uintptr(req), arg)
11 if e != 0 {
12 return os.NewSyscallError("ioctl", e)
13 }
14 return nil
15 }
0 package sys
1
2 import (
3 "github.com/mattn/go-isatty"
4 )
5
6 func IsATTY(fd int) bool {
7 return isatty.IsTerminal(uintptr(fd)) ||
8 isatty.IsCygwinTerminal(uintptr(fd))
9 }
0 package sys
1
2 import "syscall"
3
4 type FdSet syscall.FdSet
5
6 func (fs *FdSet) s() *syscall.FdSet {
7 return (*syscall.FdSet)(fs)
8 }
9
10 func NewFdSet(fds ...int) *FdSet {
11 fs := &FdSet{}
12 fs.Set(fds...)
13 return fs
14 }
15
16 func (fs *FdSet) Clear(fds ...int) {
17 for _, fd := range fds {
18 idx, bit := index(fd)
19 fs.Bits[idx] &= ^bit
20 }
21 }
22
23 func (fs *FdSet) IsSet(fd int) bool {
24 idx, bit := index(fd)
25 return fs.Bits[idx]&bit != 0
26 }
27
28 func (fs *FdSet) Set(fds ...int) {
29 for _, fd := range fds {
30 idx, bit := index(fd)
31 fs.Bits[idx] |= bit
32 }
33 }
34
35 func (fs *FdSet) Zero() {
36 *fs = FdSet{}
37 }
0 // +build darwin 386,freebsd arm,freebsd 386,linux arm,linux netbsd openbsd
1
2 // The type of FdSet.Bits is different on different platforms.
3 // This file is for those where FdSet.Bits is []int32.
4
5 package sys
6
7 const NFDBits = 32
8
9 func index(fd int) (idx uint, bit int32) {
10 u := uint(fd)
11 return u / NFDBits, 1 << (u % NFDBits)
12 }
0 // +build amd64,dragonfly amd64,freebsd amd64,linux arm64,linux
1
2 // The type of FdSet.Bits is different on different platforms.
3 // This file is for those where FdSet.Bits is []int64.
4
5 package sys
6
7 const NFDBits = 64
8
9 func index(fd int) (idx uint, bit int64) {
10 u := uint(fd)
11 return u / NFDBits, 1 << (u % NFDBits)
12 }
0 // +build darwin dragonfly freebsd netbsd openbsd
1
2 package sys
3
4 import "syscall"
5
6 func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *syscall.Timeval) (err error) {
7 return syscall.Select(nfd, r.s(), w.s(), e.s(), timeout)
8 }
0 // +build linux
1
2 package sys
3
4 import "syscall"
5
6 func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *syscall.Timeval) error {
7 _, err := syscall.Select(nfd, r.s(), w.s(), e.s(), timeout)
8 return err
9 }
0 package sys
1
2 import "os"
3
4 // SelectRead blocks until any of the given files is ready to be read. It
5 // returns a boolean array indicating which files are ready to be read and
6 // possible errors.
7 func SelectRead(files ...*os.File) (ready []bool, err error) {
8 maxfd := 0
9 fdset := NewFdSet()
10 for _, file := range files {
11 fd := int(file.Fd())
12 if maxfd < fd {
13 maxfd = fd
14 }
15 fdset.Set(fd)
16 }
17 err = Select(maxfd+1, fdset, nil, nil, nil)
18 ready = make([]bool, len(files))
19 for i, file := range files {
20 ready[i] = fdset.IsSet(int(file.Fd()))
21 }
22 return ready, err
23 }
0 package sys
1
2 import (
3 "syscall"
4 "testing"
5 )
6
7 func TestFdSet(t *testing.T) {
8 fs := NewFdSet(42, 233)
9 fs.Set(77)
10 fds := []int{42, 233, 77}
11 for _, i := range fds {
12 if !fs.IsSet(i) {
13 t.Errorf("fs.IsSet(%d) => false, want true", i)
14 }
15 }
16 fs.Clear(233)
17 if fs.IsSet(233) {
18 t.Errorf("fs.IsSet(233) => true, want false")
19 }
20 fs.Zero()
21 for _, i := range fds {
22 if fs.IsSet(i) {
23 t.Errorf("fs.IsSet(%d) => true, want false", i)
24 }
25 }
26 }
27
28 func TestSelect(t *testing.T) {
29 var p1, p2 [2]int
30 mustNil(syscall.Pipe(p1[:]))
31 mustNil(syscall.Pipe(p2[:]))
32 fs := NewFdSet(p1[0], p2[0])
33 var maxfd int
34 if p1[0] > p2[0] {
35 maxfd = p1[0] + 1
36 } else {
37 maxfd = p2[0] + 1
38 }
39 go func() {
40 syscall.Write(p1[1], []byte("to p1"))
41 syscall.Write(p2[1], []byte("to p2"))
42 syscall.Close(p1[1])
43 syscall.Close(p2[1])
44 }()
45 e := Select(maxfd+1, fs, nil, nil, nil)
46 if e != nil {
47 t.Errorf("Select(%v, %v, nil, nil, nil) => %v, want <nil>",
48 maxfd+1, fs, e)
49 }
50 syscall.Close(p1[0])
51 syscall.Close(p2[0])
52 }
0 // Package sys provide convenient wrappers around syscalls.
1 package sys
0 package sys
1
2 import (
3 "syscall"
4 "unsafe"
5 )
6
7 func Tcgetpgrp(fd int) (int, error) {
8 var pid int
9 _, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(fd),
10 uintptr(syscall.TIOCGPGRP), uintptr(unsafe.Pointer(&pid)))
11 if errno == 0 {
12 return pid, nil
13 }
14 return -1, errno
15 }
16
17 func Tcsetpgrp(fd int, pid int) error {
18 _, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(fd),
19 uintptr(syscall.TIOCSPGRP), uintptr(unsafe.Pointer(&pid)))
20 if errno == 0 {
21 return nil
22 }
23 return errno
24 }
0 // Copyright 2015 go-termios Author. All Rights Reserved.
1 // https://github.com/go-termios/termios
2 // Author: John Lenton <chipaca@github.com>
3
4 package sys
5
6 import (
7 "unsafe"
8
9 "golang.org/x/sys/unix"
10 )
11
12 // Termios represents terminal attributes.
13 type Termios unix.Termios
14
15 func ioctl(fd, cmd uintptr, arg unsafe.Pointer) error {
16 return ioctlu(fd, cmd, uintptr(arg))
17 }
18
19 func ioctlu(fd, cmd, arg uintptr) error {
20 _, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, cmd, arg)
21 if errno == 0 {
22 return nil
23 }
24 return errno
25 }
26
27 // NewTermiosFromFd extracts the terminal attribute of the given file
28 // descriptor.
29 func NewTermiosFromFd(fd int) (*Termios, error) {
30 var term Termios
31 if err := ioctl(uintptr(fd), getAttrIOCTL, unsafe.Pointer(&term)); err != nil {
32 return nil, err
33 }
34 return &term, nil
35 }
36
37 // ApplyToFd applies term to the given file descriptor.
38 func (term *Termios) ApplyToFd(fd int) error {
39 return ioctl(uintptr(fd), setAttrNowIOCTL, unsafe.Pointer(term))
40 }
41
42 // Copy returns a copy of term.
43 func (term *Termios) Copy() *Termios {
44 v := *term
45 return &v
46 }
47
48 // SetVTime sets the timeout in deciseconds for noncanonical read.
49 func (term *Termios) SetVTime(v uint8) {
50 term.Cc[unix.VTIME] = v
51 }
52
53 // SetVMin sets the minimal number of characters for noncanonical read.
54 func (term *Termios) SetVMin(v uint8) {
55 term.Cc[unix.VMIN] = v
56 }
57
58 // SetICanon sets the canonical flag.
59 func (term *Termios) SetICanon(v bool) {
60 setFlag(&term.Lflag, unix.ICANON, v)
61 }
62
63 // SetEcho sets the echo flag.
64 func (term *Termios) SetEcho(v bool) {
65 setFlag(&term.Lflag, unix.ECHO, v)
66 }
67
68 // FlushInput discards data written to a file descriptor but not read.
69 func FlushInput(fd int) error {
70 return ioctlu(uintptr(fd), flushIOCTL, uintptr(unix.TCIFLUSH))
71 }
0 // +build 386,darwin dragonfly freebsd linux netbsd openbsd
1
2 package sys
3
4 // The type of Termios.Lflag is different on different platforms.
5 // This file is for those where Lflag is uint32.
6
7 func setFlag(flag *uint32, mask uint32, v bool) {
8 if v {
9 *flag |= mask
10 } else {
11 *flag &= ^mask
12 }
13 }
0 // +build amd64,darwin
1
2 package sys
3
4 // The type of Termios.Lflag is different on different platforms.
5 // This file is for those where Lflag is uint64.
6
7 func setFlag(flag *uint64, mask uint64, v bool) {
8 if v {
9 *flag |= mask
10 } else {
11 *flag &= ^mask
12 }
13 }
0 // +build darwin dragonfly freebsd netbsd openbsd
1
2 // Copyright 2015 go-termios Author. All Rights Reserved.
3 // https://github.com/go-termios/termios
4 // Author: John Lenton <chipaca@github.com>
5
6 package sys
7
8 import "golang.org/x/sys/unix"
9
10 const (
11 getAttrIOCTL = unix.TIOCGETA
12 setAttrNowIOCTL = unix.TIOCSETA
13 setAttrDrainIOCTL = unix.TIOCSETAW
14 setAttrFlushIOCTL = unix.TIOCSETAF
15 flushIOCTL = unix.TIOCFLUSH
16 )
0 // +build linux
1
2 // Copyright 2015 go-termios Author. All Rights Reserved.
3 // https://github.com/go-termios/termios
4 // Author: John Lenton <chipaca@github.com>
5
6 package sys
7
8 import "golang.org/x/sys/unix"
9
10 const (
11 getAttrIOCTL = unix.TCGETS
12 setAttrNowIOCTL = unix.TCSETS
13 setAttrDrainIOCTL = unix.TCSETSW
14 setAttrFlushIOCTL = unix.TCSETSF
15 flushIOCTL = unix.TCFLSH
16 )
0 // Copyright 2015 go-termios Author. All Rights Reserved.
1 // https://github.com/go-termios/termios
2 // Author: John Lenton <chipaca@github.com>
3
4 package sys
5
6 import (
7 "fmt"
8 "unsafe"
9
10 "golang.org/x/sys/unix"
11 )
12
13 // winSize mirrors struct winsize in the C header.
14 // The following declaration matches struct winsize in the headers of
15 // Linux and FreeBSD.
16 type winSize struct {
17 row uint16
18 col uint16
19 Xpixel uint16
20 Ypixel uint16
21 }
22
23 // GetWinsize queries the size of the terminal referenced by
24 // the given file descriptor.
25
26 func GetWinsize(fd int) (row, col int) {
27 ws := winSize{}
28 if err := ioctl(uintptr(fd), unix.TIOCGWINSZ, unsafe.Pointer(&ws)); err != nil {
29 fmt.Printf("error in winSize: %v", err)
30 return -1, -1
31 }
32
33 // Pick up a reasonable value for row and col
34 // if they equal zero in special case,
35 // e.g. serial console
36 if ws.col == 0 {
37 ws.col = 80
38 }
39 if ws.row == 0 {
40 ws.row = 24
41 }
42
43 return int(ws.row), int(ws.col)
44 }
0 package util
1
2 // CeilDiv computes ceil(float(a)/b) without using float arithmetics.
3 func CeilDiv(a, b int) int {
4 return (a + b - 1) / b
5 }
0 package util
1
2 import "testing"
3
4 var ceilDivTests = []struct {
5 a, b, out int
6 }{
7 {9, 3, 3},
8 {10, 3, 4},
9 {11, 3, 4},
10 {12, 3, 4},
11 }
12
13 func TestCeilDiv(t *testing.T) {
14 for _, tt := range ceilDivTests {
15 if o := CeilDiv(tt.a, tt.b); o != tt.out {
16 t.Errorf("CeilDiv(%v, %v) => %v, want %v", tt.a, tt.b, o, tt.out)
17 }
18 }
19 }
0 package util
1
2 import (
3 "bytes"
4 "fmt"
5 "reflect"
6 )
7
8 // DeepPrint is like printing with the %#v formatter of fmt, but it prints
9 // pointer fields recursively.
10 func DeepPrint(x interface{}) string {
11 b := &bytes.Buffer{}
12 deepPrint(b, reflect.ValueOf(x))
13 return b.String()
14 }
15
16 func deepPrint(b *bytes.Buffer, v reflect.Value) {
17 i := v.Interface()
18 t := v.Type()
19
20 // GoStringer
21 if g, ok := i.(fmt.GoStringer); ok {
22 b.WriteString(g.GoString())
23 return
24 }
25
26 // nil
27 switch v.Kind() {
28 case reflect.Interface, reflect.Map, reflect.Slice, reflect.Ptr:
29 if v.IsNil() {
30 b.WriteString("nil")
31 return
32 }
33 }
34
35 switch v.Kind() {
36 case reflect.Array, reflect.Slice, reflect.Map, reflect.Struct:
37 // Composite kinds
38 b.WriteString(t.String())
39 b.WriteRune('{')
40 switch v.Kind() {
41 case reflect.Array, reflect.Slice:
42 for i := 0; i < v.Len(); i++ {
43 if i > 0 {
44 b.WriteString(", ")
45 }
46 deepPrint(b, v.Index(i))
47 }
48 case reflect.Map:
49 keys := v.MapKeys()
50 for i, k := range keys {
51 if i > 0 {
52 b.WriteString(", ")
53 }
54 deepPrint(b, k)
55 b.WriteString(": ")
56 deepPrint(b, v.MapIndex(k))
57 }
58 case reflect.Struct:
59 for i := 0; i < t.NumField(); i++ {
60 if i > 0 {
61 b.WriteString(", ")
62 }
63 b.WriteString(t.Field(i).Name)
64 b.WriteString(": ")
65 deepPrint(b, v.Field(i))
66 }
67 }
68 b.WriteRune('}')
69 case reflect.Ptr:
70 b.WriteRune('&')
71 deepPrint(b, reflect.Indirect(v))
72 return
73 case reflect.Interface:
74 deepPrint(b, v.Elem())
75 return
76 default:
77 fmt.Fprintf(b, "%#v", i)
78 return
79 }
80 }
0 package util
1
2 import (
3 "testing"
4 )
5
6 type S struct {
7 I int
8 S string
9 Pt *T
10 G G
11 }
12
13 type T struct {
14 M map[string]string
15 }
16
17 type G struct {
18 }
19
20 type U struct {
21 I int
22 S string
23 }
24
25 func (g G) GoString() string {
26 return "<G>"
27 }
28
29 var deepPrintTests = []struct {
30 in interface{}
31 wanted string
32 }{
33 {1, "1"},
34 {"foobar", `"foobar"`},
35 {[]int{42, 44}, `[]int{42, 44}`},
36 {[]int(nil), `nil`},
37 {(*int)(nil), `nil`},
38 {&S{42, "DON'T PANIC", &T{map[string]string{"foo": "bar"}}, G{}},
39 `&util.S{I: 42, S: "DON'T PANIC", Pt: &util.T{M: map[string]string{"foo": "bar"}}, G: <G>}`},
40 {[]interface{}{&U{42, "DON'T PANIC"}, 42, "DON'T PANIC"}, `[]interface {}{&util.U{I: 42, S: "DON'T PANIC"}, 42, "DON'T PANIC"}`},
41 }
42
43 func TestDeepPrint(t *testing.T) {
44 for _, tt := range deepPrintTests {
45 if out := DeepPrint(tt.in); out != tt.wanted {
46 t.Errorf("DeepPrint(%v) => %#q, want %#q", tt.in, out, tt.wanted)
47 }
48 }
49 // Test map.
50 in := map[string]int{"foo": 42, "bar": 233}
51 out := DeepPrint(in)
52 wanted1 := `map[string]int{"foo": 42, "bar": 233}`
53 wanted2 := `map[string]int{"bar": 233, "foo": 42}`
54 if out != wanted1 && out != wanted2 {
55 t.Errorf("DeepPrint(%v) => %#q, want %#q or %#q", in, out, wanted1, wanted2)
56 }
57 }
0 package util
1
2 import "bytes"
3
4 // Errors pack multiple errors into one error.
5 type Errors struct {
6 Errors []error
7 }
8
9 func (es *Errors) Error() string {
10 switch len(es.Errors) {
11 case 0:
12 return "no error"
13 case 1:
14 return es.Errors[0].Error()
15 default:
16 var buf bytes.Buffer
17 buf.WriteString("multiple errors: ")
18 for i, e := range es.Errors {
19 if i > 0 {
20 buf.WriteString("; ")
21 }
22 buf.WriteString(e.Error())
23 }
24 return buf.String()
25 }
26 }
27
28 func (es *Errors) Append(e error) {
29 es.Errors = append(es.Errors, e)
30 }
31
32 func CatError(err error, more error) error {
33 if err == nil {
34 return more
35 }
36 if more == nil {
37 return err
38 }
39 if es, ok := err.(*Errors); ok {
40 es.Append(more)
41 return es
42 }
43 return &Errors{[]error{err, more}}
44 }
0 package util
1
2 import (
3 "os"
4 "sort"
5 )
6
7 // FullNames returns the full names of non-hidden files under a directory. The
8 // directory name should end in a slash. If the directory cannot be listed, it
9 // returns nil.
10 //
11 // The output should be the same as globbing dir + "*". It is used for testing
12 // globbing.
13 func FullNames(dir string) []string {
14 f, err := os.Open(dir)
15 if err != nil {
16 return nil
17 }
18
19 names, err := f.Readdirnames(-1)
20 f.Close()
21 if err != nil {
22 return nil
23 }
24
25 fullnames := make([]string, 0, len(names))
26 for _, name := range names {
27 if name[0] != '.' {
28 fullnames = append(fullnames, dir+name)
29 }
30 }
31
32 sort.Strings(fullnames)
33 return fullnames
34 }
0 package util
1
2 import (
3 "os/exec"
4 "reflect"
5 "sort"
6 "strings"
7 "testing"
8 )
9
10 func ls(dir string) []string {
11 // BUG: will fail if there are filenames containing newlines.
12 output, err := exec.Command("ls", dir).Output()
13 mustOK(err)
14 names := strings.Split(strings.Trim(string(output), "\n"), "\n")
15 for i := range names {
16 names[i] = dir + names[i]
17 }
18 sort.Strings(names)
19 return names
20 }
21
22 func TestFullNames(t *testing.T) {
23 for _, dir := range []string{"/", "/usr/"} {
24 wantNames := ls(dir)
25 names := FullNames(dir)
26 if !reflect.DeepEqual(names, wantNames) {
27 t.Errorf(`FullNames(%q) -> %s, want %s`, dir, names, wantNames)
28 }
29 }
30 }
0 package util
1
2 import (
3 "fmt"
4 "os"
5 "os/user"
6 "strings"
7 )
8
9 // GetHome finds the home directory of a specified user. When given an empty
10 // string, it finds the home directory of the current user.
11 func GetHome(uname string) (string, error) {
12 if uname == "" {
13 // Use $HOME as override if we are looking for the home of the current
14 // variable.
15 home := os.Getenv("HOME")
16 if home != "" {
17 return strings.TrimRight(home, "/"), nil
18 }
19 }
20
21 // Look up the user.
22 var u *user.User
23 var err error
24 if uname == "" {
25 u, err = user.Current()
26 } else {
27 u, err = user.Lookup(uname)
28 }
29 if err != nil {
30 return "", fmt.Errorf("can't resolve ~%s: %s", uname, err.Error())
31 }
32 return strings.TrimRight(u.HomeDir, "/"), nil
33 }
0 package util
1
2 import (
3 "os"
4 "strings"
5 )
6
7 // Getwd returns path of the working directory in a format suitable as the
8 // prompt.
9 func Getwd() string {
10 pwd, err := os.Getwd()
11 if err != nil {
12 return "?"
13 }
14 return TildeAbbr(pwd)
15 }
16
17 // TildeAbbr abbreviates the user's home directory to ~.
18 func TildeAbbr(path string) string {
19 home, err := GetHome("")
20 if err == nil {
21 if path == home {
22 return "~"
23 } else if strings.HasPrefix(path, home+"/") {
24 return "~" + path[len(home):]
25 }
26 }
27 return path
28 }
0 package util
1
2 import (
3 "os"
4 "path"
5 "path/filepath"
6 "runtime"
7 "testing"
8 )
9
10 func TestGetwd(t *testing.T) {
11 InTempDir(func(tmpdir string) {
12 // On some systems /tmp is a symlink.
13 tmpdir, err := filepath.EvalSymlinks(tmpdir)
14 if err != nil {
15 panic(err)
16 }
17 if gotwd := Getwd(); gotwd != tmpdir {
18 t.Errorf("Getwd() -> %v, want %v", gotwd, tmpdir)
19 }
20
21 // Override $HOME to trick GetHome.
22 os.Setenv("HOME", tmpdir)
23
24 if gotwd := Getwd(); gotwd != "~" {
25 t.Errorf("Getwd() -> %v, want ~", gotwd)
26 }
27
28 mustOK(os.Mkdir("a", 0700))
29 mustOK(os.Chdir("a"))
30 if gotwd := Getwd(); gotwd != "~/a" {
31 t.Errorf("Getwd() -> %v, want ~/a", gotwd)
32 }
33
34 // On macOS os.Getwd will still return the old path name in face of
35 // directory being removed. Hence we only test this on Linux.
36 // TODO(xiaq): Check the behavior on other BSDs and relax this condition
37 // if possible.
38 if runtime.GOOS == "linux" {
39 mustOK(os.Remove(path.Join(tmpdir, "a")))
40 if gotwd := Getwd(); gotwd != "?" {
41 t.Errorf("Getwd() -> %v, want ?", gotwd)
42 }
43 }
44 })
45 }
46
47 func mustOK(err error) {
48 if err != nil {
49 panic(err)
50 }
51 }
0 package util
1
2 import (
3 "io/ioutil"
4 "os"
5 )
6
7 // InTempDir creates a new temporary directory, cd into it, and runs a function,
8 // passing the path of the temporary directory. After the function returns, it
9 // goes back to the original directory if possible, and remove the temporary
10 // directory. It panics if it cannot make a temporary directory or cd into it.
11 //
12 // It is useful in tests.
13 func InTempDir(f func(string)) {
14 tmpdir, err := ioutil.TempDir("", "elvishtest.")
15 if err != nil {
16 panic(err)
17 }
18 defer os.RemoveAll(tmpdir)
19
20 pwd, err := os.Getwd()
21 if err != nil {
22 defer os.Chdir(pwd)
23 }
24
25 err = os.Chdir(tmpdir)
26 if err != nil {
27 panic(err)
28 }
29 f(tmpdir)
30 }
0 package util
1
2 const (
3 MaxUint = ^uint(0)
4 MinUint = 0
5 MaxInt = int(MaxUint >> 1)
6 MinInt = -MaxInt - 1
7 )
0 package util
1
2 import (
3 "io"
4 "io/ioutil"
5 "log"
6 "os"
7 )
8
9 var (
10 out io.Writer = ioutil.Discard
11 logFile *os.File
12 loggers []*log.Logger
13 )
14
15 // GetLogger gets a logger with a prefix.
16 func GetLogger(prefix string) *log.Logger {
17 logger := log.New(out, prefix, log.LstdFlags)
18 loggers = append(loggers, logger)
19 return logger
20 }
21
22 func setOutput(newout io.Writer) {
23 out = newout
24 for _, logger := range loggers {
25 logger.SetOutput(out)
26 }
27 }
28
29 func SetOutputFile(fname string) error {
30 if logFile != nil {
31 logFile.Close()
32 }
33 if fname == "" {
34 logFile = nil
35 setOutput(ioutil.Discard)
36 return nil
37 }
38 var err error
39 logFile, err = os.OpenFile(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
40 if err != nil {
41 return err
42 }
43 setOutput(logFile)
44 return nil
45 }
0 package util
1
2 type Pprinter interface {
3 Pprint(indent string) string
4 }
0 package util
1
2 import (
3 "errors"
4 "io/ioutil"
5 "os"
6 "strings"
7 )
8
9 var (
10 ErrNotExecutable = errors.New("not executable")
11 ErrNotFound = errors.New("not found")
12 )
13
14 // Search tries to resolve an external command and return the full (possibly
15 // relative) path.
16 func Search(paths []string, exe string) (string, error) {
17 if DontSearch(exe) {
18 if IsExecutable(exe) {
19 return exe, nil
20 }
21 return "", ErrNotExecutable
22 }
23 for _, p := range paths {
24 full := p + "/" + exe
25 if IsExecutable(full) {
26 return full, nil
27 }
28 }
29 return "", ErrNotFound
30 }
31
32 // EachExecutable calls f for each executable file in paths.
33 func EachExecutable(paths []string, f func(string)) {
34 for _, dir := range paths {
35 // XXX Ignore error
36 infos, _ := ioutil.ReadDir(dir)
37 for _, info := range infos {
38 if !info.IsDir() && (info.Mode()&0111 != 0) {
39 f(info.Name())
40 }
41 }
42 }
43 }
44
45 // DontSearch determines whether the path to an external command should be
46 // taken literally and not searched.
47 func DontSearch(exe string) bool {
48 return exe == ".." || strings.ContainsRune(exe, '/')
49 }
50
51 // IsExecutable determines whether path refers to an executable file.
52 func IsExecutable(path string) bool {
53 fi, err := os.Stat(path)
54 if err != nil {
55 return false
56 }
57 fm := fi.Mode()
58 return !fm.IsDir() && (fm&0111 != 0)
59 }
0 package util
1
2 import (
3 "fmt"
4 "io"
5 "strings"
6 )
7
8 type SourceContext struct {
9 Name string
10 Source string
11 Begin int
12 End int
13 Next *SourceContext
14 }
15
16 var CulpritStyle = "1;4"
17
18 func (sc *SourceContext) Pprint(w io.Writer, sourceIndent string) {
19 if sc.Begin == -1 {
20 fmt.Fprintf(w, "%s, unknown position", sc.Name)
21 return
22 } else if sc.Begin < 0 || sc.End > len(sc.Source) || sc.Begin > sc.End {
23 fmt.Fprintf(w, "%s, invalid position %d-%d", sc.Name, sc.Begin, sc.End)
24 return
25 }
26
27 before, culprit, after := bca(sc.Source, sc.Begin, sc.End)
28 // Find the part of "before" that is on the same line as the culprit.
29 lineBefore := lastLine(before)
30 // Find on which line the culprit begins.
31 beginLine := strings.Count(before, "\n") + 1
32
33 // If the culprit ends with a newline, stripe it. Otherwise stick the part
34 // of "after" that is on the same line of the last line of the culprit.
35 var lineAfter string
36 if strings.HasSuffix(culprit, "\n") {
37 culprit = culprit[:len(culprit)-1]
38 } else {
39 lineAfter = firstLine(after)
40 }
41
42 // Find on which line and column the culprit ends.
43 endLine := beginLine + strings.Count(culprit, "\n")
44
45 if beginLine == endLine {
46 fmt.Fprintf(w, "%s, line %d:\n", sc.Name, beginLine)
47 } else {
48 fmt.Fprintf(w, "%s, line %d-%d:\n", sc.Name, beginLine, endLine)
49 }
50
51 fmt.Fprintf(w, "%s%s", sourceIndent, lineBefore)
52
53 if culprit == "" {
54 culprit = "^"
55 }
56 for i, line := range strings.Split(culprit, "\n") {
57 if i > 0 {
58 fmt.Fprintf(w, "\n%s", sourceIndent)
59 }
60 fmt.Fprintf(w, "\033[%sm%s\033[m", CulpritStyle, line)
61 }
62
63 fmt.Fprintf(w, "%s", lineAfter)
64 }
65
66 func bca(s string, a, b int) (string, string, string) {
67 return s[:a], s[a:b], s[b:]
68 }
69
70 func countRunes(s string) int {
71 return strings.Count(s, "") - 1
72 }
73
74 func firstLine(s string) string {
75 i := strings.IndexByte(s, '\n')
76 if i == -1 {
77 return s
78 }
79 return s[:i]
80 }
81
82 func lastLine(s string) string {
83 // When s does not contain '\n', LastIndexByte returns -1, which happens to
84 // be what we want.
85 return s[strings.LastIndexByte(s, '\n')+1:]
86 }
0 package util
1
2 import (
3 "errors"
4 "strings"
5 )
6
7 // ErrIndexOutOfRange is returned when out-of-range errors occur.
8 var ErrIndexOutOfRange = errors.New("substring out of range")
9
10 // FindContext takes a position in a text and finds its line number,
11 // corresponding line and column numbers. Line and column numbers are counted
12 // from 0. Used in diagnostic messages.
13 func FindContext(text string, pos int) (lineno, colno int, line string) {
14 var i, linestart int
15 var r rune
16 for i, r = range text {
17 if i == pos {
18 break
19 }
20 if r == '\n' {
21 lineno++
22 linestart = i + 1
23 colno = 0
24 } else {
25 colno++
26 }
27 }
28 line = strings.SplitN(text[linestart:], "\n", 2)[0]
29 return
30 }
31
32 // FindFirstEOL returns the index of the first '\n'. When there is no '\n', the
33 // length of s is returned.
34 func FindFirstEOL(s string) int {
35 eol := strings.IndexRune(s, '\n')
36 if eol == -1 {
37 eol = len(s)
38 }
39 return eol
40 }
41
42 // FindLastSOL returns an index just after the last '\n'.
43 func FindLastSOL(s string) int {
44 return strings.LastIndex(s, "\n") + 1
45 }
46
47 // SubstringByRune returns the range of the i-th rune (inclusive) through the
48 // j-th rune (exclusive) in s.
49 func SubstringByRune(s string, low, high int) (string, error) {
50 if low > high || low < 0 || high < 0 {
51 return "", ErrIndexOutOfRange
52 }
53 var bLow, bHigh, j int
54 for i := range s {
55 if j == low {
56 bLow = i
57 }
58 if j == high {
59 bHigh = i
60 }
61 j++
62 }
63 if j < high {
64 return "", ErrIndexOutOfRange
65 }
66 if low == high {
67 return "", nil
68 }
69 if j == high {
70 bHigh = len(s)
71 }
72 return s[bLow:bHigh], nil
73 }
74
75 // NthRune returns the n-th rune of s.
76 func NthRune(s string, n int) (rune, error) {
77 if n < 0 {
78 return 0, ErrIndexOutOfRange
79 }
80 var j int
81 for _, r := range s {
82 if j == n {
83 return r, nil
84 }
85 j++
86 }
87 return 0, ErrIndexOutOfRange
88 }
89
90 // MatchSubseq returns whether pattern is a subsequence of s.
91 func MatchSubseq(s, pattern string) bool {
92 for _, p := range pattern {
93 i := strings.IndexRune(s, p)
94 if i == -1 {
95 return false
96 }
97 s = s[i+len(string(p)):]
98 }
99 return true
100 }
0 package util
1
2 import "testing"
3
4 var findContextTests = []struct {
5 text string
6 pos int
7 lineno, colno int
8 line string
9 }{
10 {"a\nb", 2, 1, 0, "b"},
11 }
12
13 func TestFindContext(t *testing.T) {
14 for _, tt := range findContextTests {
15 lineno, colno, line := FindContext(tt.text, tt.pos)
16 if lineno != tt.lineno || colno != tt.colno || line != tt.line {
17 t.Errorf("FindContext(%v, %v) => (%v, %v, %v), want (%v, %v, %v)",
18 tt.text, tt.pos, lineno, colno, line, tt.lineno, tt.colno, tt.line)
19 }
20 }
21 }
22
23 var SubstringByRuneTests = []struct {
24 s string
25 low, high int
26 wantedStr string
27 wantedErr error
28 }{
29 {"Hello world", 1, 4, "ell", nil},
30 {"你好世界", 0, 0, "", nil},
31 {"你好世界", 1, 1, "", nil},
32 {"你好世界", 1, 2, "好", nil},
33 {"你好世界", 1, 4, "好世界", nil},
34 {"你好世界", -1, -1, "", ErrIndexOutOfRange},
35 {"你好世界", 0, 5, "", ErrIndexOutOfRange},
36 {"你好世界", 5, 5, "", ErrIndexOutOfRange},
37 }
38
39 func TestSubstringByRune(t *testing.T) {
40 for _, tt := range SubstringByRuneTests {
41 s, e := SubstringByRune(tt.s, tt.low, tt.high)
42 if s != tt.wantedStr || e != tt.wantedErr {
43 t.Errorf("SubstringByRune(%q, %v, %d) => (%q, %v), want (%q, %v)",
44 tt.s, tt.low, tt.high, s, e, tt.wantedStr, tt.wantedErr)
45 }
46 }
47 }
48
49 var NthRuneTests = []struct {
50 s string
51 n int
52 wantedRune rune
53 wantedErr error
54 }{
55 {"你好世界", -1, 0, ErrIndexOutOfRange},
56 {"你好世界", 0, '你', nil},
57 {"你好世界", 4, 0, ErrIndexOutOfRange},
58 }
59
60 func TestNthRune(t *testing.T) {
61 for _, tt := range NthRuneTests {
62 r, e := NthRune(tt.s, tt.n)
63 if r != tt.wantedRune || e != tt.wantedErr {
64 t.Errorf("NthRune(%q, %v) => (%q, %v), want (%q, %v)",
65 tt.s, tt.n, r, e, tt.wantedRune, tt.wantedErr)
66 }
67 }
68 }
69
70 var EOLSOLTests = []struct {
71 s string
72 wantFirstEOL, wantLastSOL int
73 }{
74 {"0", 1, 0},
75 {"\n12", 0, 1},
76 {"01\n", 2, 3},
77 {"01\n34", 2, 3},
78 }
79
80 func TestEOLSOL(t *testing.T) {
81 for _, tc := range EOLSOLTests {
82 eol := FindFirstEOL(tc.s)
83 if eol != tc.wantFirstEOL {
84 t.Errorf("FindFirstEOL(%q) => %d, want %d", tc.s, eol, tc.wantFirstEOL)
85 }
86 sol := FindLastSOL(tc.s)
87 if sol != tc.wantLastSOL {
88 t.Errorf("FindLastSOL(%q) => %d, want %d", tc.s, sol, tc.wantLastSOL)
89 }
90 }
91 }
92
93 var MatchSubseqTests = []struct {
94 s, p string
95 want bool
96 }{
97 {"elvish", "e", true},
98 {"elvish", "elh", true},
99 {"elvish", "sh", true},
100 {"elves/elvish", "l/e", true},
101 {"elves/elvish", "e/e", true},
102 {"elvish", "le", false},
103 {"elvish", "evii", false},
104 }
105
106 func TestMatchSubseq(t *testing.T) {
107 for _, tc := range MatchSubseqTests {
108 b := MatchSubseq(tc.s, tc.p)
109 if b != tc.want {
110 t.Errorf("MatchSubseq(%q, %q) -> %v, want %v", tc.s, tc.p, b, tc.want)
111 }
112 }
113 }
0 package util
1
2 import "unicode/utf8"
3
4 func HasSubseq(s, t string) bool {
5 i, j := 0, 0
6 for i < len(s) && j < len(t) {
7 s0, di := utf8.DecodeRuneInString(s[i:])
8 t0, dj := utf8.DecodeRuneInString(t[j:])
9 i += di
10 if s0 == t0 {
11 j += dj
12 }
13 }
14 return j == len(t)
15 }
0 package util
1
2 import "testing"
3
4 var hasSubseqTests = []struct {
5 s, t string
6 want bool
7 }{
8 {"", "", true},
9 {"a", "", true},
10 {"a", "a", true},
11 {"ab", "a", true},
12 {"ab", "b", true},
13 {"abc", "ac", true},
14 {"abcdefg", "bg", true},
15 {"abcdefg", "ga", false},
16 {"foo lorem ipsum", "f l i", true},
17 {"foo lorem ipsum", "oo o pm", true},
18 {"你好世界", "好", true},
19 {"你好世界", "好界", true},
20 }
21
22 func TestHasSubseq(t *testing.T) {
23 for _, test := range hasSubseqTests {
24 if b := HasSubseq(test.s, test.t); b != test.want {
25 t.Errorf("HasSubseq(%q, %q) = %v, want %v", test.s, test.t, b, test.want)
26 }
27 }
28 }
0 package util
1
2 // Thrown wraps an error that was raised by Throw, so that it can be recognized
3 // by Catch.
4 type Thrown struct {
5 Error error
6 }
7
8 // Throw panics with err wrapped properly so that it can be catched by Catch.
9 func Throw(err error) {
10 panic(Thrown{err})
11 }
12
13 // Catch tries to catch an error thrown by Throw and stop the panic. If the
14 // panic is not caused by Throw, the panic is not stopped. It should be called
15 // directly from defer.
16 func Catch(perr *error) {
17 r := recover()
18 if r == nil {
19 return
20 }
21 if exc, ok := r.(Thrown); ok {
22 *perr = exc.Error
23 } else {
24 panic(r)
25 }
26 }
27
28 // PCall calls a function and catches anything Thrown'n and returns it. It does
29 // not protect against panics not using Throw, nor can it distinguish between
30 // nothing thrown and Throw(nil).
31 func PCall(f func()) (e error) {
32 defer Catch(&e)
33 f()
34 // If we reach here, f didn't throw anything.
35 return nil
36 }
37
38 // Throws returns whether calling f throws out a certain error (using Throw). It
39 // is useful for testing.
40 func Throws(f func(), e error) bool {
41 return PCall(f) == e
42 }
43
44 // DoesntThrow returns whether calling f does not throw anything. It is useful
45 // for testing.
46 func DoesntThrow(f func()) bool {
47 return PCall(f) == nil
48 }
0 package util
1
2 import (
3 "errors"
4 "testing"
5 )
6
7 func recoverPanic(f func()) (recovered interface{}) {
8 defer func() {
9 recovered = recover()
10 }()
11 f()
12 return nil
13 }
14
15 func TestThrowAndCatch(t *testing.T) {
16 tothrow := errors.New("an error to throw")
17 // Throw should cause a panic
18 f := func() {
19 Throw(tothrow)
20 }
21 if recoverPanic(f) == nil {
22 t.Errorf("Throw did not cause a panic")
23 }
24
25 // Catch should catch what was thrown
26 caught := func() (err error) {
27 defer Catch(&err)
28 Throw(tothrow)
29 return nil
30 }()
31 if caught != tothrow {
32 t.Errorf("thrown %v, but caught %v", tothrow, caught)
33 }
34
35 // Catch should not recover panics not caused by Throw
36 var err error
37 f = func() {
38 defer Catch(&err)
39 panic(errors.New("233"))
40 }
41 _ = recoverPanic(f)
42 if err != nil {
43 t.Errorf("Catch recovered panic not caused via Throw")
44 }
45
46 // Catch should do nothing when there is no panic
47 err = nil
48 f = func() {
49 defer Catch(&err)
50 }
51 f()
52 if err != nil {
53 t.Errorf("Catch recovered something when there is no panic")
54 }
55 }
56
57 func TestPCallThrowsDoesntThrow(t *testing.T) {
58 errToThrow := errors.New("error to throw")
59
60 // PCall catches throws
61 if PCall(func() { Throw(errToThrow) }) != errToThrow {
62 t.Errorf("PCall does not catch throws")
63 }
64 // PCall returns nil when nothing has been thrown
65 if PCall(func() {}) != nil {
66 t.Errorf("PCall returns non-nil when nothing has been thrown")
67 }
68 // PCall returns nil when nil has been thrown
69 if PCall(func() { Throw(nil) }) != nil {
70 t.Errorf("PCall returns non-nil when nil has been thrown")
71 }
72
73 if Throws(func() { Throw(errToThrow) }, errToThrow) != true {
74 t.Errorf("Throws returns false when function throws wanted error")
75 }
76 if Throws(func() { Throw(errToThrow) }, errors.New("")) != false {
77 t.Errorf("Throws returns true when function throws unwanted error")
78 }
79 if Throws(func() {}, errToThrow) != false {
80 t.Errorf("Throws returns true when function does not throw")
81 }
82
83 if DoesntThrow(func() { Throw(errToThrow) }) != false {
84 t.Errorf("DoesntThrow returns true when function throws")
85 }
86 if DoesntThrow(func() {}) != true {
87 t.Errorf("DoesntThrow returns false when function doesn't throw")
88 }
89 }
0 // Package util contains utility functions.
1 package util
0 package util
1
2 import (
3 "sort"
4 "strings"
5 )
6
7 var wcwidthOverride = map[rune]int{}
8
9 // Taken from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c (public domain)
10 var combining = [][2]rune{
11 {0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489},
12 {0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2},
13 {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603},
14 {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670},
15 {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED},
16 {0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A},
17 {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902},
18 {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D},
19 {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981},
20 {0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD},
21 {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C},
22 {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D},
23 {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC},
24 {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD},
25 {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C},
26 {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D},
27 {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0},
28 {0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48},
29 {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC},
30 {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD},
31 {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D},
32 {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6},
33 {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E},
34 {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC},
35 {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35},
36 {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E},
37 {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97},
38 {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030},
39 {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039},
40 {0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F},
41 {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753},
42 {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD},
43 {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD},
44 {0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922},
45 {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B},
46 {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34},
47 {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42},
48 {0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF},
49 {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063},
50 {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F},
51 {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B},
52 {0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F},
53 {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB},
54 {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F},
55 {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169},
56 {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD},
57 {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F},
58 {0xE0100, 0xE01EF},
59 }
60
61 func isCombining(r rune) bool {
62 n := len(combining)
63 i := sort.Search(n, func(i int) bool { return r <= combining[i][1] })
64 return i < n && r >= combining[i][0]
65 }
66
67 // Wcwidth returns the width of a rune when displayed on the terminal.
68 func Wcwidth(r rune) int {
69 if w, ok := wcwidthOverride[r]; ok {
70 return w
71 }
72 if r == 0 ||
73 r < 32 || (0x7f <= r && r < 0xa0) || // Control character
74 isCombining(r) {
75 return 0
76 }
77
78 if r >= 0x1100 &&
79 (r <= 0x115f || /* Hangul Jamo init. consonants */
80 r == 0x2329 || r == 0x232a ||
81 (r >= 0x2e80 && r <= 0xa4cf &&
82 r != 0x303f) || /* CJK ... Yi */
83 (r >= 0xac00 && r <= 0xd7a3) || /* Hangul Syllables */
84 (r >= 0xf900 && r <= 0xfaff) || /* CJK Compatibility Ideographs */
85 (r >= 0xfe10 && r <= 0xfe19) || /* Vertical forms */
86 (r >= 0xfe30 && r <= 0xfe6f) || /* CJK Compatibility Forms */
87 (r >= 0xff00 && r <= 0xff60) || /* Fullwidth Forms */
88 (r >= 0xffe0 && r <= 0xffe6) || /* Fullwidth Forms */
89 (r >= 0x20000 && r <= 0x2fffd) || /* CJK Extensions */
90 (r >= 0x30000 && r <= 0x3fffd) || /* Reserved for historical Chinese scripts */
91 (r >= 0x1f300 && r <= 0x1f6ff)) { // Miscellaneous Symbols and Pictographs ... Geometric Shapes Extended
92 return 2
93 }
94 return 1
95 }
96
97 // OverrideWcwidth overrides the wcwidth of a rune to be a specific non-negative
98 // value. OverrideWcwidth panics if w < 0.
99 func OverrideWcwidth(r rune, w int) {
100 if w < 0 {
101 panic("negative width")
102 }
103 wcwidthOverride[r] = w
104 }
105
106 // UnoverrideWcwidth removes the override of a rune.
107 func UnoverrideWcwidth(r rune) {
108 delete(wcwidthOverride, r)
109 }
110
111 // Wcswidth returns the width of a string when displayed on the terminal,
112 // assuming no soft line breaks.
113 func Wcswidth(s string) (w int) {
114 for _, r := range s {
115 w += Wcwidth(r)
116 }
117 return
118 }
119
120 // TrimWcwidth trims the string s so that it has a width of at most wmax.
121 func TrimWcwidth(s string, wmax int) string {
122 w := 0
123 for i, r := range s {
124 w += Wcwidth(r)
125 if w > wmax {
126 return s[:i]
127 }
128 }
129 return s
130 }
131
132 // ForceWcwidth forces the string s to the given display width by trimming and
133 // padding.
134 func ForceWcwidth(s string, width int) string {
135 w := 0
136 for i, r := range s {
137 w0 := Wcwidth(r)
138 w += w0
139 if w > width {
140 w -= w0
141 s = s[:i]
142 break
143 }
144 }
145 return s + strings.Repeat(" ", width-w)
146 }
147
148 func TrimEachLineWcwidth(s string, width int) string {
149 lines := strings.Split(s, "\n")
150 for i := range lines {
151 lines[i] = TrimWcwidth(lines[i], width)
152 }
153 return strings.Join(lines, "\n")
154 }
0 package util
1
2 import (
3 "testing"
4 )
5
6 var wcwidthTests = []struct {
7 in rune
8 wanted int
9 }{
10 {'\u0301', 0}, // Combining acute accent
11 {'a', 1},
12 {'Ω', 1},
13 {'好', 2},
14 {'か', 2},
15 }
16
17 func TestWcwidth(t *testing.T) {
18 for _, tt := range wcwidthTests {
19 out := Wcwidth(tt.in)
20 if out != tt.wanted {
21 t.Errorf("wcwidth(%q) => %v, want %v", tt.in, out, tt.wanted)
22 }
23 }
24 }
25
26 func TestOverrideWcwidth(t *testing.T) {
27 r := '❱'
28 oldw := Wcwidth(r)
29 w := oldw + 1
30
31 OverrideWcwidth(r, w)
32 if Wcwidth(r) != w {
33 t.Errorf("Wcwidth(%q) != %d after OverrideWcwidth", r, w)
34 }
35 UnoverrideWcwidth(r)
36 if Wcwidth(r) != oldw {
37 t.Errorf("Wcwidth(%q) != %d after UnoverrideWcwidth", r, oldw)
38 }
39 }
0 #!/usr/bin/env elvish
1
2 out = ./embedded-html.go
3
4 {
5 echo "package web"
6 print "const mainPageHTML = `"
7 cat main.html | sed 's/`/`+"`"+`/g'
8 echo "`"
9 } > $out
10
11 gofmt -w $out
0 package web
1
2 const mainPageHTML = `<html>
3
4 <div id="scrollback">
5 <div id="progress"></div>
6 </div>
7 <textarea id="code" rows="4"></textarea>
8
9 <style>
10 #code {
11 width: 100%;
12 }
13 #scrollback, #code, #progress {
14 font-family: monospace;
15 font-size: 11pt;
16 }
17 .code {
18 color: white;
19 background-color: black;
20 }
21 .error, .exception {
22 color: red;
23 }
24 .exception {
25 font-weight: bold;
26 }
27 .server-error {
28 color: white;
29 background-color: red;
30 }
31 </style>
32
33 <script>
34 // TODO(xiaq): Stream results.
35 var $scrollback = document.getElementById('scrollback'),
36 $code = document.getElementById('code'),
37 $progress = document.getElementById('progress');
38
39 $code.addEventListener('keypress', function(e) {
40 if (e.keyCode == 13 &&
41 !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
42 e.preventDefault();
43 execute();
44 }
45 });
46
47 function execute() {
48 var code = $code.value;
49 $code.value = '';
50 addToScrollback('code', code);
51 $progress.innerText = 'executing...';
52
53 var req = new XMLHttpRequest();
54 req.onloadend = function() {
55 $progress.innerText = '';
56 };
57 req.onload = function() {
58 var res = JSON.parse(req.responseText);
59 addToScrollback('output', res.OutBytes);
60 if (res.OutValues) {
61 for (var v of res.OutValues) {
62 addToScrollback('output-value', v);
63 }
64 }
65 addToScrollback('error', res.ErrBytes);
66 addToScrollback('exception', res.Err);
67 };
68 req.onerror = function() {
69 addToScrollback('server-error', req.responseText
70 || req.statusText
71 || (req.status == req.UNSENT && "lost connection")
72 || "unknown error");
73 };
74 req.open('POST', '/execute');
75 req.send(code);
76 }
77
78 function addToScrollback(className, innerText) {
79 var $div = document.createElement('div')
80 $div.className = className;
81 $div.innerText = innerText;
82 $scrollback.insertBefore($div, $progress);
83
84 window.scrollTo(0, document.body.scrollHeight);
85 }
86
87 </script>
88
89 </html>
90 `
0 <html>
1
2 <div id="scrollback">
3 <div id="progress"></div>
4 </div>
5 <textarea id="code" rows="4"></textarea>
6
7 <style>
8 #code {
9 width: 100%;
10 }
11 #scrollback, #code, #progress {
12 font-family: monospace;
13 font-size: 11pt;
14 }
15 .code {
16 color: white;
17 background-color: black;
18 }
19 .error, .exception {
20 color: red;
21 }
22 .exception {
23 font-weight: bold;
24 }
25 .server-error {
26 color: white;
27 background-color: red;
28 }
29 </style>
30
31 <script>
32 // TODO(xiaq): Stream results.
33 var $scrollback = document.getElementById('scrollback'),
34 $code = document.getElementById('code'),
35 $progress = document.getElementById('progress');
36
37 $code.addEventListener('keypress', function(e) {
38 if (e.keyCode == 13 &&
39 !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
40 e.preventDefault();
41 execute();
42 }
43 });
44
45 function execute() {
46 var code = $code.value;
47 $code.value = '';
48 addToScrollback('code', code);
49 $progress.innerText = 'executing...';
50
51 var req = new XMLHttpRequest();
52 req.onloadend = function() {
53 $progress.innerText = '';
54 };
55 req.onload = function() {
56 var res = JSON.parse(req.responseText);
57 addToScrollback('output', res.OutBytes);
58 if (res.OutValues) {
59 for (var v of res.OutValues) {
60 addToScrollback('output-value', v);
61 }
62 }
63 addToScrollback('error', res.ErrBytes);
64 addToScrollback('exception', res.Err);
65 };
66 req.onerror = function() {
67 addToScrollback('server-error', req.responseText
68 || req.statusText
69 || (req.status == req.UNSENT && "lost connection")
70 || "unknown error");
71 };
72 req.open('POST', '/execute');
73 req.send(code);
74 }
75
76 function addToScrollback(className, innerText) {
77 var $div = document.createElement('div')
78 $div.className = className;
79 $div.innerText = innerText;
80 $scrollback.insertBefore($div, $progress);
81
82 window.scrollTo(0, document.body.scrollHeight);
83 }
84
85 </script>
86
87 </html>
0 // Package web is the entry point for the backend of the web interface of
1 // Elvish.
2 package web
3
4 //go:generate ./embed-html
5
6 import (
7 "encoding/json"
8 "fmt"
9 "io"
10 "io/ioutil"
11 "log"
12 "net/http"
13 "os"
14
15 "github.com/elves/elvish/eval"
16 "github.com/elves/elvish/parse"
17 )
18
19 type Web struct {
20 ev *eval.Evaler
21 port int
22 }
23
24 type ExecuteResponse struct {
25 OutBytes string
26 OutValues []eval.Value
27 ErrBytes string
28 Err string
29 }
30
31 func NewWeb(ev *eval.Evaler, port int) *Web {
32 return &Web{ev, port}
33 }
34
35 func (web *Web) Run(args []string) int {
36 if len(args) > 0 {
37 fmt.Fprintln(os.Stderr, "arguments to -web are not supported yet")
38 return 2
39 }
40
41 http.HandleFunc("/", web.handleMainPage)
42 http.HandleFunc("/execute", web.handleExecute)
43 addr := fmt.Sprintf("localhost:%d", web.port)
44 log.Println("going to listen", addr)
45 err := http.ListenAndServe(addr, nil)
46
47 log.Println(err)
48 return 0
49 }
50
51 func (web *Web) handleMainPage(w http.ResponseWriter, r *http.Request) {
52 _, err := w.Write([]byte(mainPageHTML))
53 if err != nil {
54 log.Println("cannot write response:", err)
55 }
56 }
57
58 func (web *Web) handleExecute(w http.ResponseWriter, r *http.Request) {
59 bytes, err := ioutil.ReadAll(r.Body)
60 if err != nil {
61 log.Println("cannot read request body:", err)
62 return
63 }
64 text := string(bytes)
65
66 outBytes, outValues, errBytes, err := evalAndCollect(web.ev, "<web>", text)
67 errText := ""
68 if err != nil {
69 errText = err.Error()
70 }
71 responseBody, err := json.Marshal(
72 &ExecuteResponse{string(outBytes), outValues, string(errBytes), errText})
73 if err != nil {
74 log.Println("cannot marshal response body:", err)
75 }
76
77 _, err = w.Write(responseBody)
78 if err != nil {
79 log.Println("cannot write response:", err)
80 }
81 }
82
83 const (
84 outFileBufferSize = 1024
85 outChanBufferSize = 32
86 )
87
88 // evalAndCollect evaluates a piece of code with null stdin, and stdout and
89 // stderr connected to pipes (value part of stderr being a blackhole), and
90 // return the results collected on stdout and stderr, and the possible error
91 // that occurred.
92 func evalAndCollect(ev *eval.Evaler, name, text string) (
93 outBytes []byte, outValues []eval.Value, errBytes []byte, err error) {
94
95 node, err := parse.Parse(name, text)
96 if err != nil {
97 return
98 }
99 op, err := ev.Compile(node, name, text)
100 if err != nil {
101 return
102 }
103
104 outFile, chanOutBytes := makeBytesWriterAndCollect()
105 outChan, chanOutValues := makeValuesWriterAndCollect()
106 errFile, chanErrBytes := makeBytesWriterAndCollect()
107
108 ports := []*eval.Port{
109 eval.DevNullClosedChan,
110 {File: outFile, Chan: outChan},
111 {File: errFile, Chan: eval.BlackholeChan},
112 }
113 err = ev.EvalWithPorts(ports, op, name, text)
114
115 outFile.Close()
116 close(outChan)
117 errFile.Close()
118 return <-chanOutBytes, <-chanOutValues, <-chanErrBytes, err
119 }
120
121 // makeBytesWriterAndCollect makes an in-memory file that can be written to, and
122 // the written bytes will be collected in a byte slice that will be put on a
123 // channel as soon as the writer is closed.
124 func makeBytesWriterAndCollect() (*os.File, <-chan []byte) {
125 r, w, err := os.Pipe()
126 // os.Pipe returns error only on resource exhaustion.
127 if err != nil {
128 panic(err)
129 }
130 chanCollected := make(chan []byte)
131
132 go func() {
133 var (
134 collected []byte
135 buf [outFileBufferSize]byte
136 )
137 for {
138 n, err := r.Read(buf[:])
139 collected = append(collected, buf[:n]...)
140 if err != nil {
141 if err != io.EOF {
142 log.Println("error when reading output pipe:", err)
143 }
144 break
145 }
146 }
147 r.Close()
148 chanCollected <- collected
149 }()
150
151 return w, chanCollected
152 }
153
154 // makeValuesWriterAndCollect makes a Value channel for writing, and the written
155 // values will be collected in a Value slice that will be put on a channel as
156 // soon as the writer is closed.
157 func makeValuesWriterAndCollect() (chan eval.Value, <-chan []eval.Value) {
158 chanValues := make(chan eval.Value, outChanBufferSize)
159 chanCollected := make(chan []eval.Value)
160
161 go func() {
162 var collected []eval.Value
163 for {
164 for v := range chanValues {
165 collected = append(collected, v)
166 }
167 chanCollected <- collected
168 }
169 }()
170
171 return chanValues, chanCollected
172 }
0 package web
1
2 import "testing"
3
4 func TestWeb(t *testing.T) {
5 // TODO(xiaq): Add tests.
6 }