├── go.sum ├── go.mod ├── msgpack ├── doc.go ├── testdata │ ├── api.mpack.gz │ ├── api_metadata.mpack.gz │ └── funcs_data.mpack.gz ├── msgpack.go ├── field.go ├── rpc │ ├── rpc_test.go │ └── rpc.go ├── encode_test.go ├── pack_test.go ├── msgpack_test.go ├── pack.go ├── encode.go ├── unpack.go ├── unpack_test.go └── decode.go ├── examples └── remote │ ├── helloremote │ ├── go.mod │ ├── go.sum │ └── main.go │ ├── plugin │ └── hello.lua │ └── README.md ├── nvim ├── nvim_unix.go ├── nvim_windows.go ├── plugin │ ├── plugin_bench_test.go │ ├── example_test.go │ ├── eval_test.go │ ├── README.md │ ├── manifest_test.go │ ├── main.go │ ├── plugin_test.go │ └── plugin.go ├── doc.go ├── helpers_test.go ├── example_test.go ├── helpers.go ├── nvimtest │ └── nvimtest.go ├── types_test.go ├── ext_test.go ├── nvim_test.go ├── api_deprecated.go └── api_tool.go ├── .github ├── labeler.yaml └── workflows │ ├── labeler.yaml │ ├── codeql.yml │ ├── benchmark.yml │ └── test.yml ├── .gitignore ├── .codecov.yaml ├── README.md └── LICENSE /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/neovim/go-client 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /msgpack/doc.go: -------------------------------------------------------------------------------- 1 | // Package msgpack implements MessagePack encoding and decoding. 2 | package msgpack 3 | -------------------------------------------------------------------------------- /msgpack/testdata/api.mpack.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neovim/go-client/HEAD/msgpack/testdata/api.mpack.gz -------------------------------------------------------------------------------- /msgpack/testdata/api_metadata.mpack.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neovim/go-client/HEAD/msgpack/testdata/api_metadata.mpack.gz -------------------------------------------------------------------------------- /msgpack/testdata/funcs_data.mpack.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neovim/go-client/HEAD/msgpack/testdata/funcs_data.mpack.gz -------------------------------------------------------------------------------- /examples/remote/helloremote/go.mod: -------------------------------------------------------------------------------- 1 | module example.com/remote/helloremote 2 | 3 | go 1.22.2 4 | 5 | require github.com/neovim/go-client v1.2.1 6 | -------------------------------------------------------------------------------- /nvim/nvim_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package nvim 5 | 6 | // BinaryName is the name of default nvim binary name. 7 | const BinaryName = "nvim" 8 | -------------------------------------------------------------------------------- /.github/labeler.yaml: -------------------------------------------------------------------------------- 1 | # https://github.com/actions/labeler 2 | --- 3 | 4 | area/ci: 5 | - .github/** 6 | 7 | area/msgpack: 8 | - msgpack/**/*.go 9 | 10 | area/nvim: 11 | - nvim/**/*.go 12 | -------------------------------------------------------------------------------- /examples/remote/helloremote/go.sum: -------------------------------------------------------------------------------- 1 | github.com/neovim/go-client v1.2.1 h1:kl3PgYgbnBfvaIoGYi3ojyXH0ouY6dJY/rYUCssZKqI= 2 | github.com/neovim/go-client v1.2.1/go.mod h1:EeqCP3z1vJd70JTaH/KXz9RMZ/nIgEFveX83hYnh/7c= 3 | -------------------------------------------------------------------------------- /nvim/nvim_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package nvim 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | // BinaryName is the name of default nvim binary name. 11 | const BinaryName = "nvim.exe" 12 | 13 | func init() { 14 | embedProcAttr = &syscall.SysProcAttr{HideWindow: true} 15 | } 16 | -------------------------------------------------------------------------------- /examples/remote/plugin/hello.lua: -------------------------------------------------------------------------------- 1 | local chan 2 | 3 | local function ensure_job() 4 | if chan then 5 | return chan 6 | end 7 | chan = vim.fn.jobstart({ 'helloremote' }, { rpc = true }) 8 | return chan 9 | end 10 | 11 | vim.api.nvim_create_user_command('Hello', function(args) 12 | vim.fn.rpcrequest(ensure_job(), 'hello', args.fargs) 13 | end, { nargs = '*' }) 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | coverage.* 27 | -------------------------------------------------------------------------------- /nvim/plugin/plugin_bench_test.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var evalString string 8 | 9 | func BenchmarkEval(b *testing.B) { 10 | fn := func(x *struct { 11 | X int `eval:"1"` 12 | YY string `eval:"'hello'" msgpack:"Y"` 13 | Z int 14 | }) { 15 | } 16 | 17 | b.ReportAllocs() 18 | b.ResetTimer() 19 | for i := 0; i < b.N; i++ { 20 | evalString = eval("*", fn) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request Labeler 2 | 3 | on: 4 | - pull_request_target 5 | 6 | jobs: 7 | triage: 8 | runs-on: ubuntu-22.04 9 | 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | 14 | steps: 15 | - uses: actions/labeler@v4 16 | with: 17 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 18 | configuration-path: ".github/labeler.yaml" 19 | -------------------------------------------------------------------------------- /nvim/doc.go: -------------------------------------------------------------------------------- 1 | // Package nvim implements a Nvim client. 2 | // 3 | // The Nvim type implements the client. To connect to a running instance of 4 | // Nvim, create a *Nvim value using the New, Dial or NewChildProcess functions. 5 | // Call the Close() method to release the resources used by the client. 6 | // 7 | // Use the Batch type to execute a sequence of Nvim API calls atomically. The 8 | // Nvim NewBatch method creates new *Batch values. 9 | package nvim 10 | -------------------------------------------------------------------------------- /nvim/plugin/example_test.go: -------------------------------------------------------------------------------- 1 | package plugin_test 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/neovim/go-client/nvim/plugin" 7 | ) 8 | 9 | // This plugin adds the Hello function to Nvim. 10 | func Example() { 11 | plugin.Main(func(p *plugin.Plugin) error { 12 | p.HandleFunction(&plugin.FunctionOptions{Name: "Hello"}, func(args []string) (string, error) { 13 | return "Hello, " + strings.Join(args, " "), nil 14 | }) 15 | return nil 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /nvim/plugin/eval_test.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import "testing" 4 | 5 | type env struct { 6 | GOROOT string `eval:"$GOROOT"` 7 | GOPATH string `eval:"$GOPATH"` 8 | } 9 | 10 | var evalTests = []struct { 11 | fn any 12 | eval string 13 | }{ 14 | {func(x *struct { 15 | X int `eval:"1"` 16 | YY string `eval:"'hello'" msgpack:"Y"` 17 | Z int 18 | }) { 19 | }, `{'X': 1, 'Y': 'hello'}`}, 20 | 21 | // Nested struct 22 | {func(x *struct { 23 | Env env 24 | }) { 25 | }, `{'Env': {'GOROOT': $GOROOT, 'GOPATH': $GOPATH}}`}, 26 | } 27 | 28 | func TestEval(t *testing.T) { 29 | for _, tt := range evalTests { 30 | eval := eval("*", tt.fn) 31 | if eval != tt.eval { 32 | t.Errorf("eval(%T) returned %q, want %q", tt.fn, eval, tt.eval) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/remote/README.md: -------------------------------------------------------------------------------- 1 | This this example Neovim plugin shows how to invoke a [Go](https://go.dev/) 2 | function from a plugin. 3 | 4 | The plugin starts a Go program containing the function as a child process. The 5 | plugin invokes functions in the child process using 6 | [RPC](https://neovim.io/doc/user/api.html#RPC). 7 | 8 | Use the following steps to run the plugin: 9 | 10 | 1. Build the program with the [go tool](https://golang.org/cmd/go/) to an 11 | executable named `helloremote`. Ensure that the executable is in a directory in 12 | the `PATH` environment variable. 13 | ``` 14 | $ cd helloremote 15 | $ go build 16 | ``` 17 | 1. Install the plugin in this directory using a plugin manager or by adding 18 | this directory to the 19 | [runtimepath](https://neovim.io/doc/user/options.html#'runtimepath'). 20 | 1. Start Nvim and run the following command: 21 | ```vim 22 | :Hello world! 23 | ``` 24 | -------------------------------------------------------------------------------- /.codecov.yaml: -------------------------------------------------------------------------------- 1 | codecov: 2 | allow_coverage_offsets: true 3 | 4 | parsers: 5 | go: 6 | partials_as_hits: true 7 | 8 | coverage: 9 | precision: 1 10 | range: "70...100" 11 | round: down 12 | 13 | status: 14 | default_rules: 15 | flag_coverage_not_uploaded_behavior: include 16 | 17 | project: 18 | default: 19 | target: auto 20 | threshold: 1.0 21 | if_not_found: success 22 | 23 | patch: 24 | default: 25 | target: auto 26 | threshold: 10.0 27 | only_pulls: true 28 | if_not_found: failure 29 | 30 | changes: 31 | default: 32 | if_not_found: success 33 | only_pulls: false 34 | branches: 35 | - master 36 | 37 | comment: 38 | behavior: default 39 | require_changes: false 40 | show_carryforward_flags: true 41 | 42 | ignore: 43 | - "nvim/api_deprecated.go" 44 | - "nvim/nvimtest" # for testing 45 | 46 | github_checks: 47 | annotations: true 48 | -------------------------------------------------------------------------------- /nvim/helpers_test.go: -------------------------------------------------------------------------------- 1 | package nvim 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | var readerData = []string{ 11 | "\n", 12 | "hello\nworld\n", 13 | "blank\n\nline\n", 14 | "\n1\n22\n333\n", 15 | "333\n22\n1\n\n", 16 | } 17 | 18 | func TestBufferReader(t *testing.T) { 19 | v := newChildProcess(t) 20 | b, err := v.CurrentBuffer() 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | for _, d := range readerData { 25 | if err := v.SetBufferLines(b, 0, -1, true, bytes.Split([]byte(strings.TrimSuffix(d, "\n")), []byte{'\n'})); err != nil { 26 | t.Fatal(err) 27 | } 28 | for n := 1; n < 20; n++ { 29 | var buf bytes.Buffer 30 | r := NewBufferReader(v, b) 31 | _, err := io.CopyBuffer(struct{ io.Writer }{&buf}, r, make([]byte, n)) 32 | if err != nil { 33 | t.Errorf("copy %q with buffer size %d returned error %v", d, n, err) 34 | continue 35 | } 36 | if d != buf.String() { 37 | t.Errorf("copy %q with buffer size %d = %q", d, n, buf.Bytes()) 38 | continue 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/remote/helloremote/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | "github.com/neovim/go-client/nvim" 10 | ) 11 | 12 | func hello(v *nvim.Nvim, args []string) error { 13 | return v.WriteOut(fmt.Sprintf("Hello %s\n", strings.Join(args, " "))) 14 | } 15 | 16 | func main() { 17 | // Turn off timestamps in output. 18 | log.SetFlags(0) 19 | 20 | // Direct writes by the application to stdout garble the RPC stream. 21 | // Redirect the application's direct use of stdout to stderr. 22 | stdout := os.Stdout 23 | os.Stdout = os.Stderr 24 | 25 | // Create a client connected to stdio. Configure the client to use the 26 | // standard log package for logging. 27 | v, err := nvim.New(os.Stdin, stdout, stdout, log.Printf) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | // Register function with the client. 33 | v.RegisterHandler("hello", hello) 34 | 35 | // Run the RPC message loop. The Serve function returns when 36 | // nvim closes. 37 | if err := v.Serve(); err != nil { 38 | log.Fatal(err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-client 2 | 3 | [![pkg.go.dev][pkg.go.dev-badge]][pkg.go.dev] 4 | [![Github Actions][Github Actions Badge]][Github Actions] 5 | [![codecov.io][codecov-badge]][codecov] 6 | 7 | Neovim/go-client is a [Neovim](https://neovim.io/) client for [Go](https://golang.org/). 8 | 9 | Release 10 | ------- 11 | 12 | Follow the standard Go process for [publishing a module](https://go.dev/doc/modules/publishing). 13 | 14 | License 15 | ------- 16 | 17 | [Apache License 2.0](https://github.com/neovim/go-client/blob/master/LICENSE) 18 | 19 | 20 | [pkg.go.dev]: https://pkg.go.dev/github.com/neovim/go-client 21 | [Github Actions]: https://github.com/neovim/go-client/actions 22 | [codecov]: https://app.codecov.io/gh/neovim/go-client 23 | 24 | [pkg.go.dev-badge]: https://pkg.go.dev/badge/github.com/neovim/go-client.svg 25 | [Github Actions Badge]: https://img.shields.io/github/workflow/status/go-clang/gen/Test/main?label=test&logo=github&style=flat-square 26 | [codecov-badge]: https://img.shields.io/codecov/c/github/neovim/go-client/master?logo=codecov&style=flat-square 27 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | schedule: 9 | - cron: '0 20 * * *' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-22.04 # https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2204-Readme.md 15 | 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v3 24 | 25 | - name: Set up Go 26 | uses: actions/setup-go@v3 27 | with: 28 | go-version: '1.20.x' 29 | 30 | - name: Initialize CodeQL 31 | uses: github/codeql-action/init@v2 32 | with: 33 | languages: 'go' 34 | 35 | - name: Perform CodeQL Analysis 36 | uses: github/codeql-action/analyze@v2 37 | env: 38 | # hack for fetch dependencies when failed to go-autobuilder 'make' command 39 | # https://github.com/github/codeql-go/blob/b953fe39c2cb/extractor/cli/go-autobuilder/go-autobuilder.go#L409 40 | CODEQL_EXTRACTOR_GO_BUILD_COMMAND: 'true' 41 | -------------------------------------------------------------------------------- /nvim/example_test.go: -------------------------------------------------------------------------------- 1 | package nvim_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/neovim/go-client/nvim" 9 | ) 10 | 11 | // This program lists the names of the Nvim buffers when run from an Nvim 12 | // terminal. It dials to Nvim using the $NVIM_LISTEN_ADDRESS and fetches all of 13 | // the buffer names in one call using a batch. 14 | func Example() { 15 | // Get address from environment variable set by Nvim. 16 | addr := os.Getenv("NVIM_LISTEN_ADDRESS") 17 | if addr == "" { 18 | log.Fatal("NVIM_LISTEN_ADDRESS not set") 19 | } 20 | 21 | // Dial with default options. 22 | v, err := nvim.Dial(addr) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | // Cleanup on return. 28 | defer v.Close() 29 | 30 | bufs, err := v.Buffers() 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | 35 | // Get the names using a single atomic call to Nvim. 36 | names := make([]string, len(bufs)) 37 | b := v.NewBatch() 38 | for i, buf := range bufs { 39 | b.BufferName(buf, &names[i]) 40 | } 41 | if err := b.Execute(); err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | // Print the names. 46 | for _, name := range names { 47 | fmt.Println(name) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /nvim/helpers.go: -------------------------------------------------------------------------------- 1 | package nvim 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type bufferReader struct { 8 | err error 9 | v *Nvim 10 | lines [][]byte 11 | b Buffer 12 | } 13 | 14 | // compile time check whether the bufferReader implements io.Reader interface. 15 | var _ io.Reader = (*bufferReader)(nil) 16 | 17 | // NewBufferReader returns a reader for the specified buffer. If b = 0, then 18 | // the current buffer is used. 19 | func NewBufferReader(v *Nvim, b Buffer) io.Reader { 20 | return &bufferReader{v: v, b: b} 21 | } 22 | 23 | // Read implements io.Reader. 24 | func (r *bufferReader) Read(p []byte) (n int, err error) { 25 | if r.err != nil { 26 | return 0, r.err 27 | } 28 | if r.lines == nil { 29 | r.lines, r.err = r.v.BufferLines(r.b, 0, -1, true) 30 | if r.err != nil { 31 | return 0, r.err 32 | } 33 | } 34 | for { 35 | if len(r.lines) == 0 { 36 | r.err = io.EOF 37 | return n, r.err 38 | } 39 | if len(p) == 0 { 40 | return n, nil 41 | } 42 | 43 | line0 := r.lines[0] 44 | if len(line0) == 0 { 45 | p[0] = '\n' 46 | p = p[1:] 47 | n++ 48 | r.lines = r.lines[1:] 49 | continue 50 | } 51 | 52 | nn := copy(p, line0) 53 | n += nn 54 | p = p[nn:] 55 | r.lines[0] = line0[nn:] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark 2 | 3 | on: 4 | push: 5 | branches: 6 | - "benchmark/*" 7 | pull_request: 8 | branches: 9 | - "benchmark/*" 10 | 11 | defaults: 12 | run: 13 | shell: bash 14 | 15 | jobs: 16 | benchmark: 17 | runs-on: ubuntu-22.04 # https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2204-Readme.md 18 | 19 | steps: 20 | - name: Set flag environment variable 21 | run: | 22 | echo "OS=$(echo ${{ runner.os }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV 23 | echo "GO_VERSION=$(echo ${{ matrix.go-version }} | cut -d. -f-2)" >> $GITHUB_ENV 24 | 25 | - name: Checkout code 26 | uses: actions/checkout@v3 27 | 28 | - name: Install Go 29 | uses: actions/setup-go@v3 30 | with: 31 | go-version: '1.20.x' 32 | 33 | - name: Install nvim binary 34 | uses: rhysd/action-setup-vim@v1 35 | with: 36 | neovim: true 37 | version: v0.9.1 38 | 39 | - name: Run Benchmark 40 | run: | 41 | go test -v -count=3 -run=none -bench=. -benchmem -timeout=15m ./... | tee bench-${{ env.OS }}-${{ env.GO_VERSION }}.txt 42 | 43 | - name: 'Upload benchmark Artifact' 44 | uses: actions/upload-artifact@v3 45 | with: 46 | name: bench-${{ env.OS }}-${{ env.GO_VERSION }}.txt 47 | path: bench-${{ env.OS }}-${{ env.GO_VERSION }}.txt 48 | -------------------------------------------------------------------------------- /nvim/nvimtest/nvimtest.go: -------------------------------------------------------------------------------- 1 | package nvimtest 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/neovim/go-client/nvim" 10 | ) 11 | 12 | // NewChildProcess returns the new *Nvim, and registers cleanup to tb.Cleanup. 13 | func NewChildProcess(tb testing.TB) *nvim.Nvim { 14 | tb.Helper() 15 | 16 | ctx, cancel := context.WithCancel(context.Background()) 17 | tb.Cleanup(func() { 18 | cancel() 19 | }) 20 | 21 | tmpdir := tb.TempDir() 22 | envs := os.Environ() 23 | envs = append(envs, []string{ 24 | fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpdir), 25 | fmt.Sprintf("XDG_DATA_HOME=%s", tmpdir), 26 | fmt.Sprintf("NVIM_LOG_FILE=%s", os.DevNull), 27 | }...) 28 | opts := []nvim.ChildProcessOption{ 29 | nvim.ChildProcessCommand(nvim.BinaryName), 30 | nvim.ChildProcessArgs( 31 | "--clean", // Mimics a fresh install of Nvim. See :help --clean 32 | "--embed", // Use stdin/stdout as a msgpack-RPC channel, so applications can embed and control Nvim via the RPC API. 33 | "--headless", // Start without UI, and do not wait for nvim_ui_attach 34 | "-c", "set packpath=", // Clean packpath 35 | ), 36 | nvim.ChildProcessContext(ctx), 37 | nvim.ChildProcessEnv(envs), 38 | nvim.ChildProcessServe(true), 39 | nvim.ChildProcessLogf(tb.Logf), 40 | } 41 | n, err := nvim.NewChildProcess(opts...) 42 | if err != nil { 43 | tb.Fatal(err) 44 | } 45 | 46 | tb.Cleanup(func() { 47 | if err := n.Close(); err != nil { 48 | tb.Fatal(err) 49 | } 50 | }) 51 | 52 | return n 53 | } 54 | -------------------------------------------------------------------------------- /msgpack/msgpack.go: -------------------------------------------------------------------------------- 1 | package msgpack 2 | 3 | // Codes from the MessagePack specification. 4 | const ( 5 | fixIntCodeMin = 0x00 6 | fixIntCodeMax = 0x7f 7 | fixMapCodeMin = 0x80 8 | fixMapCodeMax = 0x8f 9 | fixArrayCodeMin = 0x90 10 | fixArrayCodeMax = 0x9f 11 | fixStringCodeMin = 0xa0 12 | fixStringCodeMax = 0xbf 13 | nilCode = 0xc0 14 | unusedCode = 0xc1 // never used 15 | falseCode = 0xc2 16 | trueCode = 0xc3 17 | binary8Code = 0xc4 18 | binary16Code = 0xc5 19 | binary32Code = 0xc6 20 | ext8Code = 0xc7 21 | ext16Code = 0xc8 22 | ext32Code = 0xc9 23 | float32Code = 0xca 24 | float64Code = 0xcb 25 | uint8Code = 0xcc 26 | uint16Code = 0xcd 27 | uint32Code = 0xce 28 | uint64Code = 0xcf 29 | int8Code = 0xd0 30 | int16Code = 0xd1 31 | int32Code = 0xd2 32 | int64Code = 0xd3 33 | fixExt1Code = 0xd4 34 | fixExt2Code = 0xd5 35 | fixExt4Code = 0xd6 36 | fixExt8Code = 0xd7 37 | fixExt16Code = 0xd8 38 | string8Code = 0xd9 39 | string16Code = 0xda 40 | string32Code = 0xdb 41 | array16Code = 0xdc 42 | array32Code = 0xdd 43 | map16Code = 0xde 44 | map32Code = 0xdf 45 | negFixIntCodeMin = 0xe0 46 | negFixIntCodeMax = 0xff 47 | ) 48 | 49 | type aborted struct{ err error } 50 | 51 | func abort(err error) { panic(aborted{err}) } 52 | 53 | func handleAbort(err *error) { 54 | if r := recover(); r != nil { 55 | if a, ok := r.(aborted); ok { 56 | *err = a.err 57 | } else { 58 | panic(r) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /nvim/types_test.go: -------------------------------------------------------------------------------- 1 | package nvim 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestLogLevel_String(t *testing.T) { 9 | t.Parallel() 10 | 11 | tests := []struct { 12 | name string 13 | level LogLevel 14 | want string 15 | }{ 16 | { 17 | name: "Trace", 18 | level: LogTraceLevel, 19 | want: "TraceLevel", 20 | }, 21 | { 22 | name: "Debug", 23 | level: LogDebugLevel, 24 | want: "DebugLevel", 25 | }, 26 | { 27 | name: "Info", 28 | level: LogInfoLevel, 29 | want: "InfoLevel", 30 | }, 31 | { 32 | name: "Warn", 33 | level: LogWarnLevel, 34 | want: "WarnLevel", 35 | }, 36 | { 37 | name: "Error", 38 | level: LogErrorLevel, 39 | want: "ErrorLevel", 40 | }, 41 | { 42 | name: "unknown", 43 | level: LogLevel(-1), 44 | want: "unknown Level", 45 | }, 46 | } 47 | for _, tt := range tests { 48 | tt := tt 49 | t.Run(tt.name, func(t *testing.T) { 50 | t.Parallel() 51 | 52 | if got := tt.level.String(); got != tt.want { 53 | t.Fatalf("LogLevel.String() = %v, want %v", tt.want, got) 54 | } 55 | }) 56 | } 57 | } 58 | 59 | func TestUserCommand(t *testing.T) { 60 | t.Parallel() 61 | 62 | uc := reflect.TypeOf((*UserCommand)(nil)).Elem() 63 | 64 | tests := map[string]struct { 65 | u UserCommand 66 | }{ 67 | "UserVimCommand": { 68 | u: UserVimCommand(""), 69 | }, 70 | "UserLuaCommand": { 71 | u: UserLuaCommand{}, 72 | }, 73 | } 74 | for name, tt := range tests { 75 | tt := tt 76 | t.Run(name, func(t *testing.T) { 77 | t.Parallel() 78 | 79 | v := reflect.TypeOf(tt.u) 80 | if !v.Implements(uc) { 81 | t.Fatalf("%s type not implements %q interface", v.Name(), "UserCommand") 82 | } 83 | 84 | tt.u.command() 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /nvim/ext_test.go: -------------------------------------------------------------------------------- 1 | package nvim 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | ) 7 | 8 | var decodeExtTests = []struct { 9 | n int 10 | hs []string 11 | }{ 12 | {0x0, []string{"00", "d000", "d10000", "d200000000"}}, 13 | {0x1, []string{"01", "d001", "d10001", "d200000001"}}, 14 | {0x7f, []string{"7f", "d07f", "d1007f", "d20000007f"}}, 15 | {0x80, []string{"d10080", "d200000080"}}, 16 | {0x7fff, []string{"d17fff", "d200007fff"}}, 17 | {0x8000, []string{"d200008000"}}, 18 | {0x7fffffff, []string{"d27fffffff"}}, 19 | {-0x1, []string{"ff", "d0ff", "d1ffff", "d2ffffffff"}}, 20 | {-0x20, []string{"e0", "d0e0", "d1ffe0", "d2ffffffe0"}}, 21 | {-0x21, []string{"d0df", "d1ffdf", "d2ffffffdf"}}, 22 | {-0x80, []string{"d080", "d1ff80", "d2ffffff80"}}, 23 | {-0x81, []string{"d1ff7f", "d2ffffff7f"}}, 24 | {-0x8000, []string{"d18000", "d2ffff8000"}}, 25 | {-0x8001, []string{"d2ffff7fff"}}, 26 | {-0x80000000, []string{"d280000000"}}, 27 | {0xff, []string{"ccff", "cd00ff", "ce000000ff"}}, 28 | {0x100, []string{"cd0100", "ce00000100"}}, 29 | {0xffff, []string{"cdffff", "ce0000ffff"}}, 30 | {0x10000, []string{"ce00010000"}}, 31 | } 32 | 33 | func TestDecodeExt(t *testing.T) { 34 | for _, tt := range decodeExtTests { 35 | for _, h := range tt.hs { 36 | p, err := hex.DecodeString(h) 37 | if err != nil { 38 | t.Errorf("hex.DecodeString(%s) returned error %v", h, err) 39 | continue 40 | } 41 | n, err := decodeExt(p) 42 | if err != nil { 43 | t.Errorf("decodeExt(%s) returned %v", h, err) 44 | continue 45 | } 46 | if n != tt.n { 47 | t.Errorf("decodeExt(%s) = %x, want %x", h, n, tt.n) 48 | } 49 | } 50 | } 51 | } 52 | 53 | func TestEncodeExt(t *testing.T) { 54 | for _, tt := range decodeExtTests { 55 | n, err := decodeExt(encodeExt(tt.n)) 56 | if n != tt.n || err != nil { 57 | t.Errorf("decodeExt(encodeExt(%x)) returned %x, %v", tt.n, n, err) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /nvim/plugin/README.md: -------------------------------------------------------------------------------- 1 | Status 2 | ------ 3 | 4 | This package will be deprecated when Neovim [removes the concept of 5 | remote plugins](https://github.com/neovim/neovim/issues/27949). 6 | 7 | 8 | Example 9 | ------- 10 | 11 | This example plugin adds the Hello command to Nvim. 12 | 13 | ```go 14 | package main 15 | 16 | import ( 17 | "strings" 18 | "github.com/neovim/go-client/nvim/plugin" 19 | ) 20 | 21 | func hello(args []string) (string, error) { 22 | return "Hello " + strings.Join(args, " "), nil 23 | } 24 | 25 | func main() { 26 | plugin.Main(func(p *plugin.Plugin) error { 27 | p.HandleFunction(&plugin.FunctionOptions{Name: "Hello"}, hello) 28 | return nil 29 | }) 30 | } 31 | ``` 32 | 33 | Build the program with the [go tool](https://golang.org/cmd/go/) to an 34 | executable named `hello`. Ensure that the executable is in a directory in 35 | the `PATH` environment variable. 36 | 37 | ```bash 38 | // Use the `go build` command to generate an executable. 39 | // To ensure this "hello" executable is on your path, 40 | // you can move "hello" to your $GOPATH/bin directory 41 | // or add the current directory to the `PATH` environment variable. 42 | go build -o hello 43 | ``` 44 | 45 | Add the following plugin to Nvim: 46 | 47 | ```vim 48 | if exists('g:loaded_hello') 49 | finish 50 | endif 51 | let g:loaded_hello = 1 52 | 53 | function! s:Requirehello(host) abort 54 | " 'hello' is the binary created by compiling the program above. 55 | return jobstart(['hello'], {'rpc': v:true}) 56 | endfunction 57 | 58 | call remote#host#Register('hello', 'x', function('s:Requirehello')) 59 | " The following lines are generated by running the program 60 | " command line flag --manifest hello 61 | call remote#host#RegisterPlugin('hello', '0', [ 62 | \ {'type': 'function', 'name': 'Hello', 'sync': 1, 'opts': {}}, 63 | \ ]) 64 | 65 | " vim:ts=4:sw=4:et 66 | ``` 67 | 68 | Start Nvim and run the following command: 69 | 70 | ```vim 71 | :echo Hello('world') 72 | ``` 73 | 74 | 75 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | release: 9 | types: 10 | - published 11 | - created 12 | - edited 13 | 14 | defaults: 15 | run: 16 | shell: bash 17 | 18 | jobs: 19 | test: 20 | strategy: 21 | matrix: 22 | os: 23 | - ubuntu-22.04 # https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2204-Readme.md 24 | - macos-13 # https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md 25 | - windows-2022 # https://github.com/actions/runner-images/blob/main/images/win/Windows2022-Readme.md 26 | go-version: 27 | - 1.18.x 28 | - 1.19.x 29 | - 1.20.x 30 | neovim-version: 31 | - v0.9.1 32 | - nightly 33 | fail-fast: false 34 | 35 | runs-on: ${{ matrix.os }} 36 | 37 | steps: 38 | - name: Set flag environment variable 39 | run: | 40 | echo "OS=$(echo ${{ runner.os }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV 41 | echo "GO_VERSION=$(echo ${{ matrix.go-version }} | cut -d. -f-2)" >> $GITHUB_ENV 42 | echo "NVIM_VERSION=$(if [ ${{ matrix.neovim-version }} != 'nightly' ]; then echo 'stable'; else echo 'nightly'; fi)" >> $GITHUB_ENV 43 | 44 | - name: Install Go 45 | uses: actions/setup-go@v3 46 | with: 47 | go-version: ${{ matrix.go-version }} 48 | 49 | - name: Checkout code 50 | uses: actions/checkout@v3 51 | 52 | - name: Install neovim binary 53 | uses: rhysd/action-setup-vim@v1 54 | with: 55 | neovim: true 56 | version: ${{ matrix.neovim-version }} 57 | 58 | - name: Test and take a coverage 59 | run: | 60 | go test -race -count=1 -covermode=atomic -coverpkg=./... -coverprofile=coverage.out ./... 61 | 62 | - uses: codecov/codecov-action@v3 63 | with: 64 | file: coverage.out 65 | flags: ${{ env.OS }}-${{ env.GO_VERSION }}-${{ env.NVIM_VERSION }} 66 | env_vars: OS,GO_VERSION,NVIM_VERSION 67 | -------------------------------------------------------------------------------- /nvim/plugin/manifest_test.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | originalManifest = "call remote#host#RegisterPlugin('P', '0', [\n\\ {'type': 'function', 'name': 'Foo', 'sync': 1, 'opts': {}},\n\\ ])\n" 10 | updatedManifest = "call remote#host#RegisterPlugin('P', '0', [\n\\ {'type': 'function', 'name': 'Bar', 'sync': 1, 'opts': {}},\n\\ ])\n" 11 | ) 12 | 13 | func TestReplaceManifest(t *testing.T) { 14 | t.Parallel() 15 | 16 | var replaceManifestTests = []struct { 17 | name string 18 | original string 19 | expected string 20 | }{ 21 | { 22 | name: "Original at beginning of file", 23 | original: fmt.Sprintf("%sline A\nline B\n", originalManifest), 24 | expected: fmt.Sprintf("%sline A\nline B\n", updatedManifest), 25 | }, 26 | { 27 | name: "Original in middle of file", 28 | original: fmt.Sprintf("line A\n%sline B\n", originalManifest), 29 | expected: fmt.Sprintf("line A\n%sline B\n", updatedManifest), 30 | }, 31 | { 32 | name: "Original at end of file", 33 | original: fmt.Sprintf("line A\nline B\n%s", originalManifest), 34 | expected: fmt.Sprintf("line A\nline B\n%s", updatedManifest), 35 | }, 36 | { 37 | name: "Original at end of file, no trailing \\n", 38 | original: fmt.Sprintf("line A\nline B\n%s", originalManifest[:len(originalManifest)-1]), 39 | expected: fmt.Sprintf("line A\nline B\n%s", updatedManifest), 40 | }, 41 | { 42 | name: "No manifest", 43 | original: "line A\nline B\n", 44 | expected: fmt.Sprintf("line A\nline B\n%s", updatedManifest), 45 | }, 46 | { 47 | name: "Empty file", 48 | original: "", 49 | expected: updatedManifest, 50 | }, 51 | { 52 | name: "No manifest, no trailing \\n", 53 | original: "line A\nline B", 54 | expected: fmt.Sprintf("line A\nline B\n%s", updatedManifest), 55 | }, 56 | { 57 | name: "Extra \\ ])` in file", // ensure non-greedy match trailing ]) 58 | original: fmt.Sprintf("line A\n%sline B\n\\ ])\nline C\n", originalManifest), 59 | expected: fmt.Sprintf("line A\n%sline B\n\\ ])\nline C\n", updatedManifest), 60 | }, 61 | } 62 | for _, tt := range replaceManifestTests { 63 | tt := tt 64 | t.Run(tt.name, func(t *testing.T) { 65 | t.Parallel() 66 | 67 | actual := string(replaceManifest("P", []byte(tt.original), []byte(updatedManifest))) 68 | if actual != tt.expected { 69 | t.Fatalf("%s\n got = %q\nwant = %q", tt.name, actual, tt.expected) 70 | } 71 | 72 | // Replace should be idempotent. 73 | actual = string(replaceManifest("P", []byte(tt.expected), []byte(updatedManifest))) 74 | if actual != tt.expected { 75 | t.Fatalf("%s (no change expected)\n got = %q\nwant = %q", tt.name, actual, tt.expected) 76 | } 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /nvim/plugin/main.go: -------------------------------------------------------------------------------- 1 | // Package plugin is a Nvim remote plugin host. 2 | package plugin 3 | 4 | import ( 5 | "bytes" 6 | "flag" 7 | "log" 8 | "os" 9 | "regexp" 10 | 11 | "github.com/neovim/go-client/nvim" 12 | ) 13 | 14 | // Main implements the main function for a Nvim remote plugin. 15 | // 16 | // Plugin applications call the Main function to run the plugin. The Main 17 | // function creates a Nvim client, calls the supplied function to register 18 | // handlers with the plugin and then runs the server loop to handle requests 19 | // from Nvim. 20 | // 21 | // Applications should use the default logger in the standard log package to 22 | // write to Nvim's log. 23 | // 24 | // Run the plugin application with the command line option --manifest=hostName 25 | // to print the plugin manifest to stdout. Add the manifest manually to a 26 | // Vimscript file. The :UpdateRemotePlugins command is not supported at this 27 | // time. 28 | // 29 | // If the --manifest=host command line flag is specified, then Main prints the 30 | // plugin manifest to stdout insead of running the application as a plugin. 31 | // If the --location=vimfile command line flag is specified, then plugin 32 | // manifest will be automatically written to .vim file. 33 | func Main(registerHandlers func(p *Plugin) error) { 34 | pluginHost := flag.String("manifest", "", "Write plugin manifest for `host` to stdout") 35 | vimFilePath := flag.String("location", "", "Manifest is automatically written to `.vim file`") 36 | flag.Parse() 37 | 38 | if *pluginHost != "" { 39 | log.SetFlags(0) 40 | p := New(nil) 41 | if err := registerHandlers(p); err != nil { 42 | log.Fatal(err) 43 | } 44 | manifest := p.Manifest(*pluginHost) 45 | if *vimFilePath != "" { 46 | if err := overwriteManifest(*vimFilePath, *pluginHost, manifest); err != nil { 47 | log.Fatal(err) 48 | } 49 | } else { 50 | os.Stdout.Write(manifest) 51 | } 52 | return 53 | } 54 | 55 | stdout := os.Stdout 56 | os.Stdout = os.Stderr 57 | log.SetFlags(0) 58 | 59 | v, err := nvim.New(os.Stdin, stdout, stdout, log.Printf) 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | p := New(v) 64 | if err := registerHandlers(p); err != nil { 65 | log.Fatal(err) 66 | } 67 | if err := v.Serve(); err != nil { 68 | log.Fatal(err) 69 | } 70 | } 71 | 72 | func overwriteManifest(path, host string, manifest []byte) error { 73 | input, err := os.ReadFile(path) 74 | if err != nil { 75 | return err 76 | } 77 | output := replaceManifest(host, input, manifest) 78 | return os.WriteFile(path, output, 0666) 79 | } 80 | 81 | func replaceManifest(host string, input, manifest []byte) []byte { 82 | p := regexp.MustCompile(`(?ms)^call remote#host#RegisterPlugin\('` + regexp.QuoteMeta(host) + `'.*?^\\ ]\)$`) 83 | match := p.FindIndex(input) 84 | var output []byte 85 | if match == nil { 86 | if len(input) > 0 && input[len(input)-1] != '\n' { 87 | input = append(input, '\n') 88 | } 89 | output = append(input, manifest...) 90 | } else { 91 | if match[1] != len(input) { 92 | // No need for trailing \n if in middle of file. 93 | manifest = bytes.TrimSuffix(manifest, []byte{'\n'}) 94 | } 95 | output = append([]byte{}, input[:match[0]]...) 96 | output = append(output, manifest...) 97 | output = append(output, input[match[1]:]...) 98 | } 99 | return output 100 | } 101 | -------------------------------------------------------------------------------- /msgpack/field.go: -------------------------------------------------------------------------------- 1 | package msgpack 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type field struct { 11 | name string 12 | omitEmpty bool 13 | array bool 14 | index []int 15 | typ reflect.Type 16 | empty reflect.Value 17 | } 18 | 19 | func collectFields(fields []*field, t reflect.Type, visited map[reflect.Type]bool, depth map[string]int, index []int) []*field { 20 | // Break recursion 21 | if visited[t] { 22 | return fields 23 | } 24 | visited[t] = true 25 | 26 | for i := 0; i < t.NumField(); i++ { 27 | sf := t.Field(i) 28 | if sf.PkgPath != "" && !sf.Anonymous { 29 | // Skip field if not exported and not anonymous 30 | continue 31 | } 32 | 33 | var ( 34 | name string 35 | omitEmpty bool 36 | array bool 37 | ) 38 | for i, p := range strings.Split(sf.Tag.Get("msgpack"), ",") { 39 | if i == 0 { 40 | name = p 41 | } else if p == "omitempty" { 42 | omitEmpty = true 43 | } else if p == "array" { 44 | array = true 45 | } else { 46 | panic(fmt.Errorf("msgpack: unknown field tag %s for type %s", p, t.Name())) 47 | } 48 | } 49 | 50 | if name == "-" { 51 | // Skip field when field tag starts with "-" 52 | continue 53 | } 54 | 55 | ft := sf.Type 56 | if ft.Name() == "" && ft.Kind() == reflect.Ptr { 57 | ft = ft.Elem() 58 | } 59 | 60 | if name == "" && sf.Anonymous && ft.Kind() == reflect.Struct { 61 | // Flatten anonymous struct field 62 | fields = collectFields(fields, ft, visited, depth, append(index, i)) 63 | continue 64 | } 65 | 66 | if name == "" { 67 | name = sf.Name 68 | } 69 | 70 | // Check for name collisions 71 | d, found := depth[name] 72 | if !found { 73 | d = 65535 74 | } 75 | 76 | if len(index) == d { 77 | // There is another field with same name and same depth 78 | // Remove that field and skip this field 79 | j := 0 80 | for i := 0; i < len(fields); i++ { 81 | if name != fields[i].name { 82 | fields[j] = fields[i] 83 | j++ 84 | } 85 | } 86 | fields = fields[:j] 87 | continue 88 | } 89 | depth[name] = len(index) 90 | 91 | f := &field{ 92 | name: name, 93 | omitEmpty: omitEmpty, 94 | array: array, 95 | index: make([]int, len(index)+1), 96 | typ: sf.Type, 97 | } 98 | copy(f.index, index) 99 | f.index[len(index)] = i 100 | 101 | // Parse empty field tag 102 | if e := sf.Tag.Get("empty"); e != "" { 103 | switch sf.Type.Kind() { 104 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 105 | bits := 0 106 | if sf.Type.Kind() != reflect.Int { 107 | bits = sf.Type.Bits() 108 | } 109 | v, err := strconv.ParseInt(e, 10, bits) 110 | if err != nil { 111 | panic(fmt.Errorf("msgpack: error parsing field empty field %s.%s: %w", t.Name(), sf.Name, err)) 112 | } 113 | f.empty = reflect.New(sf.Type).Elem() 114 | f.empty.SetInt(v) 115 | 116 | case reflect.Bool: 117 | v, err := strconv.ParseBool(e) 118 | if err != nil { 119 | panic(fmt.Errorf("msgpack: error parsing field empty field %s.%s: %w", t.Name(), sf.Name, err)) 120 | } 121 | f.empty = reflect.New(sf.Type).Elem() 122 | f.empty.SetBool(v) 123 | 124 | case reflect.String: 125 | f.empty = reflect.New(sf.Type).Elem() 126 | f.empty.SetString(e) 127 | 128 | default: 129 | panic(fmt.Errorf("msgpack: unsupported empty field %s.%s", t.Name(), sf.Name)) 130 | } 131 | } 132 | 133 | fields = append(fields, f) 134 | 135 | } 136 | 137 | return fields 138 | } 139 | 140 | func fieldsForType(t reflect.Type) ([]*field, bool) { 141 | fields := collectFields(nil, t, make(map[reflect.Type]bool), make(map[string]int), nil) 142 | array := false 143 | 144 | for _, field := range fields { 145 | if field.array { 146 | array = true 147 | break 148 | } 149 | } 150 | 151 | return fields, array 152 | } 153 | 154 | func fieldByIndex(v reflect.Value, index []int) reflect.Value { 155 | for _, i := range index { 156 | if v.Kind() == reflect.Ptr { 157 | if v.IsNil() { 158 | return reflect.Value{} 159 | } 160 | v = v.Elem() 161 | } 162 | v = v.Field(i) 163 | } 164 | 165 | return v 166 | } 167 | -------------------------------------------------------------------------------- /nvim/nvim_test.go: -------------------------------------------------------------------------------- 1 | package nvim 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "reflect" 11 | "runtime" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | // newChildProcess returns the new *Nvim, and registers cleanup to tb.Cleanup. 17 | func newChildProcess(tb testing.TB, opts ...ChildProcessOption) (v *Nvim) { 18 | tb.Helper() 19 | 20 | envs := os.Environ() 21 | envs = append(envs, []string{ 22 | "XDG_CONFIG_HOME=", 23 | "XDG_DATA_HOME=", 24 | }...) 25 | 26 | ctx := context.Background() 27 | copts := []ChildProcessOption{ 28 | ChildProcessCommand(BinaryName), 29 | ChildProcessArgs( 30 | "-u", "NONE", 31 | "-n", 32 | "-i", "NONE", 33 | "--headless", 34 | "--embed", 35 | ), 36 | ChildProcessContext(ctx), 37 | ChildProcessLogf(tb.Logf), 38 | ChildProcessEnv(envs), 39 | } 40 | copts = append(copts, opts...) 41 | n, err := NewChildProcess(copts...) 42 | if err != nil { 43 | tb.Fatal(err) 44 | } 45 | v = n 46 | 47 | done := make(chan error, 1) 48 | go func() { 49 | done <- v.Serve() 50 | }() 51 | 52 | tb.Cleanup(func() { 53 | if err := v.Close(); err != nil { 54 | tb.Fatal(err) 55 | } 56 | 57 | err := <-done 58 | if err != nil { 59 | tb.Fatal(err) 60 | } 61 | 62 | const nvimlogFile = ".nvimlog" 63 | wd, err := os.Getwd() 64 | if err != nil { 65 | tb.Fatal(err) 66 | } 67 | if walkErr := filepath.Walk(wd, func(path string, info os.FileInfo, err error) error { 68 | if err != nil { 69 | return err 70 | } 71 | 72 | if info.IsDir() { 73 | return nil 74 | } 75 | 76 | if fname := info.Name(); fname == nvimlogFile { 77 | if err := os.RemoveAll(path); err != nil { 78 | return fmt.Errorf("failed to remove %s file: %w", path, err) 79 | } 80 | } 81 | 82 | return nil 83 | }); walkErr != nil && !os.IsNotExist(err) { 84 | tb.Fatal(fmt.Errorf("walkErr: %w", errors.Unwrap(walkErr))) 85 | } 86 | }) 87 | 88 | if err := v.Command("set packpath="); err != nil { 89 | tb.Fatal(err) 90 | } 91 | 92 | return v 93 | } 94 | 95 | func TestDial(t *testing.T) { 96 | if runtime.GOOS == "windows" { 97 | t.Skip("not supported dial unix socket on windows GOOS") 98 | } 99 | 100 | t.Parallel() 101 | 102 | v1 := newChildProcess(t) 103 | var addr string 104 | if err := v1.VVar("servername", &addr); err != nil { 105 | t.Fatal(err) 106 | } 107 | 108 | v2, err := Dial(addr, DialLogf(t.Logf)) 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | defer v2.Close() 113 | 114 | if err := v2.SetVar("dial_test", "Hello"); err != nil { 115 | t.Fatal(err) 116 | } 117 | 118 | var result string 119 | if err := v1.Var("dial_test", &result); err != nil { 120 | t.Fatal(err) 121 | } 122 | 123 | if expected := "Hello"; result != expected { 124 | t.Fatalf("got %s, want %s", result, expected) 125 | } 126 | 127 | if err := v2.Close(); err != nil { 128 | log.Fatal(err) 129 | } 130 | } 131 | 132 | func TestEmbedded(t *testing.T) { 133 | t.Parallel() 134 | 135 | v, err := NewEmbedded(&EmbedOptions{ 136 | Path: BinaryName, 137 | Args: []string{"-u", "NONE", "-n"}, 138 | Env: []string{}, 139 | Logf: t.Logf, 140 | }) 141 | if err != nil { 142 | t.Fatal(err) 143 | } 144 | 145 | done := make(chan error, 1) 146 | go func() { 147 | done <- v.Serve() 148 | }() 149 | 150 | var n int 151 | if err := v.Eval("1+2", &n); err != nil { 152 | log.Fatal(err) 153 | } 154 | 155 | if want := 3; n != want { 156 | log.Fatalf("got %d, want %d", n, want) 157 | } 158 | 159 | if err := v.Close(); err != nil { 160 | t.Fatal(err) 161 | } 162 | 163 | select { 164 | case err := <-done: 165 | if err != nil { 166 | t.Fatal(err) 167 | } 168 | case <-time.After(10 * time.Second): 169 | t.Fatal("timeout waiting for serve to exit") 170 | } 171 | } 172 | 173 | func TestCallWithNoArgs(t *testing.T) { 174 | t.Parallel() 175 | 176 | v := newChildProcess(t) 177 | 178 | var wd string 179 | err := v.Call("getcwd", &wd) 180 | if err != nil { 181 | t.Fatal(err) 182 | } 183 | } 184 | 185 | func TestCallWithNoArgsWithDisabledEmbed(t *testing.T) { 186 | t.Parallel() 187 | 188 | v := newChildProcess(t, ChildProcessArgs("--embed"), ChildProcessDisableEmbed()) 189 | 190 | var wd string 191 | err := v.Call("getcwd", &wd) 192 | if err != nil { 193 | t.Fatal(err) 194 | } 195 | } 196 | 197 | func TestStructValue(t *testing.T) { 198 | t.Parallel() 199 | 200 | v := newChildProcess(t) 201 | 202 | t.Run("Nvim", func(t *testing.T) { 203 | var expected, actual struct { 204 | Str string 205 | Num int 206 | } 207 | expected.Str = `Hello` 208 | expected.Num = 42 209 | if err := v.SetVar(`structvar`, &expected); err != nil { 210 | t.Fatal(err) 211 | } 212 | if err := v.Var(`structvar`, &actual); err != nil { 213 | t.Fatal(err) 214 | } 215 | 216 | if !reflect.DeepEqual(&actual, &expected) { 217 | t.Fatalf("SetVar: got %+v, want %+v", &actual, &expected) 218 | } 219 | }) 220 | 221 | t.Run("Batch", func(t *testing.T) { 222 | b := v.NewBatch() 223 | 224 | var expected, actual struct { 225 | Str string 226 | Num int 227 | } 228 | expected.Str = `Hello` 229 | expected.Num = 42 230 | b.SetVar(`structvar`, &expected) 231 | b.Var(`structvar`, &actual) 232 | if err := b.Execute(); err != nil { 233 | t.Fatal(err) 234 | } 235 | 236 | if !reflect.DeepEqual(&actual, &expected) { 237 | t.Fatalf("SetVar: got %+v, want %+v", &actual, &expected) 238 | } 239 | }) 240 | } 241 | -------------------------------------------------------------------------------- /msgpack/rpc/rpc_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "net" 8 | "reflect" 9 | "sync" 10 | "testing" 11 | ) 12 | 13 | func testClientServer(tb testing.TB, opts ...Option) (client, server *Endpoint, cleanup func()) { 14 | tb.Helper() 15 | 16 | opts = append(opts, WithLogf(tb.Logf)) 17 | 18 | serverConn, clientConn := net.Pipe() 19 | 20 | var err error 21 | server, err = NewEndpoint(serverConn, serverConn, serverConn, opts...) 22 | if err != nil { 23 | tb.Fatal(err) 24 | } 25 | 26 | client, err = NewEndpoint(clientConn, clientConn, clientConn, opts...) 27 | if err != nil { 28 | tb.Fatal(err) 29 | } 30 | 31 | var wg sync.WaitGroup 32 | wg.Add(1) 33 | go func() { 34 | if err := server.Serve(); err != nil && !errors.Is(err, io.ErrClosedPipe) { 35 | tb.Errorf("server: %v", err) 36 | } 37 | wg.Done() 38 | }() 39 | 40 | wg.Add(1) 41 | go func() { 42 | if err := client.Serve(); err != nil && !errors.Is(err, io.ErrClosedPipe) { 43 | tb.Errorf("server: %v", err) 44 | } 45 | wg.Done() 46 | }() 47 | 48 | if tb.Failed() { 49 | tb.FailNow() 50 | } 51 | 52 | cleanup = func() { 53 | client.Close() 54 | server.Close() 55 | wg.Wait() 56 | } 57 | 58 | return client, server, cleanup 59 | } 60 | 61 | func TestEndpoint(t *testing.T) { 62 | t.Parallel() 63 | 64 | client, server, cleanup := testClientServer(t) 65 | defer cleanup() 66 | 67 | addFn := func(a, b int) (int, error) { return a + b, nil } 68 | if err := server.Register("add", addFn); err != nil { 69 | t.Fatal(err) 70 | } 71 | 72 | // call 73 | var sum int 74 | if err := client.Call("add", &sum, 1, 2); err != nil { 75 | t.Fatal(err) 76 | } 77 | if sum != 3 { 78 | t.Fatalf("sum = %d, want %d", sum, 3) 79 | } 80 | 81 | // notification 82 | notifCh := make(chan string) 83 | n1Fn := func(s string) { notifCh <- s } 84 | if err := server.Register("n1", n1Fn); err != nil { 85 | t.Fatal(err) 86 | } 87 | 88 | const n = 10 89 | for i := 0; i < i; i++ { 90 | for j := 0; j < n; j++ { 91 | if err := client.Notify("n1", fmt.Sprintf("notif %d,%d", i, j)); err != nil { 92 | t.Fatal(err) 93 | } 94 | } 95 | for j := 0; j < n; j++ { 96 | got := <-notifCh 97 | want := fmt.Sprintf("notif %d,%d", i, j) 98 | if got != want { 99 | t.Fatalf("got %q, want %q", got, want) 100 | } 101 | } 102 | } 103 | } 104 | 105 | func TestArgs(t *testing.T) { 106 | t.Parallel() 107 | 108 | client, server, cleanup := testClientServer(t) 109 | defer cleanup() 110 | 111 | if err := server.Register("n", func(a, b string) ([]string, error) { 112 | return append([]string{a, b}), nil 113 | }); err != nil { 114 | t.Fatal(err) 115 | } 116 | 117 | if err := server.Register("v", func(a, b string, x ...string) ([]string, error) { 118 | return append([]string{a, b}, x...), nil 119 | }); err != nil { 120 | t.Fatal(err) 121 | } 122 | 123 | if err := server.Register("a", func(x ...string) ([]string, error) { 124 | return x, nil 125 | }); err != nil { 126 | t.Fatal(err) 127 | } 128 | 129 | argsTests := []struct { 130 | sm string 131 | args []any 132 | result []string 133 | }{ 134 | { 135 | sm: "n", 136 | args: []any{}, 137 | result: []string{"", ""}, 138 | }, 139 | { 140 | sm: "n", 141 | args: []any{"a"}, 142 | result: []string{"a", ""}, 143 | }, 144 | { 145 | sm: "n", 146 | args: []any{"a", "b"}, 147 | result: []string{"a", "b"}, 148 | }, 149 | { 150 | sm: "n", 151 | args: []any{"a", "b", "c"}, 152 | result: []string{"a", "b"}, 153 | }, 154 | { 155 | sm: "v", 156 | args: []any{}, 157 | result: []string{"", ""}, 158 | }, 159 | { 160 | sm: "v", 161 | args: []any{"a"}, 162 | result: []string{"a", ""}, 163 | }, 164 | { 165 | sm: "v", 166 | args: []any{"a", "b"}, 167 | result: []string{"a", "b"}, 168 | }, 169 | { 170 | sm: "v", 171 | args: []any{"a", "b", "x1"}, 172 | result: []string{"a", "b", "x1"}, 173 | }, 174 | { 175 | sm: "v", 176 | args: []any{"a", "b", "x1", "x2"}, 177 | result: []string{"a", "b", "x1", "x2"}, 178 | }, 179 | { 180 | sm: "v", 181 | args: []any{"a", "b", "x1", "x2", "x3"}, 182 | result: []string{"a", "b", "x1", "x2", "x3"}, 183 | }, 184 | { 185 | sm: "a", 186 | args: []any{}, 187 | result: []string(nil), 188 | }, 189 | { 190 | sm: "a", 191 | args: []any{"x1", "x2", "x3"}, 192 | result: []string{"x1", "x2", "x3"}, 193 | }, 194 | } 195 | for _, tt := range argsTests { 196 | t.Run(tt.sm, func(t *testing.T) { 197 | var result []string 198 | if err := client.Call(tt.sm, &result, tt.args...); err != nil { 199 | t.Fatalf("%s(%v) returned error %v", tt.sm, tt.args, err) 200 | } 201 | 202 | if !reflect.DeepEqual(result, tt.result) { 203 | t.Fatalf("%s(%v) returned %#v, want %#v", tt.sm, tt.args, result, tt.result) 204 | } 205 | }) 206 | } 207 | } 208 | 209 | func TestCallAfterClose(t *testing.T) { 210 | t.Parallel() 211 | 212 | client, server, cleanup := testClientServer(t) 213 | 214 | if err := server.Register("a", func() error { 215 | return nil 216 | }); err != nil { 217 | t.Fatal(err) 218 | } 219 | cleanup() 220 | 221 | if err := client.Call("a", nil); err == nil { 222 | t.Fatal("expected error") 223 | } 224 | } 225 | 226 | func TestExtraArgs(t *testing.T) { 227 | t.Parallel() 228 | 229 | client, server, cleanup := testClientServer(t) 230 | defer cleanup() 231 | 232 | if err := server.Register("a", func(hello string) error { 233 | if hello != "hello" { 234 | t.Fatal("first arg not equal to 'hello'") 235 | } 236 | return nil 237 | }, "hello"); err != nil { 238 | t.Fatal(err) 239 | } 240 | 241 | if err := client.Call("a", nil); err != nil { 242 | t.Fatal(err) 243 | } 244 | 245 | if err := server.Register("b", func(hello *string) error { 246 | if hello != nil { 247 | t.Fatal("first arg not nil") 248 | } 249 | return nil 250 | }, nil); err != nil { 251 | t.Fatal(err) 252 | } 253 | 254 | if err := client.Call("b", nil); err != nil { 255 | t.Fatal(err) 256 | } 257 | } 258 | 259 | func TestBadFunction(t *testing.T) { 260 | t.Parallel() 261 | 262 | _, server, cleanup := testClientServer(t) 263 | defer cleanup() 264 | 265 | if err := server.Register("a", func(hello string) int { 266 | return 1 267 | }); err == nil { 268 | t.Fatal("expected error, got nil") 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /msgpack/encode_test.go: -------------------------------------------------------------------------------- 1 | package msgpack 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type ( 10 | typedString string 11 | typedByte byte 12 | typedByteSlice []byte 13 | typedTypedByteSlice []typedByte 14 | ) 15 | 16 | type ( 17 | NotStruct int 18 | AnonFieldNotStruct struct { 19 | NotStruct 20 | } 21 | ) 22 | 23 | type ra struct { 24 | Sa string 25 | Ra *rb 26 | } 27 | 28 | type rb struct { 29 | Sb string 30 | Rb *ra 31 | } 32 | 33 | type me struct { 34 | s string 35 | } 36 | 37 | func (m me) MarshalMsgPack(enc *Encoder) error { 38 | return enc.PackString(m.s) 39 | } 40 | 41 | func TestEncode(t *testing.T) { 42 | t.Parallel() 43 | 44 | encodeTests := map[string]struct { 45 | v any 46 | data []any 47 | }{ 48 | "Bool": { 49 | v: true, 50 | data: []any{true}, 51 | }, 52 | "Int": { 53 | v: int(1), 54 | data: []any{(1)}, 55 | }, 56 | "Int8": { 57 | v: int8(2), 58 | data: []any{2}, 59 | }, 60 | "Int16": { 61 | v: int16(3), 62 | data: []any{3}, 63 | }, 64 | "Int32": { 65 | v: int32(4), 66 | data: []any{4}, 67 | }, 68 | "Int64": { 69 | v: int64(5), 70 | data: []any{5}, 71 | }, 72 | "Uint": { 73 | v: uint(1), 74 | data: []any{1}, 75 | }, 76 | "Uint8": { 77 | v: uint8(2), 78 | data: []any{2}, 79 | }, 80 | "Uint16": { 81 | v: uint16(3), 82 | data: []any{3}, 83 | }, 84 | "Uint32": { 85 | v: uint32(4), 86 | data: []any{4}, 87 | }, 88 | "Uint64": { 89 | v: uint64(5), 90 | data: []any{5}, 91 | }, 92 | "Float32": { 93 | v: float32(5.0), 94 | data: []any{5.0}, 95 | }, 96 | "Float64": { 97 | v: float64(6.0), 98 | data: []any{6.0}, 99 | }, 100 | "String": { 101 | v: "hello", 102 | data: []any{"hello"}, 103 | }, 104 | "Bytes": { 105 | v: []byte("world"), 106 | data: []any{[]byte("world")}, 107 | }, 108 | "typedString": { 109 | v: typedString("quux"), 110 | data: []any{"quux"}, 111 | }, 112 | "typedByteSlice": { 113 | v: typedByteSlice("foo"), 114 | data: []any{ 115 | []byte("foo"), 116 | }, 117 | }, 118 | "typedTypedByteSlice": { 119 | v: typedTypedByteSlice("bar"), 120 | data: []any{[]byte("bar")}, 121 | }, 122 | "StringSlice/Nil": { 123 | v: []string(nil), 124 | data: []any{nil}, 125 | }, 126 | "StringSlice/Empty": { 127 | v: []string{}, 128 | data: []any{arrayLen(0)}, 129 | }, 130 | "StringSlice/Value": { 131 | v: []string{"hello", "world"}, 132 | data: []any{ 133 | arrayLen(2), 134 | "hello", 135 | "world", 136 | }, 137 | }, 138 | "StringArray/Value": { 139 | v: [2]string{"hello", "world"}, 140 | data: []any{ 141 | arrayLen(2), 142 | "hello", 143 | "world", 144 | }, 145 | }, 146 | "MapStringString/Nil": { 147 | v: map[string]string(nil), 148 | data: []any{nil}, 149 | }, 150 | "MapStringString/Value": { 151 | v: map[string]string{"hello": "world"}, 152 | data: []any{ 153 | mapLen(1), 154 | "hello", 155 | "world", 156 | }, 157 | }, 158 | "IntPointer": { 159 | v: new(int), 160 | data: []any{0}, 161 | }, 162 | "TagNames": { 163 | v: struct { 164 | A int 165 | B int `msgpack:"BB"` 166 | C int `msgpack:"omitempty"` 167 | D int `msgpack:"-"` 168 | }{ 169 | A: 1, 170 | B: 2, 171 | C: 3, 172 | D: 4, 173 | }, 174 | data: []any{ 175 | mapLen(3), 176 | "A", 1, 177 | "BB", 2, 178 | "omitempty", 3, 179 | }, 180 | }, 181 | "StructAsArray": { 182 | v: struct { 183 | I int `msgpack:",array"` 184 | S string 185 | }{ 186 | I: 22, 187 | S: "skidoo", 188 | }, 189 | data: []any{arrayLen(2), 22, "skidoo"}, 190 | }, 191 | "OmitEmpty": { 192 | v: struct { 193 | B bool `msgpack:"b,omitempty"` 194 | Bo bool `msgpack:"bo,omitempty"` 195 | Be bool `msgpack:"be,omitempty" empty:"true"` 196 | 197 | S string `msgpack:"s,omitempty"` 198 | So string `msgpack:"so,omitempty"` 199 | Se string `msgpack:"se,omitempty" empty:"blank"` 200 | 201 | I int `msgpack:"i,omitempty"` 202 | Io int `msgpack:"io,omitempty"` 203 | Ie int `msgpack:"ie,omitempty" empty:"-1"` 204 | 205 | U uint `msgpack:"u,omitempty"` 206 | Uo uint `msgpack:"uo,omitempty"` 207 | 208 | F float64 `msgpack:"f,omitempty"` 209 | Fo float64 `msgpack:"fo,omitempty"` 210 | 211 | D float64 `msgpack:"d,omitempty"` 212 | Do float64 `msgpack:"do,omitempty"` 213 | 214 | Sl []string `msgpack:"sl,omitempty"` 215 | Slo []string `msgpack:"slo,omitempty"` 216 | 217 | M map[string]string `msgpack:"m,omitempty"` 218 | Mo map[string]string `msgpack:"mo,omitempty"` 219 | 220 | P *int `msgpack:"p,omitempty"` 221 | Po *int `msgpack:"po,omitempty"` 222 | }{ 223 | B: false, 224 | Be: true, 225 | S: "1", 226 | Se: "blank", 227 | I: 2, 228 | Ie: -1, 229 | U: 3, 230 | F: 4.0, 231 | D: 5.0, 232 | Sl: []string{"hello"}, 233 | M: map[string]string{"foo": "bar"}, 234 | P: new(int), 235 | }, 236 | data: []any{ 237 | mapLen(8), 238 | "s", "1", 239 | "i", 2, 240 | "u", 3, 241 | "f", 4.0, 242 | "d", 5.0, 243 | "sl", arrayLen(1), "hello", 244 | "m", mapLen(1), "foo", "bar", 245 | "p", 0, 246 | }, 247 | }, 248 | "Struct": { 249 | v: &ra{"foo", &rb{"bar", &ra{"quux", nil}}}, 250 | data: []any{ 251 | mapLen(2), 252 | "Sa", "foo", 253 | "Ra", mapLen(2), 254 | "Sb", "bar", 255 | "Rb", mapLen(2), 256 | "Sa", "quux", 257 | "Ra", nil, 258 | }, 259 | }, 260 | "MarshalMsgPack": { 261 | v: &me{"hello"}, 262 | data: []any{"hello"}, 263 | }, 264 | "Interface": { 265 | v: []any{"foo", "bar"}, 266 | data: []any{arrayLen(2), "foo", "bar"}, 267 | }, 268 | "Nil": { 269 | v: nil, 270 | data: []any{nil}, 271 | }, 272 | } 273 | for name, tt := range encodeTests { 274 | tt := tt 275 | t.Run(name, func(t *testing.T) { 276 | t.Parallel() 277 | 278 | var buf bytes.Buffer 279 | enc := NewEncoder(&buf) 280 | if err := enc.Encode(tt.v); err != nil { 281 | t.Fatalf("encode %#v returned error %v", tt.v, err) 282 | } 283 | data, err := unpack(buf.Bytes()) 284 | if err != nil { 285 | t.Fatalf("unpack %#v returned error %v", tt.v, err) 286 | } 287 | if !reflect.DeepEqual(data, tt.data) { 288 | t.Fatalf("encode %#v\n\t got: %#v\n\twant: %#v", tt.v, data, tt.data) 289 | } 290 | }) 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /msgpack/pack_test.go: -------------------------------------------------------------------------------- 1 | package msgpack 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestPack(t *testing.T) { 11 | t.Parallel() 12 | 13 | packTests := map[string]struct { 14 | // Expected value 15 | v any 16 | // Hex encodings of typ, v 17 | hs string 18 | }{ 19 | "Bool/True": { 20 | v: true, 21 | hs: "c3", 22 | }, 23 | "Bool/False": { 24 | v: false, 25 | hs: "c2", 26 | }, 27 | "Int64/0x0": { 28 | v: int64(0x0), 29 | hs: "00", 30 | }, 31 | "Int64/0x1": { 32 | v: int64(0x1), 33 | hs: "01", 34 | }, 35 | "Int64/0x7f": { 36 | v: int64(0x7f), 37 | hs: "7f", 38 | }, 39 | "Int64/0x80": { 40 | v: int64(0x80), 41 | hs: "cc80", 42 | }, 43 | "Int64/0x7fff": { 44 | v: int64(0x7fff), 45 | hs: "cd7fff", 46 | }, 47 | "Int64/0x8000": { 48 | v: int64(0x8000), 49 | hs: "cd8000", 50 | }, 51 | "Int64/0x7fffffff": { 52 | v: int64(0x7fffffff), 53 | hs: "ce7fffffff", 54 | }, 55 | "Int64/0x80000000": { 56 | v: int64(0x80000000), 57 | hs: "ce80000000", 58 | }, 59 | "Int64/0x7fffffffffffffff": { 60 | v: int64(0x7fffffffffffffff), 61 | hs: "cf7fffffffffffffff", 62 | }, 63 | "Int64/-0x1": { 64 | v: int64(-0x1), 65 | hs: "ff", 66 | }, 67 | "Int64/-0x20": { 68 | v: int64(-0x20), 69 | hs: "e0", 70 | }, 71 | "Int64/-0x21": { 72 | v: int64(-0x21), 73 | hs: "d0df", 74 | }, 75 | "Int64/-0x80": { 76 | v: int64(-0x80), 77 | hs: "d080", 78 | }, 79 | "Int64/-0x81": { 80 | v: int64(-0x81), 81 | hs: "d1ff7f", 82 | }, 83 | "Int64/-0x8000": { 84 | v: int64(-0x8000), 85 | hs: "d18000", 86 | }, 87 | "Int64/-0x8001": { 88 | v: int64(-0x8001), 89 | hs: "d2ffff7fff", 90 | }, 91 | "Int64/-0x80000000": { 92 | v: int64(-0x80000000), 93 | hs: "d280000000", 94 | }, 95 | "Int64/-0x80000001": { 96 | v: int64(-0x80000001), 97 | hs: "d3ffffffff7fffffff", 98 | }, 99 | "Int64/-0x8000000000000000": { 100 | v: int64(-0x8000000000000000), 101 | hs: "d38000000000000000", 102 | }, 103 | "Uint64/0x0": { 104 | v: uint64(0x0), 105 | hs: "00", 106 | }, 107 | "Uint64/0x1": { 108 | v: uint64(0x1), 109 | hs: "01", 110 | }, 111 | "Uint64/0x7f": { 112 | v: uint64(0x7f), 113 | hs: "7f", 114 | }, 115 | "Uint64/0xff": { 116 | v: uint64(0xff), 117 | hs: "ccff", 118 | }, 119 | "Uint64/0x100": { 120 | v: uint64(0x100), 121 | hs: "cd0100", 122 | }, 123 | "Uint64/0xffff": { 124 | v: uint64(0xffff), 125 | hs: "cdffff", 126 | }, 127 | "Uint64/0x10000": { 128 | v: uint64(0x10000), 129 | hs: "ce00010000", 130 | }, 131 | "Uint64/0xffffffff": { 132 | v: uint64(0xffffffff), 133 | hs: "ceffffffff", 134 | }, 135 | "Uint64/0x100000000": { 136 | v: uint64(0x100000000), 137 | hs: "cf0000000100000000", 138 | }, 139 | "Uint64/0xffffffffffffffff": { 140 | v: uint64(0xffffffffffffffff), 141 | hs: "cfffffffffffffffff", 142 | }, 143 | "Float64/1.23456": { 144 | v: float64(1.23456), 145 | hs: "cb3ff3c0c1fc8f3238", 146 | }, 147 | "String/Empty": { 148 | v: string(""), 149 | hs: "a0", 150 | }, 151 | "String/1": { 152 | v: string("1"), 153 | hs: "a131", 154 | }, 155 | "String/1234567890123456789012345678901": { 156 | v: string("1234567890123456789012345678901"), 157 | hs: "bf31323334353637383930313233343536373839303132333435363738393031", 158 | }, 159 | "String/12345678901234567890123456789012": { 160 | v: string("12345678901234567890123456789012"), 161 | hs: "d9203132333435363738393031323334353637383930313233343536373839303132", 162 | }, 163 | "Binary/Empty": { 164 | v: []byte(""), 165 | hs: "c400", 166 | }, 167 | "Binary/1": { 168 | v: []byte("1"), 169 | hs: "c40131", 170 | }, 171 | "MapLen/0x0": { 172 | v: mapLen(0x0), 173 | hs: "80", 174 | }, 175 | "MapLen/0x1": { 176 | v: mapLen(0x1), 177 | hs: "81", 178 | }, 179 | "MapLen/0xf": { 180 | v: mapLen(0xf), 181 | hs: "8f", 182 | }, 183 | "MapLen/0x10": { 184 | v: mapLen(0x10), 185 | hs: "de0010", 186 | }, 187 | "MapLen/0xffff": { 188 | v: mapLen(0xffff), 189 | hs: "deffff", 190 | }, 191 | "MapLen/0x10000": { 192 | v: mapLen(0x10000), 193 | hs: "df00010000", 194 | }, 195 | "MapLen/0xffffffff": { 196 | v: mapLen(0xffffffff), 197 | hs: "dfffffffff", 198 | }, 199 | "ArrayLen/0x0": { 200 | v: arrayLen(0x0), 201 | hs: "90", 202 | }, 203 | "ArrayLen/0x1": { 204 | v: arrayLen(0x1), 205 | hs: "91", 206 | }, 207 | "ArrayLen/0xf": { 208 | v: arrayLen(0xf), 209 | hs: "9f", 210 | }, 211 | "ArrayLen/0x10": { 212 | v: arrayLen(0x10), 213 | hs: "dc0010", 214 | }, 215 | "ArrayLen/0xffff": { 216 | v: arrayLen(0xffff), 217 | hs: "dcffff", 218 | }, 219 | "ArrayLen/0x10000": { 220 | v: arrayLen(0x10000), 221 | hs: "dd00010000", 222 | }, 223 | "ArrayLen/0xffffffff": { 224 | v: arrayLen(0xffffffff), 225 | hs: "ddffffffff", 226 | }, 227 | "Extension/1/Empty": { 228 | v: extension{1, ""}, 229 | hs: "c70001", 230 | }, 231 | "Extension/2/1": { 232 | v: extension{2, "1"}, 233 | hs: "d40231", 234 | }, 235 | "Extension/3/12": { 236 | v: extension{3, "12"}, 237 | hs: "d5033132", 238 | }, 239 | "Extension/4/1234": { 240 | v: extension{4, "1234"}, 241 | hs: "d60431323334", 242 | }, 243 | "Extension/5/12345678": { 244 | v: extension{5, "12345678"}, 245 | hs: "d7053132333435363738", 246 | }, 247 | "Extension/6/1234567890123456": { 248 | v: extension{6, "1234567890123456"}, 249 | hs: "d80631323334353637383930313233343536", 250 | }, 251 | "Extension/7/12345678901234567": { 252 | v: extension{7, "12345678901234567"}, 253 | hs: "c711073132333435363738393031323334353637", 254 | }, 255 | "Nil": { 256 | v: nil, 257 | hs: "c0", 258 | }, 259 | } 260 | for name, tt := range packTests { 261 | tt := tt 262 | t.Run(name, func(t *testing.T) { 263 | t.Parallel() 264 | 265 | var arg string 266 | switch reflect.ValueOf(tt.v).Kind() { 267 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 268 | arg = fmt.Sprintf("%T %x", tt.v, tt.v) 269 | default: 270 | arg = fmt.Sprintf("%T %v", tt.v, tt.v) 271 | } 272 | 273 | p, err := pack(tt.v) 274 | if err != nil { 275 | t.Fatalf("pack %s returned error %v", arg, err) 276 | } 277 | 278 | h := hex.EncodeToString(p) 279 | if h != tt.hs { 280 | t.Fatalf("pack %s returned %s, want %s", arg, h, tt.hs) 281 | } 282 | }) 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /msgpack/msgpack_test.go: -------------------------------------------------------------------------------- 1 | package msgpack 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | "math" 9 | "reflect" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | // pack packs the values vs and returns the result. 15 | // 16 | // Go Type Encoder method 17 | // nil PackNil 18 | // bool PackBool 19 | // int64 PackInt 20 | // uint64 PackUint 21 | // float64 PackFloat 22 | // arrayLen PackArrayLen 23 | // mapLen PackMapLen 24 | // string PackString(s, false) 25 | // []byte PackBytes(s, true) 26 | // extension PackExtension(k, d) 27 | func pack(vs ...any) ([]byte, error) { 28 | var buf bytes.Buffer 29 | enc := NewEncoder(&buf) 30 | 31 | for _, v := range vs { 32 | var err error 33 | switch v := v.(type) { 34 | case int64: 35 | err = enc.PackInt(v) 36 | case uint64: 37 | err = enc.PackUint(v) 38 | case bool: 39 | err = enc.PackBool(v) 40 | case float64: 41 | err = enc.PackFloat(v) 42 | case arrayLen: 43 | err = enc.PackArrayLen(int64(v)) 44 | case mapLen: 45 | err = enc.PackMapLen(int64(v)) 46 | case string: 47 | err = enc.PackString(v) 48 | case []byte: 49 | err = enc.PackBinary(v) 50 | case extension: 51 | err = enc.PackExtension(v.k, []byte(v.d)) 52 | case nil: 53 | err = enc.PackNil() 54 | default: 55 | err = fmt.Errorf("no pack for type %T", v) 56 | } 57 | 58 | if err != nil { 59 | return nil, err 60 | } 61 | } 62 | 63 | return buf.Bytes(), nil 64 | } 65 | 66 | // unpack unpacks a byte slice to the following types. 67 | // 68 | // Type Go 69 | // Nil nil 70 | // Bool bool 71 | // Int int 72 | // Uint int 73 | // Float float64 74 | // ArrayLen arrayLen 75 | // MapLen mapLen 76 | // String string 77 | // Binary []byte 78 | // Extension extension 79 | // 80 | // This function is not suitable for unpack tests because the integer and float 81 | // types are mapped to int and float64 respectively. 82 | func unpack(p []byte) ([]any, error) { 83 | var data []any 84 | u := NewDecoder(bytes.NewReader(p)) 85 | 86 | for { 87 | err := u.Unpack() 88 | if err == io.EOF { 89 | break 90 | } else if err != nil { 91 | return nil, err 92 | } 93 | 94 | var v any 95 | switch u.Type() { 96 | case Nil: 97 | v = nil 98 | case Bool: 99 | v = u.Bool() 100 | case Int: 101 | v = int(u.Int()) 102 | case Uint: 103 | v = int(u.Uint()) 104 | case Float: 105 | v = u.Float() 106 | case Binary: 107 | v = u.Bytes() 108 | case String: 109 | v = u.String() 110 | case ArrayLen: 111 | v = arrayLen(u.Int()) 112 | case MapLen: 113 | v = mapLen(u.Int()) 114 | case Extension: 115 | v = extension{u.Extension(), u.String()} 116 | default: 117 | return nil, fmt.Errorf("unpack %d not handled", u.Type()) 118 | } 119 | 120 | data = append(data, v) 121 | } 122 | 123 | return data, nil 124 | } 125 | 126 | type ( 127 | arrayLen uint32 128 | ) 129 | 130 | type ( 131 | mapLen uint32 132 | ) 133 | 134 | type extension struct { 135 | k int 136 | d string 137 | } 138 | 139 | type testExtension1 struct { 140 | data []byte 141 | } 142 | 143 | // Make sure a testExtension1 implements the Marshaler and Unmarshaler interfaces. 144 | var _ Marshaler = (*testExtension1)(nil) 145 | var _ Unmarshaler = (*testExtension1)(nil) 146 | 147 | func (x testExtension1) MarshalMsgPack(enc *Encoder) error { 148 | return enc.PackExtension(1, x.data) 149 | } 150 | 151 | func (x *testExtension1) UnmarshalMsgPack(dec *Decoder) error { 152 | if dec.Type() != Extension || dec.Extension() != 1 { 153 | err := &DecodeConvertError{ 154 | SrcType: dec.Type(), 155 | DestType: reflect.TypeOf(x), 156 | } 157 | dec.Skip() 158 | return err 159 | } 160 | x.data = dec.Bytes() 161 | return nil 162 | } 163 | 164 | var testExtensionMap = ExtensionMap{ 165 | 1: func(data []byte) (any, error) { return testExtension1{data}, nil }, 166 | } 167 | 168 | func ptrInt(i int) *int { 169 | return &i 170 | } 171 | 172 | func ptrUint(u uint) *uint { 173 | return &u 174 | } 175 | 176 | func makeString(sz int) string { 177 | var s strings.Builder 178 | var x int 179 | for i := 0; i < sz; i++ { 180 | if x >= 10 { 181 | x = 0 182 | } 183 | s.WriteByte(byte(x + 48)) 184 | x++ 185 | } 186 | return s.String() 187 | } 188 | 189 | type testReader struct { 190 | p []byte 191 | pos int 192 | } 193 | 194 | func NewTestReader(b []byte) io.Reader { 195 | return &testReader{p: b} 196 | } 197 | 198 | func (r *testReader) Read(b []byte) (int, error) { 199 | n := copy(b, r.p[r.pos:]) 200 | if n < len(r.p) { 201 | r.pos = r.pos + n 202 | } 203 | 204 | if r.pos >= len(r.p) { 205 | r.pos = 0 206 | } 207 | return n, nil 208 | } 209 | 210 | type WriteByteWriter interface { 211 | io.Writer 212 | io.ByteWriter 213 | } 214 | 215 | type testArrayBuilder struct { 216 | buffer []any 217 | tb testing.TB 218 | } 219 | 220 | func NewTestArrayBuilder(tb testing.TB) *testArrayBuilder { 221 | return &testArrayBuilder{ 222 | tb: tb, 223 | } 224 | } 225 | 226 | func (e *testArrayBuilder) Add(v any) { 227 | e.buffer = append(e.buffer, v) 228 | } 229 | 230 | func (e *testArrayBuilder) Count() int64 { 231 | return int64(len(e.buffer)) 232 | } 233 | 234 | func (e testArrayBuilder) encode(w WriteByteWriter) { 235 | e.tb.Helper() 236 | 237 | c := len(e.buffer) 238 | switch { 239 | case c < 16: 240 | if err := w.WriteByte(fixArrayCodeMin + byte(c)); err != nil { 241 | e.tb.Fatalf("msgpack: failed to write fixed array header: %v", err) 242 | } 243 | case c < math.MaxUint16: 244 | if err := w.WriteByte(array16Code); err != nil { 245 | e.tb.Fatalf("msgpack: failed to write 16-bit array header prefix: %v", err) 246 | } 247 | b := make([]byte, 5) 248 | binary.BigEndian.PutUint16(b, uint16(c)) 249 | if _, err := w.Write(b); err != nil { 250 | e.tb.Fatalf("msgpack: failed to write 16-bit array header: %v", err) 251 | } 252 | case c < math.MaxUint32: 253 | if err := w.WriteByte(array32Code); err != nil { 254 | e.tb.Fatalf("msgpack: failed to write 32-bit array header prefix: %v", err) 255 | } 256 | b := make([]byte, 7) 257 | binary.BigEndian.PutUint32(b, uint32(c)) 258 | if _, err := w.Write(b); err != nil { 259 | e.tb.Fatalf("msgpack: failed to write 32-bit array header: %v", err) 260 | } 261 | default: 262 | e.tb.Fatalf("msgpack: array element count out of range (%d)", c) 263 | } 264 | 265 | enc := NewEncoder(w) 266 | for _, v := range e.buffer { 267 | if err := enc.Encode(v); err != nil { 268 | e.tb.Fatalf("msgpack: failed to encode array element %s: %v", reflect.TypeOf(v), err) 269 | } 270 | } 271 | } 272 | 273 | func (e testArrayBuilder) Bytes() []byte { 274 | var buf bytes.Buffer 275 | e.encode(&buf) 276 | return buf.Bytes() 277 | } 278 | 279 | type testMapBuilder struct { 280 | valuas []any 281 | tb testing.TB 282 | } 283 | 284 | func NewTestMapBuilder(tb testing.TB) *testMapBuilder { 285 | return &testMapBuilder{ 286 | tb: tb, 287 | } 288 | } 289 | 290 | func (m *testMapBuilder) Add(key string, value any) { 291 | m.valuas = append(m.valuas, key, value) 292 | } 293 | 294 | func (e *testMapBuilder) Count() int64 { 295 | return int64(len(e.valuas)) / 2 296 | } 297 | 298 | func (m *testMapBuilder) encode(w WriteByteWriter) { 299 | m.tb.Helper() 300 | 301 | c := len(m.valuas) / 2 302 | switch { 303 | case c < 16: 304 | if err := w.WriteByte(fixMapCodeMin + byte(c)); err != nil { 305 | m.tb.Fatalf("msgpack: failed to write element size prefix: %v", err) 306 | } 307 | case c < math.MaxUint16: 308 | if err := w.WriteByte(map16Code); err != nil { 309 | m.tb.Fatalf("msgpack: failed to write 16-bit element size prefix: %v", err) 310 | } 311 | b := make([]byte, 5) 312 | binary.BigEndian.PutUint16(b, uint16(c)) 313 | if _, err := w.Write(b); err != nil { 314 | m.tb.Fatalf("msgpack: failed to write 16-bit element size: %v", err) 315 | } 316 | case c < math.MaxUint32: 317 | if err := w.WriteByte(map32Code); err != nil { 318 | m.tb.Fatalf("msgpack: failed to write 32-bit element size prefix: %v", err) 319 | } 320 | b := make([]byte, 7) 321 | binary.BigEndian.PutUint32(b, uint32(c)) 322 | if _, err := w.Write(b); err != nil { 323 | m.tb.Fatalf("msgpack: failed to write 32-bit element size: %v", err) 324 | } 325 | default: 326 | m.tb.Fatalf("msgpack: map element count out of range (%d", c) 327 | } 328 | 329 | e := NewEncoder(w) 330 | for i := 0; i < c; i++ { 331 | if err := e.Encode(m.valuas[i*2]); err != nil { 332 | m.tb.Fatalf("msgpack: map builder: failed to encode map key %s", m.valuas[i]) 333 | } 334 | 335 | if err := e.Encode(m.valuas[i*2+1]); err != nil { 336 | m.tb.Fatalf("msgpack: map builder: failed to encode map element for %s: %v", m.valuas[i], err) 337 | } 338 | } 339 | } 340 | 341 | func (m *testMapBuilder) Bytes() []byte { 342 | var buf bytes.Buffer 343 | m.encode(&buf) 344 | return buf.Bytes() 345 | } 346 | -------------------------------------------------------------------------------- /msgpack/pack.go: -------------------------------------------------------------------------------- 1 | package msgpack 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "math" 7 | ) 8 | 9 | var ( 10 | // ErrLongStringOrBinary is the long string or binary error. 11 | ErrLongStringOrBinary = errors.New("msgpack: long string or binary") 12 | 13 | // ErrIllegalSize is the illegal array or map size error. 14 | ErrIllegalSize = errors.New("msgpack: illegal array or map size") 15 | ) 16 | 17 | // Encoder writes values in MessagePack format. 18 | type Encoder struct { 19 | buf [32]byte 20 | w io.Writer 21 | writeString func(string) (int, error) 22 | err error // permanent error 23 | } 24 | 25 | // NewEncoder allocates and initializes a new Unpacker. 26 | func NewEncoder(w io.Writer) *Encoder { 27 | e := &Encoder{ 28 | w: w, 29 | } 30 | 31 | if ws, ok := w.(interface { 32 | WriteString(string) (int, error) 33 | }); ok { 34 | e.writeString = ws.WriteString 35 | } else { 36 | e.writeString = e.writeStringUnopt 37 | } 38 | 39 | return e 40 | } 41 | 42 | func (e *Encoder) writeStringUnopt(s string) (int, error) { 43 | if len(s) <= len(e.buf) { 44 | copy(e.buf[:], s) 45 | return e.w.Write(e.buf[:len(s)]) 46 | } 47 | 48 | return e.w.Write([]byte(s)) 49 | } 50 | 51 | type numCodes struct { 52 | c8 byte 53 | c16 byte 54 | c32 byte 55 | c64 byte 56 | } 57 | 58 | var ( 59 | stringLenEncodings = &numCodes{ 60 | c8: string8Code, 61 | c16: string16Code, 62 | c32: string32Code, 63 | c64: 0, 64 | } 65 | binaryLenEncodings = &numCodes{ 66 | c8: binary8Code, 67 | c16: binary16Code, 68 | c32: binary32Code, 69 | c64: 0, 70 | } 71 | arrayLenEncodings = &numCodes{ 72 | c8: 0, 73 | c16: array16Code, 74 | c32: array32Code, 75 | c64: 0, 76 | } 77 | mapLenEncodings = &numCodes{ 78 | c8: 0, 79 | c16: map16Code, 80 | c32: map32Code, 81 | c64: 0, 82 | } 83 | extLenEncodings = &numCodes{ 84 | c8: ext8Code, 85 | c16: ext16Code, 86 | c32: ext32Code, 87 | c64: 0, 88 | } 89 | uintEncodings = &numCodes{ 90 | c8: uint8Code, 91 | c16: uint16Code, 92 | c32: uint32Code, 93 | c64: uint64Code, 94 | } 95 | ) 96 | 97 | func (e *Encoder) encodeNum(fc *numCodes, v uint64) []byte { 98 | switch { 99 | case fc.c8 != 0 && v <= math.MaxUint8: 100 | e.buf[0] = fc.c8 101 | e.buf[1] = byte(v) 102 | return e.buf[:2] 103 | 104 | case v <= math.MaxUint16: 105 | e.buf[0] = fc.c16 106 | e.buf[1] = byte(v >> 8) 107 | e.buf[2] = byte(v) 108 | return e.buf[:3] 109 | 110 | case v <= math.MaxUint32: 111 | e.buf[0] = fc.c32 112 | e.buf[1] = byte(v >> 24) 113 | e.buf[2] = byte(v >> 16) 114 | e.buf[3] = byte(v >> 8) 115 | e.buf[4] = byte(v) 116 | return e.buf[:5] 117 | 118 | default: 119 | e.buf[0] = fc.c64 120 | e.buf[1] = byte(v >> 56) 121 | e.buf[2] = byte(v >> 48) 122 | e.buf[3] = byte(v >> 40) 123 | e.buf[4] = byte(v >> 32) 124 | e.buf[5] = byte(v >> 24) 125 | e.buf[6] = byte(v >> 16) 126 | e.buf[7] = byte(v >> 8) 127 | e.buf[8] = byte(v) 128 | return e.buf[:9] 129 | } 130 | } 131 | 132 | // PackBool writes a Bool value to the MessagePack stream. 133 | func (e *Encoder) PackBool(b bool) error { 134 | if b { 135 | e.buf[0] = trueCode 136 | } else { 137 | e.buf[0] = falseCode 138 | } 139 | 140 | _, err := e.w.Write(e.buf[:1]) 141 | return err 142 | } 143 | 144 | // PackInt packs an Int value to the MessagePack stream. 145 | func (e *Encoder) PackInt(v int64) error { 146 | var b []byte 147 | 148 | switch { 149 | case 0 <= v && v <= math.MaxInt8: 150 | e.buf[0] = byte(v) 151 | b = e.buf[:1] 152 | 153 | case v > 0: 154 | // Pack as unsigned for compatibility with other encoders. 155 | b = e.encodeNum(uintEncodings, uint64(v)) 156 | 157 | case v >= -32: 158 | e.buf[0] = byte(v) 159 | b = e.buf[:1] 160 | 161 | case v >= math.MinInt8: 162 | e.buf[0] = int8Code 163 | e.buf[1] = byte(v) 164 | b = e.buf[:2] 165 | 166 | case v >= math.MinInt16: 167 | e.buf[0] = int16Code 168 | e.buf[1] = byte(v >> 8) 169 | e.buf[2] = byte(v) 170 | b = e.buf[:3] 171 | 172 | case v >= math.MinInt32: 173 | e.buf[0] = int32Code 174 | e.buf[1] = byte(v >> 24) 175 | e.buf[2] = byte(v >> 16) 176 | e.buf[3] = byte(v >> 8) 177 | e.buf[4] = byte(v) 178 | b = e.buf[:5] 179 | 180 | default: 181 | e.buf[0] = int64Code 182 | e.buf[1] = byte(v >> 56) 183 | e.buf[2] = byte(v >> 48) 184 | e.buf[3] = byte(v >> 40) 185 | e.buf[4] = byte(v >> 32) 186 | e.buf[5] = byte(v >> 24) 187 | e.buf[6] = byte(v >> 16) 188 | e.buf[7] = byte(v >> 8) 189 | e.buf[8] = byte(v) 190 | b = e.buf[:9] 191 | } 192 | 193 | _, err := e.w.Write(b) 194 | return err 195 | } 196 | 197 | // PackUint packs a Uint value to the message pack stream. 198 | func (e *Encoder) PackUint(v uint64) error { 199 | var b []byte 200 | 201 | if v <= math.MaxInt8 { 202 | // Pack as signed for compatibility with other encoders. 203 | e.buf[0] = byte(v) 204 | b = e.buf[:1] 205 | } else { 206 | b = e.encodeNum(uintEncodings, v) 207 | } 208 | 209 | _, err := e.w.Write(b) 210 | return err 211 | } 212 | 213 | // PackFloat writes a Float value to the MessagePack stream. 214 | func (e *Encoder) PackFloat(f float64) error { 215 | n := math.Float64bits(f) 216 | e.buf[0] = float64Code 217 | e.buf[1] = byte(n >> 56) 218 | e.buf[2] = byte(n >> 48) 219 | e.buf[3] = byte(n >> 40) 220 | e.buf[4] = byte(n >> 32) 221 | e.buf[5] = byte(n >> 24) 222 | e.buf[6] = byte(n >> 16) 223 | e.buf[7] = byte(n >> 8) 224 | e.buf[8] = byte(n) 225 | 226 | _, err := e.w.Write(e.buf[:9]) 227 | return err 228 | } 229 | 230 | func (e *Encoder) packStringLen(n int64) error { 231 | var b []byte 232 | 233 | if n < 32 { 234 | e.buf[0] = byte(fixStringCodeMin + n) 235 | b = e.buf[:1] 236 | } else if n <= math.MaxUint32 { 237 | b = e.encodeNum(stringLenEncodings, uint64(n)) 238 | } else { 239 | return ErrLongStringOrBinary 240 | } 241 | 242 | _, err := e.w.Write(b) 243 | return err 244 | } 245 | 246 | // PackString writes a String value to the MessagePack stream. 247 | func (e *Encoder) PackString(v string) error { 248 | if err := e.packStringLen(int64(len(v))); err != nil { 249 | return err 250 | } 251 | 252 | _, err := e.writeString(v) 253 | return err 254 | } 255 | 256 | // PackStringBytes writes a String value to the MessagePack stream. 257 | func (e *Encoder) PackStringBytes(v []byte) error { 258 | if err := e.packStringLen(int64(len(v))); err != nil { 259 | return err 260 | } 261 | 262 | _, err := e.w.Write(v) 263 | return err 264 | } 265 | 266 | // PackBinary writes a Binary value to the MessagePack stream. 267 | func (e *Encoder) PackBinary(v []byte) error { 268 | n := uint64(len(v)) 269 | 270 | if n > math.MaxUint32 { 271 | return ErrLongStringOrBinary 272 | } 273 | 274 | if _, err := e.w.Write(e.encodeNum(binaryLenEncodings, n)); err != nil { 275 | return err 276 | } 277 | 278 | _, err := e.w.Write(v) 279 | return err 280 | } 281 | 282 | func (e *Encoder) packArrayMapLen(fixMin int64, fc *numCodes, v int64) error { 283 | if v < 0 || v > math.MaxUint32 { 284 | return ErrIllegalSize 285 | } 286 | 287 | var b []byte 288 | if v < 16 { 289 | e.buf[0] = byte(fixMin + v) 290 | b = e.buf[:1] 291 | } else { 292 | b = e.encodeNum(fc, uint64(v)) 293 | } 294 | 295 | _, err := e.w.Write(b) 296 | return err 297 | } 298 | 299 | // PackArrayLen write an Array length to the MessagePack stream. The 300 | // application must write n objects to the stream following this call. 301 | func (e *Encoder) PackArrayLen(n int64) error { 302 | return e.packArrayMapLen(fixArrayCodeMin, arrayLenEncodings, n) 303 | } 304 | 305 | // PackMapLen write an Map length to the MessagePack stream. The application 306 | // must write n key-value pairs to the stream following this call. 307 | func (e *Encoder) PackMapLen(n int64) error { 308 | return e.packArrayMapLen(fixMapCodeMin, mapLenEncodings, n) 309 | } 310 | 311 | // PackExtension writes an extension to the MessagePack stream. 312 | func (e *Encoder) PackExtension(kind int, data []byte) error { 313 | var b []byte 314 | 315 | switch len(data) { 316 | case 1: 317 | e.buf[0] = fixExt1Code 318 | e.buf[1] = byte(kind) 319 | b = e.buf[:2] 320 | 321 | case 2: 322 | e.buf[0] = fixExt2Code 323 | e.buf[1] = byte(kind) 324 | b = e.buf[:2] 325 | 326 | case 4: 327 | e.buf[0] = fixExt4Code 328 | e.buf[1] = byte(kind) 329 | b = e.buf[:2] 330 | 331 | case 8: 332 | e.buf[0] = fixExt8Code 333 | e.buf[1] = byte(kind) 334 | b = e.buf[:2] 335 | 336 | case 16: 337 | e.buf[0] = fixExt16Code 338 | e.buf[1] = byte(kind) 339 | b = e.buf[:2] 340 | 341 | default: 342 | b = e.encodeNum(extLenEncodings, uint64(len(data))) 343 | b = append(b, byte(kind)) 344 | } 345 | 346 | if _, err := e.w.Write(b); err != nil { 347 | return err 348 | } 349 | 350 | _, err := e.w.Write(data) 351 | return err 352 | } 353 | 354 | // PackNil writes a Nil value to the MessagePack stream. 355 | func (e *Encoder) PackNil() error { 356 | e.buf[0] = nilCode 357 | _, err := e.w.Write(e.buf[:1]) 358 | return err 359 | } 360 | 361 | // PackRaw writes bytes directly to the MessagePack stream. It is the 362 | // application's responsibility to ensure that the bytes are valid. 363 | func (e *Encoder) PackRaw(p []byte) error { 364 | _, err := e.w.Write(p) 365 | return err 366 | } 367 | -------------------------------------------------------------------------------- /nvim/api_deprecated.go: -------------------------------------------------------------------------------- 1 | // Code generated by running "go generate" in github.com/neovim/go-client/nvim. DO NOT EDIT. 2 | 3 | package nvim 4 | 5 | // EmbedOptions specifies options for starting an embedded instance of Nvim. 6 | // 7 | // Deprecated: Use ChildProcessOption instead. 8 | type EmbedOptions struct { 9 | // Logf log function for rpc.WithLogf. 10 | Logf func(string, ...any) 11 | 12 | // Dir specifies the working directory of the command. The working 13 | // directory in the current process is used if Dir is "". 14 | Dir string 15 | 16 | // Path is the path of the command to run. If Path = "", then 17 | // StartEmbeddedNvim searches for "nvim" on $PATH. 18 | Path string 19 | 20 | // Args specifies the command line arguments. Do not include the program 21 | // name (the first argument) or the --embed option. 22 | Args []string 23 | 24 | // Env specifies the environment of the Nvim process. The current process 25 | // environment is used if Env is nil. 26 | Env []string 27 | } 28 | 29 | // NewEmbedded starts an embedded instance of Nvim using the specified options. 30 | // 31 | // The application must call Serve() to handle RPC requests and responses. 32 | // 33 | // Deprecated: Use NewChildProcess instead. 34 | func NewEmbedded(options *EmbedOptions) (*Nvim, error) { 35 | if options == nil { 36 | options = &EmbedOptions{} 37 | } 38 | path := options.Path 39 | if path == "" { 40 | path = "nvim" 41 | } 42 | 43 | return NewChildProcess( 44 | ChildProcessArgs(append([]string{"--embed"}, options.Args...)...), 45 | ChildProcessCommand(path), 46 | ChildProcessEnv(options.Env), 47 | ChildProcessDir(options.Dir), 48 | ChildProcessServe(false)) 49 | } 50 | 51 | // ExecuteLua executes a Lua block. 52 | // 53 | // Deprecated: Use ExecLua instead. 54 | func (v *Nvim) ExecuteLua(code string, result any, args ...any) error { 55 | if args == nil { 56 | args = emptyArgs 57 | } 58 | return v.call("nvim_execute_lua", result, code, args) 59 | } 60 | 61 | // ExecuteLua executes a Lua block. 62 | // 63 | // Deprecated: Use ExecLua instead. 64 | func (b *Batch) ExecuteLua(code string, result any, args ...any) { 65 | if args == nil { 66 | args = emptyArgs 67 | } 68 | b.call("nvim_execute_lua", result, code, args) 69 | } 70 | 71 | // BufferNumber gets a buffer's number. 72 | // 73 | // Deprecated: Use int(buffer) to get the buffer's number as an integer. 74 | // 75 | // See: [nvim_buf_get_number()] 76 | // 77 | // [nvim_buf_get_number()]: https://neovim.io/doc/user/api.html#nvim_buf_get_number() 78 | func (v *Nvim) BufferNumber(buffer Buffer) (number int, err error) { 79 | err = v.call("nvim_buf_get_number", &number, buffer) 80 | return number, err 81 | } 82 | 83 | // BufferNumber gets a buffer's number. 84 | // 85 | // Deprecated: Use int(buffer) to get the buffer's number as an integer. 86 | // 87 | // See: [nvim_buf_get_number()] 88 | // 89 | // [nvim_buf_get_number()]: https://neovim.io/doc/user/api.html#nvim_buf_get_number() 90 | func (b *Batch) BufferNumber(buffer Buffer, number *int) { 91 | b.call("nvim_buf_get_number", number, buffer) 92 | } 93 | 94 | // ClearBufferHighlight clears highlights from a given source group and a range 95 | // of lines. 96 | // 97 | // To clear a source group in the entire buffer, pass in 1 and -1 to startLine 98 | // and endLine respectively. 99 | // 100 | // The lineStart and lineEnd parameters specify the range of lines to clear. 101 | // The end of range is exclusive. Specify -1 to clear to the end of the file. 102 | // 103 | // Deprecated: Use ClearBufferNamespace instead. 104 | // 105 | // See: [nvim_buf_clear_highlight()] 106 | // 107 | // [nvim_buf_clear_highlight()]: https://neovim.io/doc/user/api.html#nvim_buf_clear_highlight() 108 | func (v *Nvim) ClearBufferHighlight(buffer Buffer, srcID int, startLine int, endLine int) error { 109 | return v.call("nvim_buf_clear_highlight", nil, buffer, srcID, startLine, endLine) 110 | } 111 | 112 | // ClearBufferHighlight clears highlights from a given source group and a range 113 | // of lines. 114 | // 115 | // To clear a source group in the entire buffer, pass in 1 and -1 to startLine 116 | // and endLine respectively. 117 | // 118 | // The lineStart and lineEnd parameters specify the range of lines to clear. 119 | // The end of range is exclusive. Specify -1 to clear to the end of the file. 120 | // 121 | // Deprecated: Use ClearBufferNamespace instead. 122 | // 123 | // See: [nvim_buf_clear_highlight()] 124 | // 125 | // [nvim_buf_clear_highlight()]: https://neovim.io/doc/user/api.html#nvim_buf_clear_highlight() 126 | func (b *Batch) ClearBufferHighlight(buffer Buffer, srcID int, startLine int, endLine int) { 127 | b.call("nvim_buf_clear_highlight", nil, buffer, srcID, startLine, endLine) 128 | } 129 | 130 | // SetBufferVirtualText set the virtual text (annotation) for a buffer line. 131 | // 132 | // By default (and currently the only option), the text will be placed after 133 | // the buffer text. 134 | // 135 | // Virtual text will never cause reflow, rather virtual text will be truncated at the end of the screen line. 136 | // The virtual text will begin one cell (|lcs-eol| or space) after the ordinary text. 137 | // 138 | // Namespaces are used to support batch deletion/updating of virtual text. 139 | // To create a namespace, use CreateNamespace. Virtual text is cleared using ClearBufferNamespace. 140 | // 141 | // The same nsID can be used for both virtual text and highlights added by AddBufferHighlight, 142 | // both can then be cleared with a single call to ClearBufferNamespace. 143 | // If the virtual text never will be cleared by an API call, pass "nsID = -1". 144 | // 145 | // As a shorthand, "nsID = 0" can be used to create a new namespace for the 146 | // virtual text, the allocated id is then returned. 147 | // 148 | // The opts arg is reserved for future use. 149 | // 150 | // Deprecated: Use SetBufferExtmark instead. 151 | // 152 | // See: [nvim_buf_set_virtual_text()] 153 | // 154 | // [nvim_buf_set_virtual_text()]: https://neovim.io/doc/user/api.html#nvim_buf_set_virtual_text() 155 | func (v *Nvim) SetBufferVirtualText(buffer Buffer, nsID int, line int, chunks []TextChunk, opts map[string]any) (id int, err error) { 156 | err = v.call("nvim_buf_set_virtual_text", &id, buffer, nsID, line, chunks, opts) 157 | return id, err 158 | } 159 | 160 | // SetBufferVirtualText set the virtual text (annotation) for a buffer line. 161 | // 162 | // By default (and currently the only option), the text will be placed after 163 | // the buffer text. 164 | // 165 | // Virtual text will never cause reflow, rather virtual text will be truncated at the end of the screen line. 166 | // The virtual text will begin one cell (|lcs-eol| or space) after the ordinary text. 167 | // 168 | // Namespaces are used to support batch deletion/updating of virtual text. 169 | // To create a namespace, use CreateNamespace. Virtual text is cleared using ClearBufferNamespace. 170 | // 171 | // The same nsID can be used for both virtual text and highlights added by AddBufferHighlight, 172 | // both can then be cleared with a single call to ClearBufferNamespace. 173 | // If the virtual text never will be cleared by an API call, pass "nsID = -1". 174 | // 175 | // As a shorthand, "nsID = 0" can be used to create a new namespace for the 176 | // virtual text, the allocated id is then returned. 177 | // 178 | // The opts arg is reserved for future use. 179 | // 180 | // Deprecated: Use SetBufferExtmark instead. 181 | // 182 | // See: [nvim_buf_set_virtual_text()] 183 | // 184 | // [nvim_buf_set_virtual_text()]: https://neovim.io/doc/user/api.html#nvim_buf_set_virtual_text() 185 | func (b *Batch) SetBufferVirtualText(buffer Buffer, nsID int, line int, chunks []TextChunk, opts map[string]any, id *int) { 186 | b.call("nvim_buf_set_virtual_text", id, buffer, nsID, line, chunks, opts) 187 | } 188 | 189 | // HLByID gets a highlight definition by name. 190 | // 191 | // hlID is the highlight id as returned by HLIDByName. 192 | // 193 | // rgb is the whether the export RGB colors. 194 | // 195 | // The returned highlight is the highlight definition. 196 | // 197 | // See: [nvim_get_hl_by_id()] 198 | // 199 | // [nvim_get_hl_by_id()]: https://neovim.io/doc/user/api.html#nvim_get_hl_by_id() 200 | func (v *Nvim) HLByID(hlID int, rgb bool) (highlight *HLAttrs, err error) { 201 | var result HLAttrs 202 | err = v.call("nvim_get_hl_by_id", &result, hlID, rgb) 203 | return &result, err 204 | } 205 | 206 | // HLByID gets a highlight definition by name. 207 | // 208 | // hlID is the highlight id as returned by HLIDByName. 209 | // 210 | // rgb is the whether the export RGB colors. 211 | // 212 | // The returned highlight is the highlight definition. 213 | // 214 | // See: [nvim_get_hl_by_id()] 215 | // 216 | // [nvim_get_hl_by_id()]: https://neovim.io/doc/user/api.html#nvim_get_hl_by_id() 217 | func (b *Batch) HLByID(hlID int, rgb bool, highlight *HLAttrs) { 218 | b.call("nvim_get_hl_by_id", highlight, hlID, rgb) 219 | } 220 | 221 | // HLByName gets a highlight definition by id. 222 | // 223 | // name is Highlight group name. 224 | // 225 | // rgb is whether the export RGB colors. 226 | // 227 | // The returned highlight is the highlight definition. 228 | // 229 | // See: [nvim_get_hl_by_name()] 230 | // 231 | // [nvim_get_hl_by_name()]: https://neovim.io/doc/user/api.html#nvim_get_hl_by_name() 232 | func (v *Nvim) HLByName(name string, rgb bool) (highlight *HLAttrs, err error) { 233 | var result HLAttrs 234 | err = v.call("nvim_get_hl_by_name", &result, name, rgb) 235 | return &result, err 236 | } 237 | 238 | // HLByName gets a highlight definition by id. 239 | // 240 | // name is Highlight group name. 241 | // 242 | // rgb is whether the export RGB colors. 243 | // 244 | // The returned highlight is the highlight definition. 245 | // 246 | // See: [nvim_get_hl_by_name()] 247 | // 248 | // [nvim_get_hl_by_name()]: https://neovim.io/doc/user/api.html#nvim_get_hl_by_name() 249 | func (b *Batch) HLByName(name string, rgb bool, highlight *HLAttrs) { 250 | b.call("nvim_get_hl_by_name", highlight, name, rgb) 251 | } 252 | 253 | // CommandOutput executes a single ex command and returns the output. 254 | // 255 | // Deprecated: Use Exec instead. 256 | // 257 | // See: [nvim_command_output()] 258 | // 259 | // [nvim_command_output()]: https://neovim.io/doc/user/api.html#nvim_command_output() 260 | func (v *Nvim) CommandOutput(cmd string) (out string, err error) { 261 | err = v.call("nvim_command_output", &out, cmd) 262 | return out, err 263 | } 264 | 265 | // CommandOutput executes a single ex command and returns the output. 266 | // 267 | // Deprecated: Use Exec instead. 268 | // 269 | // See: [nvim_command_output()] 270 | // 271 | // [nvim_command_output()]: https://neovim.io/doc/user/api.html#nvim_command_output() 272 | func (b *Batch) CommandOutput(cmd string, out *string) { 273 | b.call("nvim_command_output", out, cmd) 274 | } 275 | -------------------------------------------------------------------------------- /msgpack/encode.go: -------------------------------------------------------------------------------- 1 | package msgpack 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | ) 7 | 8 | // Marshaler is the interface implemented by objects that can encode themselves 9 | // to a MessagePack stream. 10 | type Marshaler interface { 11 | MarshalMsgPack(e *Encoder) error 12 | } 13 | 14 | type encodeTypeError struct { 15 | Type reflect.Type 16 | } 17 | 18 | func (e *encodeTypeError) Error() string { 19 | return "msgpack: unsupported type: " + e.Type.String() 20 | } 21 | 22 | func encodeUnsupportedType(e *Encoder, v reflect.Value) { 23 | abort(&encodeTypeError{v.Type()}) 24 | } 25 | 26 | // Encode writes the MessagePack encoding of v to the stream. 27 | // 28 | // Encode traverses the value v recursively. If an encountered value implements 29 | // the Marshaler interface Encode calls its MarshalMsgPack method to write the 30 | // value to the stream. 31 | // 32 | // Otherwise, Encode uses the following type-dependent default encodings: 33 | // 34 | // Go Type MessagePack Type 35 | // bool true or false 36 | // float32, float64 float64 37 | // string string 38 | // []byte binary 39 | // slices, arrays array 40 | // struct, map map 41 | // 42 | // Struct values encode as maps or arrays. If any struct field tag specifies 43 | // the "array" option, then the struct is encoded as an array. Otherwise, the 44 | // struct is encoded as a map. Each exported struct field becomes a member of 45 | // the map unless 46 | // - the field's tag is "-", or 47 | // - the field is empty and its tag specifies the "omitempty" option. 48 | // 49 | // Anonymous struct fields are marshaled as if their inner exported fields 50 | // were fields in the outer struct. 51 | // 52 | // The struct field tag "empty" specifies a default value when decoding and the 53 | // empty value for the "omitempty" option. 54 | // 55 | // Pointer values encode as the value pointed to. A nil pointer encodes as the 56 | // MessagePack nil value. 57 | // 58 | // Interface values encode as the value contained in the interface. A nil 59 | // interface value encodes as the MessagePack nil value. 60 | func (e *Encoder) Encode(v any) (err error) { 61 | if v == nil { 62 | return e.PackNil() 63 | } 64 | defer handleAbort(&err) 65 | 66 | rv := reflect.ValueOf(v) 67 | encoderForType(rv.Type(), nil)(e, rv) 68 | 69 | return nil 70 | } 71 | 72 | type encodeFunc func(e *Encoder, v reflect.Value) 73 | 74 | type encodeBuilder struct { 75 | m map[reflect.Type]encodeFunc 76 | } 77 | 78 | var encodeFuncCache struct { 79 | sync.RWMutex 80 | m map[reflect.Type]encodeFunc 81 | } 82 | 83 | func encoderForType(t reflect.Type, b *encodeBuilder) encodeFunc { 84 | encodeFuncCache.RLock() 85 | f, ok := encodeFuncCache.m[t] 86 | encodeFuncCache.RUnlock() 87 | if ok { 88 | return f 89 | } 90 | 91 | save := false 92 | if b == nil { 93 | b = &encodeBuilder{m: make(map[reflect.Type]encodeFunc)} 94 | save = true 95 | } else if f, ok := b.m[t]; ok { 96 | return f 97 | } 98 | 99 | // Add temporary entry to break recursion. 100 | b.m[t] = func(e *Encoder, v reflect.Value) { 101 | f(e, v) 102 | } 103 | f = b.encoder(t) 104 | b.m[t] = f 105 | 106 | if save { 107 | encodeFuncCache.Lock() 108 | 109 | if encodeFuncCache.m == nil { 110 | encodeFuncCache.m = make(map[reflect.Type]encodeFunc) 111 | } 112 | for t, f := range b.m { 113 | encodeFuncCache.m[t] = f 114 | } 115 | 116 | encodeFuncCache.Unlock() 117 | } 118 | 119 | return f 120 | } 121 | 122 | func (b *encodeBuilder) encoder(t reflect.Type) encodeFunc { 123 | if t.Implements(marshalerType) { 124 | return b.marshalEncoder(t) 125 | } 126 | 127 | var f encodeFunc 128 | switch t.Kind() { 129 | case reflect.Bool: 130 | f = boolEncoder 131 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 132 | f = intEncoder 133 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 134 | f = uintEncoder 135 | case reflect.Float32, reflect.Float64: 136 | f = floatEncoder 137 | case reflect.String: 138 | f = stringEncoder 139 | case reflect.Array: 140 | f = b.arrayEncoder(t) 141 | case reflect.Slice: 142 | f = b.sliceEncoder(t) 143 | case reflect.Map: 144 | f = b.mapEncoder(t) 145 | case reflect.Interface: 146 | f = interfaceEncoder 147 | case reflect.Struct: 148 | f = b.structEncoder(t) 149 | case reflect.Ptr: 150 | f = b.ptrEncoder(t) 151 | default: 152 | f = encodeUnsupportedType 153 | } 154 | 155 | if t.Kind() != reflect.Ptr && reflect.PtrTo(t).Implements(marshalerType) { 156 | f = marshalAddrEncoder{f}.encode 157 | } 158 | 159 | return f 160 | } 161 | 162 | func nilEncoder(e *Encoder, v reflect.Value) { 163 | if err := e.PackNil(); err != nil { 164 | abort(err) 165 | } 166 | } 167 | 168 | func boolEncoder(e *Encoder, v reflect.Value) { 169 | if err := e.PackBool(v.Bool()); err != nil { 170 | abort(err) 171 | } 172 | } 173 | 174 | func intEncoder(e *Encoder, v reflect.Value) { 175 | if err := e.PackInt(v.Int()); err != nil { 176 | abort(err) 177 | } 178 | } 179 | 180 | func uintEncoder(e *Encoder, v reflect.Value) { 181 | if err := e.PackUint(v.Uint()); err != nil { 182 | abort(err) 183 | } 184 | } 185 | 186 | func floatEncoder(e *Encoder, v reflect.Value) { 187 | if err := e.PackFloat(v.Float()); err != nil { 188 | abort(err) 189 | } 190 | } 191 | 192 | func stringEncoder(e *Encoder, v reflect.Value) { 193 | if err := e.PackString(v.String()); err != nil { 194 | abort(err) 195 | } 196 | } 197 | 198 | func byteSliceEncoder(e *Encoder, v reflect.Value) { 199 | if err := e.PackBinary(v.Bytes()); err != nil { 200 | abort(err) 201 | } 202 | } 203 | 204 | func interfaceEncoder(e *Encoder, v reflect.Value) { 205 | if !v.IsValid() || v.IsNil() { 206 | nilEncoder(e, v) 207 | return 208 | } 209 | 210 | v = v.Elem() 211 | encoderForType(v.Type(), nil)(e, v) 212 | } 213 | 214 | type ptrEncoder struct{ elem encodeFunc } 215 | 216 | func (enc ptrEncoder) encode(e *Encoder, v reflect.Value) { 217 | if v.IsNil() { 218 | nilEncoder(e, v) 219 | return 220 | } 221 | 222 | enc.elem(e, v.Elem()) 223 | } 224 | 225 | func (b *encodeBuilder) ptrEncoder(t reflect.Type) encodeFunc { 226 | return ptrEncoder{encoderForType(t.Elem(), b)}.encode 227 | } 228 | 229 | type mapEncoder struct{ key, elem encodeFunc } 230 | 231 | func (enc *mapEncoder) encode(e *Encoder, v reflect.Value) { 232 | if v.IsNil() { 233 | nilEncoder(e, v) 234 | return 235 | } 236 | 237 | if err := e.PackMapLen(int64(v.Len())); err != nil { 238 | abort(err) 239 | } 240 | 241 | for _, k := range v.MapKeys() { 242 | enc.key(e, k) 243 | enc.elem(e, v.MapIndex(k)) 244 | } 245 | } 246 | 247 | func (b *encodeBuilder) mapEncoder(t reflect.Type) encodeFunc { 248 | enc := &mapEncoder{key: encoderForType(t.Key(), b), elem: encoderForType(t.Elem(), b)} 249 | return enc.encode 250 | } 251 | 252 | type sliceArrayEncoder struct{ elem encodeFunc } 253 | 254 | func (enc sliceArrayEncoder) encodeArray(e *Encoder, v reflect.Value) { 255 | if err := e.PackArrayLen(int64(v.Len())); err != nil { 256 | abort(err) 257 | } 258 | 259 | for i := 0; i < v.Len(); i++ { 260 | enc.elem(e, v.Index(i)) 261 | } 262 | } 263 | 264 | func (b *encodeBuilder) arrayEncoder(t reflect.Type) encodeFunc { 265 | return sliceArrayEncoder{encoderForType(t.Elem(), b)}.encodeArray 266 | } 267 | 268 | func (enc sliceArrayEncoder) encodeSlice(e *Encoder, v reflect.Value) { 269 | if v.IsNil() { 270 | nilEncoder(e, v) 271 | return 272 | } 273 | 274 | enc.encodeArray(e, v) 275 | } 276 | 277 | func (b *encodeBuilder) sliceEncoder(t reflect.Type) encodeFunc { 278 | if t.Elem().Kind() == reflect.Uint8 { 279 | return byteSliceEncoder 280 | } 281 | 282 | return sliceArrayEncoder{encoderForType(t.Elem(), b)}.encodeSlice 283 | } 284 | 285 | var marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() 286 | 287 | func marshalPtrEncoder(e *Encoder, v reflect.Value) { 288 | if v.IsNil() { 289 | nilEncoder(e, v) 290 | return 291 | } 292 | 293 | marshalEncoder(e, v) 294 | } 295 | 296 | func marshalEncoder(e *Encoder, v reflect.Value) { 297 | m := v.Interface().(Marshaler) 298 | 299 | if err := m.MarshalMsgPack(e); err != nil { 300 | abort(err) 301 | } 302 | } 303 | 304 | func (b *encodeBuilder) marshalEncoder(t reflect.Type) encodeFunc { 305 | if t.Kind() == reflect.Ptr { 306 | return marshalPtrEncoder 307 | } 308 | 309 | return marshalEncoder 310 | } 311 | 312 | type marshalAddrEncoder struct{ f encodeFunc } 313 | 314 | func (enc marshalAddrEncoder) encode(e *Encoder, v reflect.Value) { 315 | if v.CanAddr() { 316 | marshalEncoder(e, v.Addr()) 317 | return 318 | } 319 | 320 | enc.f(e, v) 321 | } 322 | 323 | type fieldEnc struct { 324 | name string 325 | empty func(reflect.Value) bool 326 | f encodeFunc 327 | index []int 328 | } 329 | 330 | type structEncoder []*fieldEnc 331 | 332 | func (enc structEncoder) encode(e *Encoder, v reflect.Value) { 333 | var n int64 334 | for _, fe := range enc { 335 | fv := fieldByIndex(v, fe.index) 336 | if !fv.IsValid() || (fe.empty != nil && fe.empty(fv)) { 337 | continue 338 | } 339 | n++ 340 | } 341 | 342 | if err := e.PackMapLen(n); err != nil { 343 | abort(err) 344 | } 345 | 346 | for _, fe := range enc { 347 | fv := fieldByIndex(v, fe.index) 348 | if !fv.IsValid() || (fe.empty != nil && fe.empty(fv)) { 349 | continue 350 | } 351 | 352 | if err := e.PackString(fe.name); err != nil { 353 | abort(err) 354 | } 355 | 356 | fe.f(e, fv) 357 | } 358 | } 359 | 360 | func (enc structEncoder) encodeArray(e *Encoder, v reflect.Value) { 361 | if err := e.PackArrayLen(int64(len(enc))); err != nil { 362 | abort(err) 363 | } 364 | 365 | for _, fe := range enc { 366 | fv := fieldByIndex(v, fe.index) 367 | fe.f(e, fv) 368 | } 369 | } 370 | 371 | func (b *encodeBuilder) structEncoder(t reflect.Type) encodeFunc { 372 | fields, array := fieldsForType(t) 373 | enc := make(structEncoder, len(fields)) 374 | 375 | for i, f := range fields { 376 | var empty func(reflect.Value) bool 377 | if f.omitEmpty { 378 | empty = emptyFunc(f) 379 | } 380 | enc[i] = &fieldEnc{ 381 | name: f.name, 382 | empty: empty, 383 | index: f.index, 384 | f: encoderForType(f.typ, b)} 385 | } 386 | 387 | if array { 388 | return enc.encodeArray 389 | } 390 | 391 | return enc.encode 392 | } 393 | 394 | func emptyFunc(f *field) func(reflect.Value) bool { 395 | if f.empty.IsValid() { 396 | return func(v reflect.Value) bool { return v.Interface() == f.empty.Interface() } 397 | } 398 | 399 | switch f.typ.Kind() { 400 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 401 | return lenEmpty 402 | case reflect.Bool: 403 | return boolEmpty 404 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 405 | return intEmpty 406 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 407 | return uintEmpty 408 | case reflect.Float32, reflect.Float64: 409 | return floatEmpty 410 | case reflect.Interface, reflect.Ptr: 411 | return nilEmpty 412 | default: 413 | return nil 414 | } 415 | } 416 | 417 | func lenEmpty(v reflect.Value) bool { return v.Len() == 0 } 418 | func boolEmpty(v reflect.Value) bool { return !v.Bool() } 419 | func intEmpty(v reflect.Value) bool { return v.Int() == 0 } 420 | func uintEmpty(v reflect.Value) bool { return v.Uint() == 0 } 421 | func floatEmpty(v reflect.Value) bool { return v.Float() == 0 } 422 | func nilEmpty(v reflect.Value) bool { return v.IsNil() } 423 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /msgpack/unpack.go: -------------------------------------------------------------------------------- 1 | package msgpack 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "math" 9 | ) 10 | 11 | // Type represents the type of value in the MessagePack stream. 12 | type Type int 13 | 14 | // list of MessagePack types. 15 | const ( 16 | Invalid Type = iota 17 | Nil 18 | Bool 19 | Int 20 | Uint 21 | Float 22 | ArrayLen 23 | MapLen 24 | String 25 | Binary 26 | Extension 27 | ) 28 | 29 | var typeNames = [...]string{ 30 | Invalid: "Invalid", 31 | Nil: "Nil", 32 | Bool: "Bool", 33 | Int: "Int", 34 | Uint: "Uint", 35 | Float: "Float", 36 | ArrayLen: "ArrayLen", 37 | MapLen: "MapLen", 38 | String: "String", 39 | Binary: "Binary", 40 | Extension: "Extension", 41 | } 42 | 43 | // String returns a string representation of the Type. 44 | func (t Type) String() string { 45 | var n string 46 | 47 | if 0 <= t && t < Type(len(typeNames)) { 48 | n = typeNames[t] 49 | } 50 | if n == "" { 51 | n = "unknown" 52 | } 53 | 54 | return n 55 | } 56 | 57 | // ErrDataSizeTooLarge is the data size too large error. 58 | var ErrDataSizeTooLarge = errors.New("msgpack: data size too large") 59 | 60 | // Decoder reads MessagePack objects from an io.Reader. 61 | type Decoder struct { 62 | extensions ExtensionMap 63 | err error 64 | r *bufio.Reader 65 | n uint64 66 | p []byte 67 | t Type 68 | peek bool 69 | } 70 | 71 | const bufioReaderSize = 4096 72 | 73 | // NewDecoder allocates and initializes a new decoder. 74 | func NewDecoder(r io.Reader) *Decoder { 75 | return &Decoder{ 76 | r: bufio.NewReaderSize(r, bufioReaderSize), 77 | } 78 | } 79 | 80 | // ExtensionMap specifies functions for converting MessagePack extensions to Go 81 | // values. 82 | // 83 | // The key is the MessagePack extension type. 84 | // The value is a function that converts the extension data to a Go value. 85 | type ExtensionMap map[int]func([]byte) (any, error) 86 | 87 | // SetExtensions specifies functions for converting MessagePack extensions to Go 88 | // values. 89 | func (d *Decoder) SetExtensions(extensions ExtensionMap) { 90 | d.extensions = extensions 91 | } 92 | 93 | // Type returns the type of the current value in the stream. 94 | func (d *Decoder) Type() Type { 95 | return d.t 96 | } 97 | 98 | // Extension returns the type of the current Extension value. 99 | func (d *Decoder) Extension() int { 100 | return int(d.n) 101 | } 102 | 103 | // Bytes returns the current String, Binary or Extension value as a slice of 104 | // bytes. 105 | func (d *Decoder) Bytes() []byte { 106 | if d.peek { 107 | p := make([]byte, len(d.p)) 108 | copy(p, d.p) 109 | d.p = p 110 | } 111 | 112 | return d.p 113 | } 114 | 115 | // BytesNoCopy returns the current String, Binary or Extension value as a slice 116 | // of bytes. The underlying array may point to data that will be overwritten by 117 | // a subsequent call to Unpack. 118 | func (d *Decoder) BytesNoCopy() []byte { 119 | return d.p 120 | } 121 | 122 | // String returns the current String, Binary or Extension value as a string. 123 | func (d *Decoder) String() string { 124 | return string(d.p) 125 | } 126 | 127 | // Int returns the current Int value. 128 | func (d *Decoder) Int() int64 { 129 | return int64(d.n) 130 | } 131 | 132 | // Uint returns the current Uint value. 133 | func (d *Decoder) Uint() uint64 { 134 | return d.n 135 | } 136 | 137 | // Len returns the current ArrayLen or MapLen value. 138 | func (d *Decoder) Len() int { 139 | return int(d.n) 140 | } 141 | 142 | // Bool returns the current Bool value. 143 | func (d *Decoder) Bool() bool { 144 | if d.n != 0 { 145 | return true 146 | } 147 | 148 | return false 149 | } 150 | 151 | // Float returns the current Float value. 152 | func (d *Decoder) Float() float64 { 153 | return math.Float64frombits(d.n) 154 | } 155 | 156 | // Unpack reads the next value from the MessagePack stream. Call Type to get the 157 | // type of the current value. Call Bool, Uint, Int, Float, Bytes or Extension 158 | // to get the value. 159 | func (d *Decoder) Unpack() error { 160 | if d.err != nil { 161 | return d.err 162 | } 163 | 164 | code, err := d.r.ReadByte() 165 | if err != nil { 166 | // Don't call d.fatal here because we don't want io.EOF converted to 167 | // io.ErrUnexpectedEOF 168 | d.err = err 169 | return err 170 | } 171 | 172 | f := formats[code] 173 | d.t = f.t 174 | 175 | d.n, err = f.n(d, code) 176 | if err != nil { 177 | return d.fatal(err) 178 | } 179 | 180 | if !f.more { 181 | d.p = nil 182 | return nil 183 | } 184 | 185 | nn := int(d.n) 186 | if nn < 0 { 187 | return d.fatal(ErrDataSizeTooLarge) 188 | } 189 | 190 | if f.t == Extension { 191 | var b byte 192 | b, err = d.r.ReadByte() 193 | if err != nil { 194 | return d.fatal(err) 195 | } 196 | d.n = uint64(b) 197 | } 198 | 199 | if nn <= bufioReaderSize { 200 | d.peek = true 201 | d.p, err = d.r.Peek(nn) 202 | if err != nil { 203 | return d.fatal(err) 204 | } 205 | d.r.Discard(nn) 206 | } else { 207 | d.peek = false 208 | d.p = make([]byte, nn) 209 | _, err := io.ReadFull(d.r, d.p) 210 | if err != nil { 211 | return d.fatal(err) 212 | } 213 | } 214 | 215 | return nil 216 | } 217 | 218 | // Skip skips over any nested values in the stream. 219 | func (d *Decoder) Skip() error { 220 | n := d.skipCount() 221 | 222 | for n > 0 { 223 | n-- 224 | if err := d.Unpack(); err != nil { 225 | return err 226 | } 227 | n += d.skipCount() 228 | } 229 | 230 | return nil 231 | } 232 | 233 | func (d *Decoder) skipCount() int { 234 | switch d.Type() { 235 | case ArrayLen: 236 | return d.Len() 237 | case MapLen: 238 | return 2 * d.Len() 239 | default: 240 | return 0 241 | } 242 | } 243 | 244 | var formats = [256]*struct { 245 | t Type 246 | n func(d *Decoder, code byte) (uint64, error) 247 | more bool 248 | }{ 249 | fixIntCodeMin: { 250 | t: Int, 251 | n: func(d *Decoder, code byte) (uint64, error) { return uint64(code), nil }, 252 | }, 253 | fixMapCodeMin: { 254 | t: MapLen, 255 | n: func(d *Decoder, code byte) (uint64, error) { return uint64(code) - uint64(fixMapCodeMin), nil }, 256 | }, 257 | fixArrayCodeMin: { 258 | t: ArrayLen, 259 | n: func(d *Decoder, code byte) (uint64, error) { return uint64(code) - uint64(fixArrayCodeMin), nil }, 260 | }, 261 | fixStringCodeMin: { 262 | t: String, 263 | n: func(d *Decoder, code byte) (uint64, error) { return uint64(code) - uint64(fixStringCodeMin), nil }, 264 | more: true, 265 | }, 266 | negFixIntCodeMin: { 267 | t: Int, 268 | n: func(d *Decoder, code byte) (uint64, error) { return uint64(int64(int8(code))), nil }, 269 | }, 270 | nilCode: { 271 | t: Nil, 272 | n: func(d *Decoder, code byte) (uint64, error) { return 0, nil }, 273 | }, 274 | falseCode: { 275 | t: Bool, 276 | n: func(d *Decoder, code byte) (uint64, error) { return 0, nil }, 277 | }, 278 | trueCode: { 279 | t: Bool, 280 | n: func(d *Decoder, code byte) (uint64, error) { return 1, nil }, 281 | }, 282 | float32Code: { 283 | t: Float, 284 | n: func(d *Decoder, code byte) (uint64, error) { 285 | n, err := d.read4(code) 286 | return math.Float64bits(float64(math.Float32frombits(uint32(n)))), err 287 | }, 288 | }, 289 | float64Code: { 290 | t: Float, 291 | n: (*Decoder).read8, 292 | }, 293 | uint8Code: { 294 | t: Uint, 295 | n: (*Decoder).read1, 296 | }, 297 | uint16Code: { 298 | t: Uint, 299 | n: (*Decoder).read2, 300 | }, 301 | uint32Code: { 302 | t: Uint, 303 | n: (*Decoder).read4, 304 | }, 305 | uint64Code: { 306 | t: Uint, 307 | n: (*Decoder).read8, 308 | }, 309 | int8Code: { 310 | t: Int, 311 | n: func(d *Decoder, code byte) (uint64, error) { 312 | n, err := d.read1(code) 313 | return uint64(int64(int8(n))), err 314 | }, 315 | }, 316 | int16Code: { 317 | t: Int, 318 | n: func(d *Decoder, code byte) (uint64, error) { 319 | n, err := d.read2(code) 320 | return uint64(int64(int16(n))), err 321 | }, 322 | }, 323 | int32Code: { 324 | t: Int, 325 | n: func(d *Decoder, code byte) (uint64, error) { 326 | n, err := d.read4(code) 327 | return uint64(int64(int32(n))), err 328 | }, 329 | }, 330 | int64Code: { 331 | t: Int, 332 | n: (*Decoder).read8, 333 | }, 334 | string8Code: { 335 | t: String, 336 | n: (*Decoder).read1, 337 | more: true, 338 | }, 339 | string16Code: { 340 | t: String, 341 | n: (*Decoder).read2, 342 | more: true, 343 | }, 344 | string32Code: { 345 | t: String, 346 | n: (*Decoder).read4, 347 | more: true, 348 | }, 349 | binary8Code: { 350 | t: Binary, 351 | n: (*Decoder).read1, 352 | more: true, 353 | }, 354 | binary16Code: { 355 | t: Binary, 356 | n: (*Decoder).read2, 357 | more: true, 358 | }, 359 | binary32Code: { 360 | t: Binary, 361 | n: (*Decoder).read4, 362 | more: true, 363 | }, 364 | array16Code: { 365 | t: ArrayLen, 366 | n: (*Decoder).read2, 367 | }, 368 | array32Code: { 369 | t: ArrayLen, 370 | n: (*Decoder).read4, 371 | }, 372 | map16Code: { 373 | t: MapLen, 374 | n: (*Decoder).read2, 375 | }, 376 | map32Code: { 377 | t: MapLen, 378 | n: (*Decoder).read4, 379 | }, 380 | fixExt1Code: { 381 | t: Extension, 382 | n: func(d *Decoder, code byte) (uint64, error) { return 1, nil }, 383 | more: true, 384 | }, 385 | fixExt2Code: { 386 | t: Extension, 387 | n: func(d *Decoder, code byte) (uint64, error) { return 2, nil }, 388 | more: true, 389 | }, 390 | fixExt4Code: { 391 | t: Extension, 392 | n: func(d *Decoder, code byte) (uint64, error) { return 4, nil }, 393 | more: true, 394 | }, 395 | fixExt8Code: { 396 | t: Extension, 397 | n: func(d *Decoder, code byte) (uint64, error) { return 8, nil }, 398 | more: true, 399 | }, 400 | fixExt16Code: { 401 | t: Extension, 402 | n: func(d *Decoder, code byte) (uint64, error) { return 16, nil }, 403 | more: true, 404 | }, 405 | ext8Code: { 406 | t: Extension, 407 | n: (*Decoder).read1, 408 | more: true, 409 | }, 410 | ext16Code: { 411 | t: Extension, 412 | n: (*Decoder).read2, 413 | more: true, 414 | }, 415 | ext32Code: { 416 | t: Extension, 417 | n: (*Decoder).read4, 418 | more: true, 419 | }, 420 | unusedCode: { 421 | t: Invalid, 422 | n: func(d *Decoder, code byte) (uint64, error) { 423 | return 0, fmt.Errorf("msgpack: unknown format code %x", code) 424 | }, 425 | }, 426 | } 427 | 428 | func init() { 429 | for i := fixIntCodeMin + 1; i <= fixIntCodeMax; i++ { 430 | formats[i] = formats[fixIntCodeMin] 431 | } 432 | 433 | for i := fixMapCodeMin + 1; i <= fixMapCodeMax; i++ { 434 | formats[i] = formats[fixMapCodeMin] 435 | } 436 | 437 | for i := fixArrayCodeMin + 1; i <= fixArrayCodeMax; i++ { 438 | formats[i] = formats[fixArrayCodeMin] 439 | } 440 | 441 | for i := fixStringCodeMin + 1; i <= fixStringCodeMax; i++ { 442 | formats[i] = formats[fixStringCodeMin] 443 | } 444 | 445 | for i := negFixIntCodeMin + 1; i <= negFixIntCodeMax; i++ { 446 | formats[i] = formats[negFixIntCodeMin] 447 | } 448 | } 449 | 450 | func (d *Decoder) fatal(err error) error { 451 | if err == io.EOF { 452 | err = io.ErrUnexpectedEOF 453 | } 454 | 455 | d.t = Invalid 456 | d.err = err 457 | return err 458 | } 459 | 460 | func (d *Decoder) read1(format byte) (uint64, error) { 461 | b, err := d.r.ReadByte() 462 | 463 | return uint64(b), err 464 | } 465 | 466 | func (d *Decoder) read2(format byte) (uint64, error) { 467 | p, err := d.r.Peek(2) 468 | if err != nil { 469 | return 0, err 470 | } 471 | d.r.Discard(2) 472 | 473 | return uint64(p[1]) | uint64(p[0])<<8, nil 474 | } 475 | 476 | func (d *Decoder) read4(format byte) (uint64, error) { 477 | p, err := d.r.Peek(4) 478 | if err != nil { 479 | return 0, err 480 | } 481 | d.r.Discard(4) 482 | 483 | return uint64(p[3]) | uint64(p[2])<<8 | uint64(p[1])<<16 | uint64(p[0])<<24, nil 484 | } 485 | 486 | func (d *Decoder) read8(format byte) (uint64, error) { 487 | p, err := d.r.Peek(8) 488 | if err != nil { 489 | return 0, err 490 | } 491 | d.r.Discard(8) 492 | 493 | return uint64(p[7]) | uint64(p[6])<<8 | uint64(p[5])<<16 | uint64(p[4])<<24 | 494 | uint64(p[3])<<32 | uint64(p[2])<<40 | uint64(p[1])<<48 | uint64(p[0])<<56, nil 495 | } 496 | -------------------------------------------------------------------------------- /nvim/plugin/plugin_test.go: -------------------------------------------------------------------------------- 1 | package plugin_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/neovim/go-client/nvim" 10 | "github.com/neovim/go-client/nvim/nvimtest" 11 | "github.com/neovim/go-client/nvim/plugin" 12 | ) 13 | 14 | func TestRegister(t *testing.T) { 15 | t.Parallel() 16 | 17 | type testEval struct { 18 | BaseDir string `eval:"fnamemodify(getcwd(), ':t')"` 19 | } 20 | 21 | p := plugin.New(nvimtest.NewChildProcess(t)) 22 | 23 | // SimpleHandler 24 | p.Handle("hello", 25 | func(s string) (string, error) { 26 | return "Hello, " + s, nil 27 | }, 28 | ) 29 | 30 | // FunctionHandler 31 | p.HandleFunction( 32 | &plugin.FunctionOptions{Name: "Hello"}, 33 | func(args []string) (string, error) { 34 | return "Hello, " + strings.Join(args, " "), nil 35 | }, 36 | ) 37 | 38 | // FunctionEvalHandler 39 | p.HandleFunction( 40 | &plugin.FunctionOptions{Name: "TestEval", Eval: "*"}, 41 | func(_ []string, eval *testEval) (string, error) { 42 | return eval.BaseDir, nil 43 | }, 44 | ) 45 | 46 | // CommandHandler 47 | p.HandleCommand( 48 | &plugin.CommandOptions{ 49 | Name: "Hello", 50 | NArgs: "*", 51 | Range: "%", 52 | Addr: "buffers", 53 | Complete: "buffer", 54 | Bang: true, 55 | Register: true, 56 | Bar: true, 57 | }, 58 | func(n *nvim.Nvim, args []string) error { 59 | chunks := []nvim.TextChunk{ 60 | {Text: `Hello`}, 61 | } 62 | for _, arg := range args { 63 | chunks = append(chunks, nvim.TextChunk{Text: arg}) 64 | } 65 | 66 | return n.Echo(chunks, true, make(map[string]any)) 67 | }, 68 | ) 69 | 70 | // CommandRangeDotHandler 71 | p.HandleCommand( 72 | &plugin.CommandOptions{ 73 | Name: "HelloRangeDot", 74 | Range: ".", 75 | }, 76 | func(n *nvim.Nvim) error { 77 | chunks := []nvim.TextChunk{ 78 | {Text: `Hello`}, 79 | } 80 | 81 | return n.Echo(chunks, true, make(map[string]any)) 82 | }, 83 | ) 84 | 85 | // CommandCountHandler 86 | p.HandleCommand( 87 | &plugin.CommandOptions{ 88 | Name: "HelloCount", 89 | Count: "0", 90 | }, 91 | func(n *nvim.Nvim) error { 92 | chunks := []nvim.TextChunk{ 93 | {Text: `Hello`}, 94 | } 95 | 96 | return n.Echo(chunks, true, make(map[string]any)) 97 | }, 98 | ) 99 | 100 | // CommandEvalHandler 101 | p.HandleCommand( 102 | &plugin.CommandOptions{ 103 | Name: "HelloEval", 104 | Eval: "*", 105 | }, 106 | func(n *nvim.Nvim, eval *testEval) error { 107 | chunks := []nvim.TextChunk{ 108 | { 109 | Text: eval.BaseDir, 110 | }, 111 | } 112 | 113 | return n.Echo(chunks, true, make(map[string]any)) 114 | }, 115 | ) 116 | 117 | // AutocmdHandler 118 | p.HandleAutocmd( 119 | &plugin.AutocmdOptions{ 120 | Event: "User", 121 | Group: "Test", 122 | Pattern: "Test", 123 | Nested: true, 124 | Once: false, 125 | }, 126 | func(n *nvim.Nvim, args []string) error { 127 | chunks := []nvim.TextChunk{ 128 | { 129 | Text: "Hello", 130 | }, 131 | { 132 | Text: "Autocmd", 133 | }, 134 | } 135 | 136 | return n.Echo(chunks, true, make(map[string]any)) 137 | }, 138 | ) 139 | 140 | // AutocmdEvalHandler 141 | p.HandleAutocmd( 142 | &plugin.AutocmdOptions{ 143 | Event: "User", 144 | Group: "Eval", 145 | Pattern: "Eval", 146 | Eval: "*", 147 | }, 148 | func(n *nvim.Nvim, eval *testEval) error { 149 | chunks := []nvim.TextChunk{ 150 | { 151 | Text: eval.BaseDir, 152 | }, 153 | } 154 | 155 | return n.Echo(chunks, true, make(map[string]any)) 156 | }, 157 | ) 158 | 159 | // AutocmdOnceHandler 160 | p.HandleAutocmd( 161 | &plugin.AutocmdOptions{ 162 | Event: "User", 163 | Group: "TestOnce", 164 | Pattern: "Once", 165 | Nested: true, 166 | Once: true, 167 | }, 168 | func(n *nvim.Nvim, args []string) error { 169 | chunks := []nvim.TextChunk{ 170 | { 171 | Text: "Hello", 172 | }, 173 | { 174 | Text: "Autocmd", 175 | }, 176 | { 177 | Text: "Once", 178 | }, 179 | } 180 | 181 | return n.Echo(chunks, true, make(map[string]any)) 182 | }, 183 | ) 184 | 185 | if err := p.RegisterForTests(); err != nil { 186 | t.Fatalf("register handlers for test: %v", err) 187 | } 188 | 189 | t.Run("SimpleHandler", func(t *testing.T) { 190 | opts := map[string]any{ 191 | "output": true, 192 | } 193 | result, err := p.Nvim.Exec(`:echo Hello('John', 'Doe')`, opts) 194 | if err != nil { 195 | t.Fatalf("exec 'echo Hello' function: %v", err) 196 | } 197 | 198 | expected := `Hello, John Doe` 199 | want := map[string]any{ 200 | "output": expected, 201 | } 202 | if !reflect.DeepEqual(result, want) { 203 | t.Fatalf("Hello returned %v, want %v", result, want) 204 | } 205 | }) 206 | 207 | t.Run("FunctionHandler", func(t *testing.T) { 208 | cid := p.Nvim.ChannelID() 209 | var result string 210 | if err := p.Nvim.Call(`rpcrequest`, &result, cid, `hello`, `world`); err != nil { 211 | t.Fatalf("call rpcrequest(%v, %v, %v, %v): %v", &result, cid, "hello", "world", err) 212 | } 213 | 214 | expected2 := `Hello, world` 215 | if result != expected2 { 216 | t.Fatalf("hello returned %q, want %q", result, expected2) 217 | } 218 | }) 219 | 220 | t.Run("FunctionEvalHandler", func(t *testing.T) { 221 | var result string 222 | if err := p.Nvim.Eval(`TestEval()`, &result); err != nil { 223 | t.Fatalf("eval 'TestEval()' function: %v", err) 224 | } 225 | 226 | expected3 := `plugin` 227 | if result != expected3 { 228 | t.Fatalf("EvalTest returned %q, want %q", result, expected3) 229 | } 230 | }) 231 | 232 | t.Run("CommandHandler", func(t *testing.T) { 233 | opts := map[string]any{ 234 | "output": true, 235 | } 236 | result, err := p.Nvim.Exec(`Hello World`, opts) 237 | if err != nil { 238 | t.Fatalf("exec 'Hello' command: %v", err) 239 | } 240 | 241 | expected := `Helloorld` 242 | want := map[string]any{ 243 | "output": expected, 244 | } 245 | if !reflect.DeepEqual(result, want) { 246 | t.Fatalf("Hello returned %v, want %v", result, want) 247 | } 248 | }) 249 | 250 | t.Run("CommandRangeDotHandler", func(t *testing.T) { 251 | opts := map[string]any{ 252 | "output": true, 253 | } 254 | result, err := p.Nvim.Exec(`HelloRangeDot`, opts) 255 | if err != nil { 256 | t.Fatalf("exec 'Hello' command: %v", err) 257 | } 258 | 259 | expected := `Hello` 260 | want := map[string]any{ 261 | "output": expected, 262 | } 263 | if !reflect.DeepEqual(result, want) { 264 | t.Fatalf("Hello returned %v, want %v", result, want) 265 | } 266 | }) 267 | 268 | t.Run("CommandCountHandler", func(t *testing.T) { 269 | opts := map[string]any{ 270 | "output": true, 271 | } 272 | result, err := p.Nvim.Exec(`HelloCount`, opts) 273 | if err != nil { 274 | t.Fatalf("exec 'Hello' command: %v", err) 275 | } 276 | 277 | expected := `Hello` 278 | want := map[string]any{ 279 | "output": expected, 280 | } 281 | if !reflect.DeepEqual(result, want) { 282 | t.Fatalf("Hello returned %v, want %v", result, want) 283 | } 284 | }) 285 | 286 | t.Run("CommandEvalHandler", func(t *testing.T) { 287 | opts := map[string]any{ 288 | "output": true, 289 | } 290 | result, err := p.Nvim.Exec(`HelloEval`, opts) 291 | if err != nil { 292 | t.Fatalf("exec 'Hello' command: %v", err) 293 | } 294 | 295 | expected := `plugin` 296 | want := map[string]any{ 297 | "output": expected, 298 | } 299 | if !reflect.DeepEqual(result, want) { 300 | t.Fatalf("Hello returned %v, want %v", result, want) 301 | } 302 | }) 303 | 304 | t.Run("AutocmdHandler", func(t *testing.T) { 305 | opts := map[string]any{ 306 | "output": true, 307 | } 308 | result, err := p.Nvim.Exec(`doautocmd User Test`, opts) 309 | if err != nil { 310 | t.Fatalf("exec 'doautocmd User Test' command: %v", err) 311 | } 312 | 313 | expected := `HelloAutocmd` 314 | want := map[string]any{ 315 | "output": expected, 316 | } 317 | if !reflect.DeepEqual(result, want) { 318 | t.Fatalf("'doautocmd User Test' returned %v, want %v", result, want) 319 | } 320 | 321 | opts2 := map[string]any{ 322 | "output": true, 323 | } 324 | result2, err := p.Nvim.Exec(`doautocmd User Test`, opts2) 325 | if err != nil { 326 | t.Fatalf("exec 'doautocmd User Test' command: %v", err) 327 | } 328 | if !reflect.DeepEqual(result2, want) { 329 | t.Fatalf("'doautocmd User Test' returned %v, want %v", result, want) 330 | } 331 | }) 332 | 333 | t.Run("AutocmdEvalHandler", func(t *testing.T) { 334 | opts := map[string]any{ 335 | "output": true, 336 | } 337 | result, err := p.Nvim.Exec(`doautocmd User Eval`, opts) 338 | if err != nil { 339 | t.Fatalf("exec 'doautocmd User Eval' command: %v", err) 340 | } 341 | 342 | expected := `plugin` 343 | want := map[string]any{ 344 | "output": expected, 345 | } 346 | if !reflect.DeepEqual(result, want) { 347 | t.Fatalf("'doautocmd User Eval' returned %v, want %v", result, want) 348 | } 349 | }) 350 | 351 | t.Run("AutocmdOnceHandler", func(t *testing.T) { 352 | opts := map[string]any{ 353 | "output": true, 354 | } 355 | result, err := p.Nvim.Exec(`doautocmd User Once`, opts) 356 | if err != nil { 357 | t.Fatalf("exec 'doautocmd User Once' command: %v", err) 358 | } 359 | 360 | expected := `HelloAutocmdOnce` 361 | want := map[string]any{ 362 | "output": expected, 363 | } 364 | if !reflect.DeepEqual(result, want) { 365 | t.Fatalf("'doautocmd User Once' returned %v, want %v", result, want) 366 | } 367 | 368 | opts2 := map[string]any{ 369 | "output": true, 370 | } 371 | result2, err := p.Nvim.Exec(`doautocmd User Once`, opts2) 372 | if err != nil { 373 | t.Fatalf("exec 'doautocmd User Once' command: %v", err) 374 | } 375 | 376 | want2 := map[string]any{ 377 | "output": "", 378 | } 379 | if !reflect.DeepEqual(result2, want2) { 380 | t.Fatalf("'doautocmd User Once' returned %v, want %v", result2, want2) 381 | } 382 | }) 383 | } 384 | 385 | func TestSubscribe(t *testing.T) { 386 | t.Parallel() 387 | 388 | p := plugin.New(nvimtest.NewChildProcess(t)) 389 | 390 | const event1 = "event1" 391 | eventFn1 := func(t *testing.T, v *nvim.Nvim) error { 392 | return v.RegisterHandler(event1, func(event ...any) { 393 | if event[0] != int64(1) { 394 | t.Fatalf("expected event[0] is 1 but got %d", event[0]) 395 | } 396 | if event[1] != int64(2) { 397 | t.Fatalf("expected event[1] is 2 but got %d", event[1]) 398 | } 399 | if event[2] != int64(3) { 400 | t.Fatalf("expected event[2] is 3 but got %d", event[2]) 401 | } 402 | }) 403 | } 404 | p.Handle(event1, func() error { return eventFn1(t, p.Nvim) }) 405 | 406 | const event2 = "event2" 407 | eventFn2 := func(t *testing.T, v *nvim.Nvim) error { 408 | return v.RegisterHandler(event1, func(event ...any) { 409 | if event[0] != int64(4) { 410 | t.Fatalf("expected event[0] is 4 but got %d", event[0]) 411 | } 412 | if event[1] != int64(5) { 413 | t.Fatalf("expected event[1] is 5 but got %d", event[1]) 414 | } 415 | if event[2] != int64(6) { 416 | t.Fatalf("expected event[2] is 6 but got %d", event[2]) 417 | } 418 | }) 419 | } 420 | p.Handle(event2, func() error { return eventFn2(t, p.Nvim) }) 421 | 422 | if err := p.RegisterForTests(); err != nil { 423 | t.Fatalf("register for test: %v", err) 424 | } 425 | 426 | if err := p.Nvim.Subscribe(event1); err != nil { 427 | t.Fatalf("subscribe(%v): %v", event1, err) 428 | } 429 | 430 | b := p.Nvim.NewBatch() 431 | b.Subscribe(event2) 432 | if err := b.Execute(); err != nil { 433 | t.Fatalf("batch execute: %v", err) 434 | } 435 | 436 | // warm-up 437 | var result int 438 | if err := p.Nvim.Eval(fmt.Sprintf(`rpcnotify(0, %q)`, event1), &result); err != nil { 439 | t.Fatalf("eval rpcnotify for warm-up of event1: %v", err) 440 | } 441 | if result != 1 { 442 | t.Fatalf("expect 1 but got %d", result) 443 | } 444 | 445 | var result2 int 446 | if err := p.Nvim.Eval(fmt.Sprintf(`rpcnotify(0, %q, 1, 2, 3)`, event1), &result2); err != nil { 447 | t.Fatalf("eval rpcnotify for event1: %v", err) 448 | } 449 | if result2 != 1 { 450 | t.Fatalf("expect 1 but got %d", result2) 451 | } 452 | 453 | var result3 int 454 | if err := p.Nvim.Eval(fmt.Sprintf(`rpcnotify(0, %q, 4, 5, 6)`, event2), &result3); err != nil { 455 | t.Fatalf("eval rpcnotify for event2: %v", err) 456 | } 457 | if result3 != 1 { 458 | t.Fatalf("expect 1 but got %d", result3) 459 | } 460 | 461 | if err := p.Nvim.Unsubscribe(event1); err != nil { 462 | t.Fatalf("unsubscribe event1: %v", err) 463 | } 464 | 465 | b.Unsubscribe(event2) 466 | if err := b.Execute(); err != nil { 467 | t.Fatalf("unsubscribe event2: %v", err) 468 | } 469 | 470 | if err := p.Nvim.Eval(fmt.Sprintf(`rpcnotify(0, %q, 7, 8, 9)`, event1), nil); err != nil { 471 | t.Fatalf("ensure rpcnotify to event1 is no-op: %v", err) 472 | } 473 | 474 | if err := p.Nvim.Eval(fmt.Sprintf(`rpcnotify(0, %q, 10, 11, 12)`, event2), nil); err != nil { 475 | t.Fatalf("ensure rpcnotify to event2 is no-op: %v", err) 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /nvim/plugin/plugin.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | "sort" 8 | "strings" 9 | 10 | "github.com/neovim/go-client/nvim" 11 | ) 12 | 13 | // Plugin represents a remote plugin. 14 | type Plugin struct { 15 | Nvim *nvim.Nvim 16 | pluginSpecs []*pluginSpec 17 | 18 | // Event/pattern counters used to generate unique paths for autocmds. 19 | eventPathCounts map[string]int 20 | } 21 | 22 | // New returns an intialized plugin. 23 | func New(v *nvim.Nvim) *Plugin { 24 | p := &Plugin{ 25 | Nvim: v, 26 | eventPathCounts: make(map[string]int), 27 | } 28 | 29 | // Disable support for "specs" method until path mechanism for supporting 30 | // binary executables with Nvim is worked out. 31 | // err := v.RegisterHandler("specs", func(path string) ([]*pluginSpec, error) { 32 | // return p.pluginSpecs, nil 33 | // }) 34 | 35 | return p 36 | } 37 | 38 | type pluginSpec struct { 39 | sm string 40 | Type string `msgpack:"type"` 41 | Name string `msgpack:"name"` 42 | Sync bool `msgpack:"sync"` 43 | Opts map[string]string `msgpack:"opts"` 44 | } 45 | 46 | func (spec *pluginSpec) path() string { 47 | if i := strings.Index(spec.sm, ":"); i > 0 { 48 | return spec.sm[:i] 49 | } 50 | 51 | return "" 52 | } 53 | 54 | func isSync(f any) bool { 55 | t := reflect.TypeOf(f) 56 | 57 | return t.Kind() == reflect.Func && t.NumOut() > 0 58 | } 59 | 60 | func (p *Plugin) handle(fn any, spec *pluginSpec) { 61 | p.pluginSpecs = append(p.pluginSpecs, spec) 62 | if p.Nvim == nil { 63 | return 64 | } 65 | 66 | if err := p.Nvim.RegisterHandler(spec.sm, fn); err != nil { 67 | panic(err) 68 | } 69 | } 70 | 71 | // Handle registers fn as a MessagePack RPC handler for the specified method 72 | // name. The function signature for fn is one of 73 | // 74 | // func([v *nvim.Nvim,] {args}) ({resultType}, error) 75 | // func([v *nvim.Nvim,] {args}) error 76 | // func([v *nvim.Nvim,] {args}) 77 | // 78 | // where {args} is zero or more arguments and {resultType} is the type of a 79 | // return value. Call the handler from Nvim using the rpcnotify and rpcrequest 80 | // functions: 81 | // 82 | // :help rpcrequest() 83 | // :help rpcnotify() 84 | func (p *Plugin) Handle(method string, fn any) { 85 | if p.Nvim == nil { 86 | return 87 | } 88 | 89 | if err := p.Nvim.RegisterHandler(method, fn); err != nil { 90 | panic(err) 91 | } 92 | } 93 | 94 | // FunctionOptions specifies function options. 95 | type FunctionOptions struct { 96 | // Name is the name of the function in Nvim. The name must be made of 97 | // alphanumeric characters and '_', and must start with a capital letter. 98 | Name string 99 | 100 | // Eval is an expression evaluated in Nvim. The result is passed to the 101 | // handler function. 102 | Eval string 103 | } 104 | 105 | // HandleFunction registers fn as a handler for a Nvim function. The function 106 | // signature for fn is one of 107 | // 108 | // func([v *nvim.Nvim,] args {arrayType} [, eval {evalType}]) ({resultType}, error) 109 | // func([v *nvim.Nvim,] args {arrayType} [, eval {evalType}]) error 110 | // 111 | // where {arrayType} is a type that can be unmarshaled from a MessagePack 112 | // array, {evalType} is a type compatible with the Eval option expression and 113 | // {resultType} is the type of function result. 114 | // 115 | // If options.Eval == "*", then HandleFunction constructs the expression to 116 | // evaluate in Nvim from the type of fn's last argument. The last argument is 117 | // assumed to be a pointer to a struct type with 'eval' field tags set to the 118 | // expression to evaluate for each field. Nested structs are supported. The 119 | // expression for the function 120 | // 121 | // func example(eval *struct{ 122 | // GOPATH string `eval:"$GOPATH"` 123 | // Cwd string `eval:"getcwd()"` 124 | // }) 125 | // 126 | // is 127 | // 128 | // {'GOPATH': $GOPATH, Cwd: getcwd()} 129 | func (p *Plugin) HandleFunction(options *FunctionOptions, fn any) { 130 | m := make(map[string]string) 131 | 132 | if options.Eval != "" { 133 | m["eval"] = eval(options.Eval, fn) 134 | } 135 | 136 | p.handle(fn, &pluginSpec{ 137 | sm: `0:function:` + options.Name, 138 | Type: `function`, 139 | Name: options.Name, 140 | Sync: isSync(fn), 141 | Opts: m, 142 | }) 143 | } 144 | 145 | // CommandOptions specifies command options. 146 | type CommandOptions struct { 147 | // Name is the name of the command in Nvim. The name must be made of 148 | // alphanumeric characters and '_', and must start with a capital 149 | // letter. 150 | Name string 151 | 152 | // NArgs specifies the number command arguments. 153 | // 154 | // 0 No arguments are allowed 155 | // 1 Exactly one argument is required, it includes spaces 156 | // * Any number of arguments are allowed (0, 1, or many), 157 | // separated by white space 158 | // ? 0 or 1 arguments are allowed 159 | // + Arguments must be supplied, but any number are allowed 160 | NArgs string 161 | 162 | // Range specifies that the command accepts a range. 163 | // 164 | // . Range allowed, default is current line. The value 165 | // "." is converted to "" for Nvim. 166 | // % Range allowed, default is whole file (1,$) 167 | // N A count (default N) which is specified in the line 168 | // number position (like |:split|); allows for zero line 169 | // number. 170 | // 171 | // :help :command-range 172 | Range string 173 | 174 | // Count specifies that the command accepts a count. 175 | // 176 | // N A count (default N) which is specified either in the line 177 | // number position, or as an initial argument (like |:Next|). 178 | // Specifying -count (without a default) acts like -count=0 179 | // 180 | // :help :command-count 181 | Count string 182 | 183 | // Addr sepcifies the domain for the range option 184 | // 185 | // lines Range of lines (this is the default) 186 | // arguments Range for arguments 187 | // buffers Range for buffers (also not loaded buffers) 188 | // loaded_buffers Range for loaded buffers 189 | // windows Range for windows 190 | // tabs Range for tab pages 191 | // 192 | // :help command-addr 193 | Addr string 194 | 195 | // Eval is evaluated in Nvim and the result is passed as an argument. 196 | Eval string 197 | 198 | // Complete specifies command completion. 199 | // 200 | // :help :command-complete 201 | Complete string 202 | 203 | // Bang specifies that the command can take a ! modifier (like :q or :w). 204 | Bang bool 205 | 206 | // Register specifies that the first argument to the command can be an 207 | // optional register name (like :del, :put, :yank). 208 | Register bool 209 | 210 | // Bar specifies that the command can be followed by a "|" and another 211 | // command. A "|" inside the command argument is not allowed then. Also 212 | // checks for a " to start a comment. 213 | Bar bool 214 | } 215 | 216 | // HandleCommand registers fn as a handler for a Nvim command. The arguments 217 | // to the function fn are: 218 | // 219 | // v *nvim.Nvim optional 220 | // args []string when options.NArgs != "" 221 | // range [2]int when options.Range == "." or Range == "%" 222 | // range int when options.Range == N or Count != "" 223 | // bang bool when options.Bang == true 224 | // register string when options.Register == true 225 | // eval any when options.Eval != "" 226 | // 227 | // The function fn must return an error. 228 | // 229 | // If options.Eval == "*", then HandleCommand constructs the expression to 230 | // evaluate in Nvim from the type of fn's last argument. See the 231 | // HandleFunction documentation for information on how the expression is 232 | // generated. 233 | func (p *Plugin) HandleCommand(options *CommandOptions, fn any) { 234 | m := make(map[string]string) 235 | 236 | if options.NArgs != "" { 237 | m[`nargs`] = options.NArgs 238 | } 239 | 240 | switch { 241 | case options.Range == `.`: 242 | options.Range = "" 243 | fallthrough 244 | case options.Range != "": 245 | m[`range`] = options.Range 246 | case options.Count != "": 247 | m[`count`] = options.Count 248 | } 249 | 250 | if options.Bang { 251 | m[`bang`] = "" 252 | } 253 | 254 | if options.Register { 255 | m[`register`] = "" 256 | } 257 | 258 | if options.Eval != "" { 259 | m[`eval`] = eval(options.Eval, fn) 260 | } 261 | 262 | if options.Addr != "" { 263 | m[`addr`] = options.Addr 264 | } 265 | 266 | if options.Bar { 267 | m[`bar`] = "" 268 | } 269 | 270 | if options.Complete != "" { 271 | m[`complete`] = options.Complete 272 | } 273 | 274 | p.handle(fn, &pluginSpec{ 275 | sm: `0:command:` + options.Name, 276 | Type: `command`, 277 | Name: options.Name, 278 | Sync: isSync(fn), 279 | Opts: m, 280 | }) 281 | } 282 | 283 | // AutocmdOptions specifies autocmd options. 284 | type AutocmdOptions struct { 285 | // Event is the event name. 286 | Event string 287 | 288 | // Group specifies the autocmd group. 289 | Group string 290 | 291 | // Pattern specifies an autocmd pattern. 292 | // 293 | // :help autocmd-patterns 294 | Pattern string 295 | 296 | // Nested allows nested autocmds. 297 | // 298 | // :help autocmd-nested 299 | Nested bool 300 | 301 | // Once supplys the command is executed once, then removed ("one shot"). 302 | // 303 | // :help autocmd-once 304 | Once bool 305 | 306 | // Eval is evaluated in Nvim and the result is passed to the handler 307 | // function. 308 | Eval string 309 | } 310 | 311 | // HandleAutocmd registers fn as a handler an autocmnd event. 312 | // 313 | // If options.Eval == "*", then HandleAutocmd constructs the expression to 314 | // evaluate in Nvim from the type of fn's last argument. See the HandleFunction 315 | // documentation for information on how the expression is generated. 316 | func (p *Plugin) HandleAutocmd(options *AutocmdOptions, fn any) { 317 | pattern := "" 318 | 319 | m := make(map[string]string) 320 | 321 | if options.Group != "" { 322 | m[`group`] = options.Group 323 | } 324 | 325 | if options.Pattern != "" { 326 | m[`pattern`] = options.Pattern 327 | pattern = options.Pattern 328 | } 329 | 330 | if options.Nested { 331 | m[`nested`] = `1` 332 | } 333 | 334 | if options.Once { 335 | m[`once`] = `1` 336 | } 337 | 338 | if options.Eval != "" { 339 | m[`eval`] = eval(options.Eval, fn) 340 | } 341 | 342 | // Compute unique path for event and pattern. 343 | ep := options.Event + ":" + pattern 344 | i := p.eventPathCounts[ep] 345 | p.eventPathCounts[ep] = i + 1 346 | 347 | sm := fmt.Sprintf(`%d:autocmd:%s`, i, ep) 348 | 349 | p.handle(fn, &pluginSpec{ 350 | sm: sm, 351 | Type: `autocmd`, 352 | Name: options.Event, 353 | Sync: isSync(fn), 354 | Opts: m, 355 | }) 356 | } 357 | 358 | // RegisterForTests registers the plugin with Nvim. Use this method for testing 359 | // plugins in an embedded instance of Nvim. 360 | func (p *Plugin) RegisterForTests() error { 361 | specs := make(map[string][]*pluginSpec) 362 | for _, spec := range p.pluginSpecs { 363 | specs[spec.path()] = append(specs[spec.path()], spec) 364 | } 365 | 366 | const host = "nvim-go-test" 367 | for path, specs := range specs { 368 | if err := p.Nvim.Call("remote#host#RegisterPlugin", nil, host, path, specs); err != nil { 369 | return err 370 | } 371 | } 372 | err := p.Nvim.Call("remote#host#Register", nil, host, "x", p.Nvim.ChannelID()) 373 | 374 | return err 375 | } 376 | 377 | func eval(eval string, f any) string { 378 | if eval != `*` { 379 | return eval 380 | } 381 | 382 | ft := reflect.TypeOf(f) 383 | if ft.Kind() != reflect.Func || ft.NumIn() < 1 { 384 | panic(`Eval: "*" option requires function with at least one argument`) 385 | } 386 | 387 | argt := ft.In(ft.NumIn() - 1) 388 | if argt.Kind() != reflect.Ptr || argt.Elem().Kind() != reflect.Struct { 389 | panic(`Eval: "*" option requires function with pointer to struct as last argument`) 390 | } 391 | 392 | return structEval(argt.Elem()) 393 | } 394 | 395 | func structEval(t reflect.Type) string { 396 | var sb strings.Builder 397 | 398 | sb.WriteByte('{') 399 | sep := "" 400 | 401 | for i := 0; i < t.NumField(); i++ { 402 | sf := t.Field(i) 403 | if sf.Anonymous { 404 | panic(`Eval: "*" does not support anonymous fields`) 405 | } 406 | 407 | eval := sf.Tag.Get("eval") 408 | if eval == "" { 409 | ft := sf.Type 410 | if ft.Kind() == reflect.Ptr { 411 | ft = ft.Elem() 412 | } 413 | 414 | if ft.Kind() == reflect.Struct { 415 | eval = structEval(ft) 416 | } 417 | } 418 | if eval == "" { 419 | continue 420 | } 421 | 422 | name := strings.Split(sf.Tag.Get("msgpack"), ",")[0] 423 | if name == "" { 424 | name = sf.Name 425 | } 426 | 427 | sb.WriteString(sep) 428 | sb.WriteByte('\'') 429 | sb.WriteString(name) 430 | sb.WriteString("': ") 431 | sb.WriteString(eval) 432 | sep = ", " 433 | } 434 | sb.WriteByte('}') 435 | 436 | return sb.String() 437 | } 438 | 439 | type byServiceMethod []*pluginSpec 440 | 441 | func (a byServiceMethod) Len() int { return len(a) } 442 | func (a byServiceMethod) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 443 | func (a byServiceMethod) Less(i, j int) bool { return a[i].sm < a[j].sm } 444 | 445 | func (p *Plugin) Manifest(host string) []byte { 446 | var buf bytes.Buffer 447 | 448 | // Sort for consistent order on output. 449 | sort.Sort(byServiceMethod(p.pluginSpecs)) 450 | escape := strings.NewReplacer(`'`, `''`).Replace 451 | 452 | prevPath := "" 453 | for _, spec := range p.pluginSpecs { 454 | path := spec.path() 455 | if path != prevPath { 456 | if prevPath != "" { 457 | fmt.Fprintf(&buf, "\\ )") 458 | } 459 | fmt.Fprintf(&buf, "call remote#host#RegisterPlugin('%s', '%s', [\n", host, path) 460 | prevPath = path 461 | } 462 | 463 | sync := "0" 464 | if spec.Sync { 465 | sync = "1" 466 | } 467 | 468 | fmt.Fprintf(&buf, "\\ {'type': '%s', 'name': '%s', 'sync': %s, 'opts': {", spec.Type, spec.Name, sync) 469 | 470 | var keys []string 471 | for k := range spec.Opts { 472 | keys = append(keys, k) 473 | } 474 | sort.Strings(keys) 475 | 476 | optDelim := "" 477 | for _, k := range keys { 478 | fmt.Fprintf(&buf, "%s'%s': '%s'", optDelim, k, escape(spec.Opts[k])) 479 | optDelim = ", " 480 | } 481 | 482 | fmt.Fprintf(&buf, "}},\n") 483 | } 484 | if prevPath != "" { 485 | fmt.Fprintf(&buf, "\\ ])\n") 486 | } 487 | 488 | return buf.Bytes() 489 | } 490 | -------------------------------------------------------------------------------- /msgpack/unpack_test.go: -------------------------------------------------------------------------------- 1 | package msgpack 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "io" 7 | "testing" 8 | ) 9 | 10 | func TestType_String(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := map[string]struct { 14 | tr Type 15 | want string 16 | }{ 17 | "Invalid": { 18 | tr: Invalid, 19 | want: "Invalid", 20 | }, 21 | "Nil": { 22 | tr: Nil, 23 | want: "Nil", 24 | }, 25 | "Bool": { 26 | tr: Bool, 27 | want: "Bool", 28 | }, 29 | "Int": { 30 | tr: Int, 31 | want: "Int", 32 | }, 33 | "Uint": { 34 | tr: Uint, 35 | want: "Uint", 36 | }, 37 | "Float": { 38 | tr: Float, 39 | want: "Float", 40 | }, 41 | "ArrayLen": { 42 | tr: ArrayLen, 43 | want: "ArrayLen", 44 | }, 45 | "MapLen": { 46 | tr: MapLen, 47 | want: "MapLen", 48 | }, 49 | "String": { 50 | tr: String, 51 | want: "String", 52 | }, 53 | "Binary": { 54 | tr: Binary, 55 | want: "Binary", 56 | }, 57 | "Extension": { 58 | tr: Extension, 59 | want: "Extension", 60 | }, 61 | "unknown": { 62 | tr: Type(11), 63 | want: "unknown", 64 | }, 65 | } 66 | for name, tt := range tests { 67 | tt := tt 68 | t.Run(name, func(t *testing.T) { 69 | t.Parallel() 70 | 71 | if got := tt.tr.String(); got != tt.want { 72 | t.Fatalf("Type.String() = %v, want %v", got, tt.want) 73 | } 74 | }) 75 | } 76 | } 77 | 78 | var unpackTests = map[string]struct { 79 | // typ is Expected type 80 | typ Type 81 | // v is Expected value 82 | v any 83 | // hs is Hex encodings of typ, v 84 | hs []string 85 | }{ 86 | "Int/0x0": { 87 | typ: Int, 88 | v: int64(0x0), 89 | hs: []string{ 90 | "00", 91 | "d000", 92 | "d10000", 93 | "d200000000", 94 | "d30000000000000000", 95 | }, 96 | }, 97 | "Int/0x1": { 98 | typ: Int, 99 | v: int64(0x1), 100 | hs: []string{ 101 | "01", 102 | "d001", 103 | "d10001", 104 | "d200000001", 105 | "d30000000000000001", 106 | }, 107 | }, 108 | "Int/0x7f": { 109 | typ: Int, 110 | v: int64(0x7f), 111 | hs: []string{ 112 | "7f", 113 | "d07f", 114 | "d1007f", 115 | "d20000007f", 116 | "d3000000000000007f", 117 | }, 118 | }, 119 | "Int/0x80": { 120 | typ: Int, 121 | v: int64(0x80), 122 | hs: []string{ 123 | "d10080", 124 | "d200000080", 125 | "d30000000000000080", 126 | }, 127 | }, 128 | "Int/0x7fff": { 129 | typ: Int, 130 | v: int64(0x7fff), 131 | hs: []string{ 132 | "d17fff", 133 | "d200007fff", 134 | "d30000000000007fff", 135 | }, 136 | }, 137 | "Int/0x8000": { 138 | typ: Int, 139 | v: int64(0x8000), 140 | hs: []string{ 141 | "d200008000", 142 | "d30000000000008000", 143 | }, 144 | }, 145 | "Int/0x7fffffff": { 146 | typ: Int, 147 | v: int64(0x7fffffff), 148 | hs: []string{ 149 | "d27fffffff", 150 | "d3000000007fffffff", 151 | }, 152 | }, 153 | "Int/0x80000000": { 154 | typ: Int, 155 | v: int64(0x80000000), 156 | hs: []string{"d30000000080000000"}, 157 | }, 158 | "Int/0x7fffffffffffffff": { 159 | typ: Int, 160 | v: int64(0x7fffffffffffffff), 161 | hs: []string{"d37fffffffffffffff"}, 162 | }, 163 | "Int/-0x1": { 164 | typ: Int, 165 | v: int64(-0x1), 166 | hs: []string{ 167 | "ff", 168 | "d0ff", 169 | "d1ffff", 170 | "d2ffffffff", 171 | "d3ffffffffffffffff", 172 | }, 173 | }, 174 | "Int/-0x20": { 175 | typ: Int, 176 | v: int64(-0x20), 177 | hs: []string{ 178 | "e0", 179 | "d0e0", 180 | "d1ffe0", 181 | "d2ffffffe0", 182 | "d3ffffffffffffffe0", 183 | }, 184 | }, 185 | "Int/-0x21": { 186 | typ: Int, 187 | v: int64(-0x21), 188 | hs: []string{ 189 | "d0df", 190 | "d1ffdf", 191 | "d2ffffffdf", 192 | "d3ffffffffffffffdf", 193 | }, 194 | }, 195 | "Int/-0x80)": { 196 | typ: Int, 197 | v: int64(-0x80), 198 | hs: []string{ 199 | "d080", 200 | "d1ff80", 201 | "d2ffffff80", 202 | "d3ffffffffffffff80", 203 | }, 204 | }, 205 | "Int/-0x81": { 206 | typ: Int, 207 | v: int64(-0x81), 208 | hs: []string{ 209 | "d1ff7f", 210 | "d2ffffff7f", 211 | "d3ffffffffffffff7f", 212 | }, 213 | }, 214 | "Int/-0x8000": { 215 | typ: Int, 216 | v: int64(-0x8000), 217 | hs: []string{ 218 | "d18000", 219 | "d2ffff8000", 220 | "d3ffffffffffff8000", 221 | }, 222 | }, 223 | "Int/-0x8001": { 224 | typ: Int, 225 | v: int64(-0x8001), 226 | hs: []string{ 227 | "d2ffff7fff", 228 | "d3ffffffffffff7fff", 229 | }, 230 | }, 231 | "Int/-0x80000000": { 232 | typ: Int, 233 | v: int64(-0x80000000), 234 | hs: []string{ 235 | "d280000000", 236 | "d3ffffffff80000000", 237 | }, 238 | }, 239 | "Int/-0x80000001": { 240 | typ: Int, 241 | v: int64(-0x80000001), 242 | hs: []string{"d3ffffffff7fffffff"}, 243 | }, 244 | "Int/-0x8000000000000000": { 245 | typ: Int, 246 | v: int64(-0x8000000000000000), 247 | hs: []string{"d38000000000000000"}, 248 | }, 249 | "Uint/0xff": { 250 | typ: Uint, 251 | v: uint64(0xff), 252 | hs: []string{ 253 | "ccff", 254 | "cd00ff", 255 | "ce000000ff", 256 | "cf00000000000000ff", 257 | }, 258 | }, 259 | "Uint/0x100": { 260 | typ: Uint, 261 | v: uint64(0x100), 262 | hs: []string{ 263 | "cd0100", 264 | "ce00000100", 265 | "cf0000000000000100", 266 | }, 267 | }, 268 | "Uint/0xffff": { 269 | typ: Uint, 270 | v: uint64(0xffff), 271 | hs: []string{ 272 | "cdffff", 273 | "ce0000ffff", 274 | "cf000000000000ffff", 275 | }, 276 | }, 277 | "Uint/0x10000": { 278 | typ: Uint, 279 | v: uint64(0x10000), 280 | hs: []string{ 281 | "ce00010000", 282 | "cf0000000000010000", 283 | }, 284 | }, 285 | "Uint/0xffffffff": { 286 | typ: Uint, 287 | v: uint64(0xffffffff), 288 | hs: []string{ 289 | "ceffffffff", 290 | "cf00000000ffffffff", 291 | }, 292 | }, 293 | "Uint/0x100000000": { 294 | typ: Uint, 295 | v: uint64(0x100000000), 296 | hs: []string{ 297 | "cf0000000100000000", 298 | }, 299 | }, 300 | "Uint/0xffffffffffffffff": { 301 | typ: Uint, 302 | v: uint64(0xffffffffffffffff), 303 | hs: []string{"cfffffffffffffffff"}, 304 | }, 305 | "Nil": { 306 | typ: Nil, 307 | v: nil, 308 | hs: []string{"c0"}, 309 | }, 310 | "Bool/True": { 311 | typ: Bool, 312 | v: true, 313 | hs: []string{"c3"}, 314 | }, 315 | "Bool/False": { 316 | typ: Bool, 317 | v: false, 318 | hs: []string{"c2"}, 319 | }, 320 | "Float/123456": { 321 | typ: Float, 322 | v: float64(123456), 323 | hs: []string{"ca47f12000"}, 324 | }, 325 | "Float/1.23456": { 326 | typ: Float, 327 | v: float64(1.23456), 328 | hs: []string{"cb3ff3c0c1fc8f3238"}, 329 | }, 330 | "MapLen/0x0": { 331 | typ: MapLen, 332 | v: int64(0x0), 333 | hs: []string{ 334 | "80", 335 | "de0000", 336 | "df00000000", 337 | }, 338 | }, 339 | "MapLen/0x1": { 340 | typ: MapLen, 341 | v: int64(0x1), 342 | hs: []string{ 343 | "81", 344 | "de0001", 345 | "df00000001", 346 | }, 347 | }, 348 | "MapLen/0xf": { 349 | typ: MapLen, 350 | v: int64(0xf), 351 | hs: []string{ 352 | "8f", 353 | "de000f", 354 | "df0000000f", 355 | }, 356 | }, 357 | "MapLen/0x10": { 358 | typ: MapLen, 359 | v: int64(0x10), 360 | hs: []string{ 361 | "de0010", 362 | "df00000010", 363 | }, 364 | }, 365 | "MapLen/0xffff": { 366 | typ: MapLen, 367 | v: int64(0xffff), 368 | hs: []string{ 369 | "deffff", 370 | "df0000ffff", 371 | }, 372 | }, 373 | "MapLen/0x10000": { 374 | typ: MapLen, 375 | v: int64(0x10000), 376 | hs: []string{"df00010000"}, 377 | }, 378 | "MapLen/0xffffffff": { 379 | typ: MapLen, 380 | v: int64(0xffffffff), 381 | hs: []string{"dfffffffff"}, 382 | }, 383 | "ArrayLen/0x0": { 384 | typ: ArrayLen, 385 | v: int64(0x0), 386 | hs: []string{ 387 | "90", 388 | "dc0000", 389 | "dd00000000", 390 | }, 391 | }, 392 | "ArrayLen/0x1": { 393 | typ: ArrayLen, 394 | v: int64(0x1), 395 | hs: []string{ 396 | "91", 397 | "dc0001", 398 | "dd00000001", 399 | }, 400 | }, 401 | "ArrayLen/0xf": { 402 | typ: ArrayLen, 403 | v: int64(0xf), 404 | hs: []string{ 405 | "9f", 406 | "dc000f", 407 | "dd0000000f", 408 | }, 409 | }, 410 | "ArrayLen/0x10": { 411 | typ: ArrayLen, 412 | v: int64(0x10), 413 | hs: []string{ 414 | "dc0010", 415 | "dd00000010", 416 | }, 417 | }, 418 | "ArrayLen/0xffff": { 419 | typ: ArrayLen, 420 | v: int64(0xffff), 421 | hs: []string{ 422 | "dcffff", 423 | "dd0000ffff", 424 | }, 425 | }, 426 | "ArrayLen/0x10000": { 427 | typ: ArrayLen, 428 | v: int64(0x10000), 429 | hs: []string{"dd00010000"}, 430 | }, 431 | "ArrayLen/0xffffffff": { 432 | typ: ArrayLen, 433 | v: int64(0xffffffff), 434 | hs: []string{"ddffffffff"}, 435 | }, 436 | "String/Empty": { 437 | typ: String, 438 | v: "", 439 | hs: []string{ 440 | "a0", 441 | "d900", 442 | "da0000", 443 | "db00000000", 444 | }, 445 | }, 446 | "String/1": { 447 | typ: String, 448 | v: "1", 449 | hs: []string{ 450 | "a131", 451 | "d90131", 452 | "da000131", 453 | "db0000000131", 454 | }, 455 | }, 456 | "String/1234567890123456789012345678901": { 457 | typ: String, 458 | v: "1234567890123456789012345678901", 459 | hs: []string{ 460 | "bf31323334353637383930313233343536373839303132333435363738393031", 461 | "d91f31323334353637383930313233343536373839303132333435363738393031", 462 | "da001f31323334353637383930313233343536373839303132333435363738393031", 463 | "db0000001f31323334353637383930313233343536373839303132333435363738393031", 464 | }, 465 | }, 466 | "String/12345678901234567890123456789012": { 467 | typ: String, 468 | v: "12345678901234567890123456789012", 469 | hs: []string{ 470 | "d9203132333435363738393031323334353637383930313233343536373839303132", 471 | "da00203132333435363738393031323334353637383930313233343536373839303132", 472 | "db000000203132333435363738393031323334353637383930313233343536373839303132", 473 | }, 474 | }, 475 | "Binary/Empty": { 476 | typ: Binary, 477 | v: "", 478 | hs: []string{ 479 | "c400", 480 | "c50000", 481 | "c600000000", 482 | }, 483 | }, 484 | "Binary/1": { 485 | typ: Binary, 486 | v: "1", 487 | hs: []string{ 488 | "c40131", 489 | "c5000131", 490 | "c60000000131", 491 | }, 492 | }, 493 | "Extension/1/Empty": { 494 | typ: Extension, 495 | v: extension{1, ""}, 496 | hs: []string{ 497 | "c70001", 498 | "c8000001", 499 | "c90000000001", 500 | }, 501 | }, 502 | "Extension/2/1": { 503 | typ: Extension, 504 | v: extension{2, "1"}, 505 | hs: []string{ 506 | "d40231", 507 | "c7010231", 508 | "c800010231", 509 | "c9000000010231", 510 | }, 511 | }, 512 | "Extension/3/12": { 513 | typ: Extension, 514 | v: extension{3, "12"}, 515 | hs: []string{ 516 | "d5033132", 517 | "c702033132", 518 | "c80002033132", 519 | "c900000002033132", 520 | }, 521 | }, 522 | "Extension/4/1234": { 523 | typ: Extension, 524 | v: extension{4, "1234"}, 525 | hs: []string{ 526 | "d60431323334", 527 | "c7040431323334", 528 | "c800040431323334", 529 | "c9000000040431323334", 530 | }, 531 | }, 532 | "Extension/5/12345678": { 533 | typ: Extension, 534 | v: extension{5, "12345678"}, 535 | hs: []string{ 536 | "d7053132333435363738", 537 | "c708053132333435363738", 538 | "c80008053132333435363738", 539 | "c900000008053132333435363738", 540 | }, 541 | }, 542 | "Extension/6/1234567890123456": { 543 | typ: Extension, 544 | v: extension{6, "1234567890123456"}, 545 | hs: []string{ 546 | "d80631323334353637383930313233343536", 547 | "c7100631323334353637383930313233343536", 548 | "c800100631323334353637383930313233343536", 549 | "c9000000100631323334353637383930313233343536", 550 | }, 551 | }, 552 | "Extension/7/12345678901234567": { 553 | typ: Extension, 554 | v: extension{7, "12345678901234567"}, 555 | hs: []string{ 556 | "c711073132333435363738393031323334353637", 557 | "c80011073132333435363738393031323334353637", 558 | "c900000011073132333435363738393031323334353637", 559 | }, 560 | }, 561 | "Invalid": { 562 | typ: Invalid, 563 | v: nil, 564 | hs: []string{"c1"}, 565 | }, 566 | } 567 | 568 | func TestUnpack(t *testing.T) { 569 | t.Parallel() 570 | 571 | for name, tt := range unpackTests { 572 | tt := tt 573 | t.Run(name, func(t *testing.T) { 574 | t.Parallel() 575 | 576 | for _, h := range tt.hs { 577 | p, err := hex.DecodeString(h) 578 | if err != nil { 579 | t.Fatalf("decode(%s) returned error %v", h, err) 580 | } 581 | 582 | d := NewDecoder(bytes.NewReader(p)) 583 | err = d.Unpack() 584 | if err != nil && tt.typ != Invalid { 585 | t.Fatalf("unpack(%s) returned %v", h, err) 586 | } 587 | 588 | if d.Type() != tt.typ { 589 | t.Fatalf("unpack(%s) returned type %d, want %d", h, d.Type(), tt.typ) 590 | } 591 | 592 | switch v := tt.v.(type) { 593 | case int64: 594 | if d.Int() != v { 595 | t.Fatalf("unpack(%s) returned %x, want %x", h, d.Int(), v) 596 | } 597 | case uint64: 598 | if d.Uint() != v { 599 | t.Fatalf("unpack(%s) returned %x, want %x", h, d.Uint(), v) 600 | } 601 | case bool: 602 | if d.Bool() != v { 603 | t.Fatalf("unpack(%s) returned %v, want %v", h, d.Bool(), v) 604 | } 605 | case float64: 606 | if d.Float() != v { 607 | t.Fatalf("unpack(%s) returned %v, want %v", h, d.Float(), v) 608 | } 609 | case string: 610 | if d.String() != v { 611 | t.Fatalf("unpack(%s) returned %q, want %q", h, d.String(), v) 612 | } 613 | case extension: 614 | k, d := d.Extension(), d.String() 615 | if k != v.k || d != v.d { 616 | t.Fatalf("unpack(%s) returned (%d, %q) want (%d, %q)", h, k, d, v.k, v.d) 617 | } 618 | case nil: 619 | // nothing to do 620 | default: 621 | t.Fatalf("no check for %T", v) 622 | } 623 | } 624 | }) 625 | } 626 | } 627 | 628 | func TestUnpackEOF(t *testing.T) { 629 | t.Parallel() 630 | 631 | for name, tt := range unpackTests { 632 | tt := tt 633 | t.Run(name, func(t *testing.T) { 634 | t.Parallel() 635 | 636 | for _, h := range tt.hs { 637 | p, err := hex.DecodeString(h) 638 | if err != nil { 639 | t.Fatalf("decode(%s) returned error %v", h, err) 640 | } 641 | 642 | for i := 1; i < len(p); i++ { 643 | d := NewDecoder(bytes.NewReader(p[:i])) 644 | err = d.Unpack() 645 | if err != io.ErrUnexpectedEOF { 646 | t.Fatalf("unpack(%s[:%d]) returned %v, want %v", h, i, err, io.ErrUnexpectedEOF) 647 | } 648 | } 649 | } 650 | }) 651 | } 652 | } 653 | -------------------------------------------------------------------------------- /msgpack/decode.go: -------------------------------------------------------------------------------- 1 | package msgpack 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "sync" 8 | ) 9 | 10 | // Unmarshaler is the interface implemented by objects that can decode 11 | // themselves from a MessagePack stream. 12 | type Unmarshaler interface { 13 | UnmarshalMsgPack(d *Decoder) error 14 | } 15 | 16 | // ErrInvalidDecodeArg is the invalid argument error. 17 | var ErrInvalidDecodeArg = errors.New("msgpack: argument to Decode must be non-nil pointer, slice or map") 18 | 19 | // DecodeConvertError describes a MessagePack value that was not appropriate 20 | // for a value of a specific Go type. 21 | type DecodeConvertError struct { 22 | // The MessagePack type of the value. 23 | SrcType Type 24 | // Option value. 25 | SrcValue any 26 | // Type of the Go value that could not be assigned to. 27 | DestType reflect.Type 28 | } 29 | 30 | // Error implements the error interface. 31 | func (e *DecodeConvertError) Error() string { 32 | if e.SrcValue == nil { 33 | return fmt.Sprintf("msgpack: cannot convert %s to %s", e.SrcType, e.DestType) 34 | } 35 | return fmt.Sprintf("msgpack: cannot convert %s(%v) to %s", e.SrcType, e.SrcValue, e.DestType) 36 | } 37 | 38 | func decodeUnsupportedType(ds *decodeState, v reflect.Value) { 39 | ds.saveErrorAndSkip(v, nil) 40 | } 41 | 42 | // decodeState represents the state while decoding value. 43 | type decodeState struct { 44 | *Decoder 45 | errSaved error 46 | } 47 | 48 | func (ds *decodeState) unpack() { 49 | if err := ds.Decoder.Unpack(); err != nil { 50 | abort(err) 51 | } 52 | } 53 | 54 | func (ds *decodeState) skip() { 55 | if err := ds.Decoder.Skip(); err != nil { 56 | abort(err) 57 | } 58 | } 59 | 60 | func (ds *decodeState) saveErrorAndSkip(destValue reflect.Value, srcValue any) { 61 | if ds.errSaved == nil { 62 | ds.errSaved = &DecodeConvertError{ 63 | SrcType: ds.Type(), 64 | SrcValue: srcValue, 65 | DestType: destValue.Type(), 66 | } 67 | } 68 | ds.skip() 69 | } 70 | 71 | // Decode decodes the next value in the stream to v. 72 | // 73 | // Decode uses the inverse of the encodings that Encoder.Encode uses, 74 | // allocating maps, slices, and pointers as necessary, with the following 75 | // additional rules: 76 | // 77 | // To decode into a pointer, Decode first handles the case of a MessagePack 78 | // nil. In that case, Decode sets the pointer to nil. Otherwise, Decode decodes 79 | // the stream into the value pointed at by the pointer. If the pointer is nil, 80 | // Decode allocates a new value for it to point to. 81 | // 82 | // To decode a MessagePack array into a slice, Decode sets the slice length to 83 | // the length of the MessagePack array or reallocates the slice if there is 84 | // insufficient capaicity. Slice elments are not cleared before decoding the 85 | // element. 86 | // 87 | // To decode a MessagePack array into a Go array, Decode decodes the 88 | // MessagePack array elements into corresponding Go array elements. If the Go 89 | // array is smaller than the MessagePack array, the additional MessagePack 90 | // array elements are discarded. If the MessagePack array is smaller than the 91 | // Go array, the additional Go array elements are set to zero values. 92 | // 93 | // If a MessagePack value is not appropriate for a given target type, or if a 94 | // MessagePack number overflows the target type, Decode skips that field and 95 | // completes the decoding as best it can. If no more serious errors are 96 | // encountered, Decode returns an DecodeConvertError describing the earliest 97 | // such error. 98 | func (d *Decoder) Decode(v any) (err error) { 99 | defer handleAbort(&err) 100 | ds := &decodeState{ 101 | Decoder: d, 102 | } 103 | ds.unpack() 104 | 105 | rv := reflect.ValueOf(v) 106 | if (rv.Kind() != reflect.Ptr && rv.Kind() != reflect.Slice && rv.Kind() != reflect.Map) || rv.IsNil() { 107 | ds.skip() 108 | return ErrInvalidDecodeArg 109 | } 110 | 111 | if rv.Kind() == reflect.Ptr { 112 | rv = rv.Elem() 113 | } 114 | decoderForType(rv.Type(), nil)(ds, rv) 115 | 116 | return ds.errSaved 117 | } 118 | 119 | var decodeFuncCache struct { 120 | sync.RWMutex 121 | m map[reflect.Type]decodeFunc 122 | } 123 | 124 | type decodeFunc func(*decodeState, reflect.Value) 125 | 126 | type decodeBuilder struct { 127 | m map[reflect.Type]decodeFunc 128 | } 129 | 130 | func decoderForType(t reflect.Type, b *decodeBuilder) decodeFunc { 131 | decodeFuncCache.RLock() 132 | f, ok := decodeFuncCache.m[t] 133 | decodeFuncCache.RUnlock() 134 | if ok { 135 | return f 136 | } 137 | 138 | save := false 139 | if b == nil { 140 | b = &decodeBuilder{m: make(map[reflect.Type]decodeFunc)} 141 | save = true 142 | } else if f, ok := b.m[t]; ok { 143 | return f 144 | } 145 | 146 | // Add temporary entry to break recursion 147 | b.m[t] = func(ds *decodeState, v reflect.Value) { 148 | f(ds, v) 149 | } 150 | f = b.decoder(t) 151 | b.m[t] = f 152 | 153 | if save { 154 | decodeFuncCache.Lock() 155 | 156 | if decodeFuncCache.m == nil { 157 | decodeFuncCache.m = make(map[reflect.Type]decodeFunc) 158 | } 159 | for t, f := range b.m { 160 | decodeFuncCache.m[t] = f 161 | } 162 | 163 | decodeFuncCache.Unlock() 164 | } 165 | return f 166 | } 167 | 168 | func (b *decodeBuilder) decoder(t reflect.Type) decodeFunc { 169 | if t.Kind() == reflect.Ptr && t.Implements(unmarshalerType) { 170 | return unmarshalDecoder 171 | } 172 | 173 | var f decodeFunc 174 | switch t.Kind() { 175 | case reflect.Bool: 176 | f = boolDecoder 177 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 178 | f = intDecoder 179 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 180 | f = uintDecoder 181 | case reflect.Float32, reflect.Float64: 182 | f = floatDecoder 183 | case reflect.String: 184 | f = stringDecoder 185 | case reflect.Array: 186 | f = b.arrayDecoder(t) 187 | case reflect.Slice: 188 | f = b.sliceDecoder(t) 189 | case reflect.Map: 190 | f = b.mapDecoder(t) 191 | case reflect.Interface: 192 | f = interfaceDecoder 193 | case reflect.Struct: 194 | f = b.structDecoder(t) 195 | case reflect.Ptr: 196 | f = b.ptrDecoder(t) 197 | default: 198 | f = decodeUnsupportedType 199 | } 200 | 201 | if t.Kind() != reflect.Ptr && reflect.PtrTo(t).Implements(unmarshalerType) { 202 | f = unmarshalAddrDecoder{f}.decode 203 | } 204 | 205 | return f 206 | } 207 | 208 | func boolDecoder(ds *decodeState, v reflect.Value) { 209 | var x bool 210 | 211 | switch ds.Type() { 212 | case Bool: 213 | x = ds.Bool() 214 | case Int: 215 | x = ds.Int() != 0 216 | case Uint: 217 | x = ds.Uint() != 0 218 | default: 219 | ds.saveErrorAndSkip(v, nil) 220 | return 221 | } 222 | 223 | v.SetBool(x) 224 | } 225 | 226 | func intDecoder(ds *decodeState, v reflect.Value) { 227 | var x int64 228 | 229 | switch ds.Type() { 230 | case Int: 231 | x = ds.Int() 232 | case Uint: 233 | n := ds.Uint() 234 | x = int64(n) 235 | if x < 0 { 236 | ds.saveErrorAndSkip(v, n) 237 | return 238 | } 239 | case Float: 240 | f := ds.Float() 241 | x = int64(f) 242 | if float64(x) != f { 243 | ds.saveErrorAndSkip(v, f) 244 | return 245 | } 246 | default: 247 | ds.saveErrorAndSkip(v, nil) 248 | return 249 | } 250 | 251 | if v.OverflowInt(x) { 252 | ds.saveErrorAndSkip(v, x) 253 | return 254 | } 255 | 256 | v.SetInt(x) 257 | } 258 | 259 | func uintDecoder(ds *decodeState, v reflect.Value) { 260 | var x uint64 261 | 262 | switch ds.Type() { 263 | case Uint: 264 | x = ds.Uint() 265 | case Int: 266 | i := ds.Int() 267 | if i < 0 { 268 | ds.saveErrorAndSkip(v, i) 269 | return 270 | } 271 | x = uint64(i) 272 | case Float: 273 | f := ds.Float() 274 | x = uint64(f) 275 | if float64(x) != f { 276 | ds.saveErrorAndSkip(v, f) 277 | return 278 | } 279 | default: 280 | ds.saveErrorAndSkip(v, nil) 281 | return 282 | } 283 | 284 | if v.OverflowUint(x) { 285 | ds.saveErrorAndSkip(v, x) 286 | return 287 | } 288 | 289 | v.SetUint(x) 290 | } 291 | 292 | func floatDecoder(ds *decodeState, v reflect.Value) { 293 | var x float64 294 | 295 | switch ds.Type() { 296 | case Int: 297 | i := ds.Int() 298 | x = float64(i) 299 | if int64(x) != i { 300 | ds.saveErrorAndSkip(v, i) 301 | return 302 | } 303 | case Uint: 304 | n := ds.Uint() 305 | x = float64(n) 306 | if uint64(x) != n { 307 | ds.saveErrorAndSkip(v, n) 308 | return 309 | } 310 | case Float: 311 | x = ds.Float() 312 | default: 313 | ds.saveErrorAndSkip(v, nil) 314 | return 315 | } 316 | 317 | v.SetFloat(x) 318 | } 319 | 320 | func stringDecoder(ds *decodeState, v reflect.Value) { 321 | var x string 322 | 323 | switch ds.Type() { 324 | case Binary, String: 325 | x = ds.String() 326 | default: 327 | ds.saveErrorAndSkip(v, nil) 328 | return 329 | } 330 | 331 | v.SetString(x) 332 | } 333 | 334 | func byteSliceDecoder(ds *decodeState, v reflect.Value) { 335 | var x []byte 336 | 337 | switch ds.Type() { 338 | case Nil: 339 | // Nothing to do 340 | case Binary, String: 341 | // TODO: check if OK to set? 342 | x = ds.Bytes() 343 | default: 344 | ds.saveErrorAndSkip(v, nil) 345 | return 346 | } 347 | 348 | v.SetBytes(x) 349 | } 350 | 351 | func interfaceDecoder(ds *decodeState, v reflect.Value) { 352 | if ds.Type() == Nil { 353 | v.Set(reflect.Zero(v.Type())) 354 | return 355 | } 356 | 357 | if v.IsNil() { 358 | if v.NumMethod() > 0 { 359 | // We don't know how to make an object of this interface type. 360 | ds.saveErrorAndSkip(v, nil) 361 | return 362 | } 363 | v.Set(reflect.Value(reflect.ValueOf(decodeNoReflect(ds)))) 364 | return 365 | } 366 | 367 | v = v.Elem() 368 | if (v.Kind() == reflect.Ptr || 369 | v.Kind() == reflect.Map || 370 | v.Kind() == reflect.Slice) && !v.IsNil() { 371 | decoderForType(v.Type(), nil)(ds, v) 372 | return 373 | } 374 | 375 | ds.saveErrorAndSkip(v, nil) 376 | } 377 | 378 | type sliceArrayDecoder struct { 379 | elem decodeFunc 380 | } 381 | 382 | func (dec sliceArrayDecoder) decodeArray(ds *decodeState, v reflect.Value) { 383 | n := ds.Len() 384 | for i := 0; i < n; i++ { 385 | ds.unpack() 386 | if i < v.Len() { 387 | dec.elem(ds, v.Index(i)) 388 | } else { 389 | ds.skip() 390 | } 391 | } 392 | 393 | if n < v.Len() { 394 | z := reflect.Zero(v.Type().Elem()) 395 | for i := n; i < v.Len(); i++ { 396 | v.Index(i).Set(z) 397 | } 398 | } 399 | } 400 | 401 | func (b *decodeBuilder) arrayDecoder(t reflect.Type) decodeFunc { 402 | return sliceArrayDecoder{elem: decoderForType(t.Elem(), b)}.decodeArray 403 | } 404 | 405 | func (dec sliceArrayDecoder) decodeSlice(ds *decodeState, v reflect.Value) { 406 | if !v.CanAddr() { 407 | dec.decodeArray(ds, v) 408 | return 409 | } 410 | 411 | n := ds.Len() 412 | if n > v.Cap() { 413 | newv := reflect.MakeSlice(v.Type(), n, n) 414 | reflect.Copy(newv, v) 415 | v.Set(newv) 416 | } else { 417 | v.SetLen(n) 418 | } 419 | 420 | for i := 0; i < n; i++ { 421 | ds.unpack() 422 | dec.elem(ds, v.Index(i)) 423 | } 424 | } 425 | 426 | func (b *decodeBuilder) sliceDecoder(t reflect.Type) decodeFunc { 427 | if t.Elem().Kind() == reflect.Uint8 { 428 | return byteSliceDecoder 429 | } 430 | 431 | return sliceArrayDecoder{elem: decoderForType(t.Elem(), b)}.decodeSlice 432 | } 433 | 434 | type mapDecoder struct { 435 | key decodeFunc 436 | elem decodeFunc 437 | } 438 | 439 | func (dec *mapDecoder) decode(ds *decodeState, v reflect.Value) { 440 | if ds.Type() != MapLen { 441 | ds.saveErrorAndSkip(v, nil) 442 | return 443 | } 444 | 445 | if v.IsNil() { 446 | v.Set(reflect.MakeMap(v.Type())) 447 | } 448 | 449 | n := ds.Len() 450 | for i := 0; i < n; i++ { 451 | ds.unpack() 452 | key := reflect.New(v.Type().Key()).Elem() 453 | dec.key(ds, key) 454 | 455 | ds.unpack() 456 | elem := reflect.New(v.Type().Elem()).Elem() 457 | dec.elem(ds, elem) 458 | 459 | v.SetMapIndex(key, elem) 460 | } 461 | } 462 | 463 | func (b *decodeBuilder) mapDecoder(t reflect.Type) decodeFunc { 464 | dec := &mapDecoder{ 465 | key: decoderForType(t.Key(), b), 466 | elem: decoderForType(t.Elem(), b), 467 | } 468 | return dec.decode 469 | } 470 | 471 | type fieldDec struct { 472 | index []int 473 | f decodeFunc 474 | empty reflect.Value 475 | } 476 | 477 | func (fd *fieldDec) setEmpty(v reflect.Value) { 478 | if !fd.empty.IsValid() { 479 | return 480 | } 481 | 482 | fv := fieldByIndex(v, fd.index) 483 | fv.Set(fd.empty) 484 | } 485 | 486 | type structArrayDecoder []*fieldDec 487 | 488 | func (dec structArrayDecoder) decode(ds *decodeState, v reflect.Value) { 489 | for _, fd := range dec { 490 | fd.setEmpty(v) 491 | } 492 | 493 | if ds.Type() != ArrayLen { 494 | ds.saveErrorAndSkip(v, nil) 495 | return 496 | } 497 | 498 | n := ds.Len() 499 | for i := 0; i < n; i++ { 500 | ds.unpack() 501 | if i < len(dec) { 502 | fd := dec[i] 503 | fv := fieldByIndex(v, fd.index) 504 | fd.f(ds, fv) 505 | } else { 506 | ds.skip() 507 | } 508 | } 509 | } 510 | 511 | type structDecoder map[string]*fieldDec 512 | 513 | func (dec structDecoder) decode(ds *decodeState, v reflect.Value) { 514 | for _, fd := range dec { 515 | fd.setEmpty(v) 516 | } 517 | 518 | if ds.Type() != MapLen { 519 | ds.saveErrorAndSkip(v, nil) 520 | return 521 | } 522 | 523 | n := ds.Len() 524 | for i := 0; i < n; i++ { 525 | // Key 526 | ds.unpack() 527 | 528 | var fd *fieldDec 529 | if ds.Type() == String || ds.Type() == Binary { 530 | fd = dec[string(ds.BytesNoCopy())] 531 | } else { 532 | ds.saveErrorAndSkip(reflect.ValueOf(""), nil) 533 | } 534 | 535 | // Value 536 | ds.unpack() 537 | 538 | if fd != nil { 539 | fv := fieldByIndex(v, fd.index) 540 | fd.f(ds, fv) 541 | } else { 542 | ds.skip() 543 | } 544 | } 545 | } 546 | 547 | func (b *decodeBuilder) structDecoder(t reflect.Type) decodeFunc { 548 | fields, array := fieldsForType(t) 549 | 550 | if array { 551 | var dec structArrayDecoder 552 | for _, field := range fields { 553 | dec = append(dec, &fieldDec{ 554 | index: field.index, 555 | f: decoderForType(field.typ, b), 556 | }) 557 | } 558 | return dec.decode 559 | } 560 | 561 | dec := make(structDecoder) 562 | for _, field := range fields { 563 | dec[field.name] = &fieldDec{ 564 | index: field.index, 565 | f: decoderForType(field.typ, b), 566 | empty: field.empty, 567 | } 568 | } 569 | 570 | return dec.decode 571 | } 572 | 573 | type ptrDecoder struct { 574 | elem decodeFunc 575 | } 576 | 577 | func (dec ptrDecoder) decode(ds *decodeState, v reflect.Value) { 578 | if ds.Type() == Nil { 579 | v.Set(reflect.Zero(v.Type())) 580 | return 581 | } 582 | 583 | if v.IsNil() { 584 | v.Set(reflect.New(v.Type().Elem())) 585 | } 586 | 587 | dec.elem(ds, v.Elem()) 588 | } 589 | 590 | func (b *decodeBuilder) ptrDecoder(t reflect.Type) decodeFunc { 591 | return ptrDecoder{elem: decoderForType(t.Elem(), b)}.decode 592 | } 593 | 594 | var unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() 595 | 596 | func unmarshalDecoder(ds *decodeState, v reflect.Value) { 597 | if ds.Type() == Nil { 598 | v.Set(reflect.Zero(v.Type())) 599 | return 600 | } 601 | 602 | if v.IsNil() { 603 | v.Set(reflect.New(v.Type().Elem())) 604 | } 605 | 606 | m := v.Interface().(Unmarshaler) 607 | err := m.UnmarshalMsgPack(ds.Decoder) 608 | if e, ok := err.(*DecodeConvertError); ok { 609 | if ds.errSaved != nil { 610 | ds.errSaved = e 611 | } 612 | } else if err != nil { 613 | abort(err) 614 | } 615 | } 616 | 617 | type unmarshalAddrDecoder struct{ f decodeFunc } 618 | 619 | func (dec unmarshalAddrDecoder) decode(ds *decodeState, v reflect.Value) { 620 | if !v.CanAddr() { 621 | dec.f(ds, v) 622 | return 623 | } 624 | 625 | unmarshalDecoder(ds, v.Addr()) 626 | } 627 | 628 | type extensionValue struct { 629 | kind int 630 | data []byte 631 | } 632 | 633 | func (ev extensionValue) MarshalMsgPack(e *Encoder) error { 634 | return e.PackExtension(ev.kind, ev.data) 635 | } 636 | 637 | func decodeNoReflect(ds *decodeState) (x any) { 638 | switch ds.Type() { 639 | case Int: 640 | return ds.Int() 641 | case Uint: 642 | return ds.Uint() 643 | case Float: 644 | return ds.Float() 645 | case Bool: 646 | return ds.Bool() 647 | case Nil: 648 | return nil 649 | case String: 650 | return ds.String() 651 | case Binary: 652 | return ds.Bytes() 653 | case ArrayLen: 654 | n := ds.Len() 655 | a := make([]any, n) 656 | for i := 0; i < n; i++ { 657 | ds.unpack() 658 | a[i] = decodeNoReflect(ds) 659 | } 660 | return a 661 | 662 | case MapLen: 663 | n := ds.Len() 664 | m := make(map[string]any) 665 | for i := 0; i < n; i++ { 666 | ds.unpack() 667 | 668 | if ds.Type() != String && ds.Type() != Binary { 669 | ds.saveErrorAndSkip(reflect.ValueOf(""), nil) 670 | ds.unpack() 671 | ds.skip() 672 | continue 673 | } 674 | 675 | key := ds.String() 676 | ds.unpack() 677 | m[key] = decodeNoReflect(ds) 678 | } 679 | return m 680 | 681 | case Extension: 682 | if f := ds.extensions[ds.Extension()]; f != nil { 683 | v, err := f(ds.Bytes()) 684 | if e, ok := err.(*DecodeConvertError); ok { 685 | if ds.errSaved != nil { 686 | ds.errSaved = e 687 | } 688 | } else if err != nil { 689 | abort(err) 690 | } 691 | return v 692 | } 693 | return extensionValue{ds.Extension(), ds.Bytes()} 694 | 695 | default: 696 | return nil 697 | } 698 | } 699 | -------------------------------------------------------------------------------- /msgpack/rpc/rpc.go: -------------------------------------------------------------------------------- 1 | // Package rpc implements MessagePack RPC. 2 | package rpc 3 | 4 | import ( 5 | "bufio" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "reflect" 10 | "sync" 11 | 12 | "github.com/neovim/go-client/msgpack" 13 | ) 14 | 15 | // kind represents a MessagePack RPC message kind. 16 | type kind int 17 | 18 | // list of kind. 19 | const ( 20 | requestMessage kind = 0 21 | replyMessage kind = 1 22 | notificationMessage kind = 2 23 | ) 24 | 25 | // state represents a MessagePack RPC state. 26 | type state int 27 | 28 | // list of state. 29 | const ( 30 | stateInit state = iota 31 | stateClosed 32 | ) 33 | 34 | var ( 35 | // ErrClosed session closed error. 36 | ErrClosed = errors.New("msgpack/rpc: session closed") 37 | 38 | // ErrInternal msgpack-rpc internal error. 39 | ErrInternal = errors.New("msgpack/rpc: internal error") 40 | 41 | // ErrHandlerNotFunction handler type is not a function error. 42 | ErrHandlerNotFunction = errors.New("msgpack/rpc: handler not a function") 43 | 44 | // ErrInvalidHandlerReturn invalid handler function return type error. 45 | ErrInvalidHandlerReturn = errors.New("msgpack/rpc: handler return must be (), (error) or (valueType, error)") 46 | 47 | // ErrInvalidArgument invalid argument error. 48 | ErrInvalidArgument = errors.New("msgpack/rpc: invalid argument") 49 | ) 50 | 51 | // Error represents a MessagePack RPC error. 52 | type Error struct { 53 | Value any 54 | } 55 | 56 | // Error implements the error interface. 57 | func (e Error) Error() string { 58 | return fmt.Sprintf("%v", e.Value) 59 | } 60 | 61 | // Call represents a MessagePack RPC call. 62 | type Call struct { 63 | Args any 64 | Reply any 65 | Err error 66 | Done chan *Call 67 | Method string 68 | } 69 | 70 | func (c *Call) done(e *Endpoint, err error) { 71 | c.Err = err 72 | select { 73 | case c.Done <- c: 74 | // ok 75 | default: 76 | e.logf("msgpack/rpc: done channel over capacity for method %s", c.Method) 77 | } 78 | } 79 | 80 | type handler struct { 81 | fn reflect.Value 82 | args []reflect.Value 83 | } 84 | 85 | type notification struct { 86 | call func([]reflect.Value) []reflect.Value 87 | next *notification 88 | method string 89 | args []reflect.Value 90 | } 91 | 92 | // Endpoint represents a MessagePack RPC peer. 93 | type Endpoint struct { 94 | err error 95 | logf func(fmt string, args ...any) 96 | 97 | done chan struct{} 98 | closer io.Closer 99 | bw *bufio.Writer 100 | enc *msgpack.Encoder 101 | dec *msgpack.Decoder 102 | 103 | handlers map[string]*handler 104 | pending map[uint64]*Call 105 | notificationsCond *sync.Cond 106 | 107 | arg reflect.Value 108 | notifications []*notification 109 | state state 110 | id uint64 111 | 112 | mu sync.Mutex 113 | handlersMu sync.RWMutex 114 | encMu sync.Mutex 115 | notificationsMu sync.Mutex 116 | } 117 | 118 | // Option is a configures a Endpoint. 119 | type Option struct{ f func(*Endpoint) } 120 | 121 | // WithExtensions configures Endpoint to define application-specific types. 122 | func WithExtensions(extensions msgpack.ExtensionMap) Option { 123 | return Option{func(e *Endpoint) { 124 | e.dec.SetExtensions(extensions) 125 | }} 126 | } 127 | 128 | // WithLogf sets the log function to Endpoint. 129 | func WithLogf(f func(fmt string, args ...any)) Option { 130 | return Option{func(e *Endpoint) { 131 | e.logf = f 132 | }} 133 | } 134 | 135 | // NewEndpoint returns a new endpoint with the specified options. 136 | func NewEndpoint(r io.Reader, w io.Writer, c io.Closer, options ...Option) (*Endpoint, error) { 137 | bw := bufio.NewWriter(w) 138 | e := &Endpoint{ 139 | done: make(chan struct{}), 140 | handlers: make(map[string]*handler), 141 | pending: make(map[uint64]*Call), 142 | closer: c, 143 | bw: bw, 144 | enc: msgpack.NewEncoder(bw), 145 | dec: msgpack.NewDecoder(r), 146 | } 147 | for _, option := range options { 148 | option.f(e) 149 | } 150 | return e, nil 151 | 152 | } 153 | 154 | func (e *Endpoint) decodeUint(what string) (uint64, error) { 155 | if err := e.dec.Unpack(); err != nil { 156 | return 0, err 157 | } 158 | t := e.dec.Type() 159 | if t != msgpack.Uint && t != msgpack.Int { 160 | return 0, fmt.Errorf("msgpack/rpc: error decoding %s, found %s", what, e.dec.Type()) 161 | } 162 | return e.dec.Uint(), nil 163 | } 164 | 165 | func (e *Endpoint) decodeString(what string) (string, error) { 166 | if err := e.dec.Unpack(); err != nil { 167 | return "", err 168 | } 169 | if e.dec.Type() != msgpack.String { 170 | return "", fmt.Errorf("msgpack/rpc: error decoding %s, found %s", what, e.dec.Type()) 171 | } 172 | return e.dec.String(), nil 173 | } 174 | 175 | func (e *Endpoint) skip(n int) error { 176 | for i := 0; i < n; i++ { 177 | if err := e.dec.Unpack(); err != nil { 178 | return err 179 | } 180 | if err := e.dec.Skip(); err != nil { 181 | return err 182 | } 183 | } 184 | return nil 185 | } 186 | 187 | // Serve serves incoming requests. Serve blocks until the peer disconnects or 188 | // there is an error. 189 | func (e *Endpoint) Serve() error { 190 | e.notificationsCond = sync.NewCond(&e.notificationsMu) 191 | defer e.enqueNotification(nil) 192 | go e.runNotifications() 193 | 194 | for { 195 | if err := e.dec.Unpack(); err != nil { 196 | if err == io.EOF { 197 | err = nil 198 | } 199 | return e.close(err) 200 | } 201 | 202 | messageLen := e.dec.Len() 203 | if messageLen < 1 { 204 | return e.close(fmt.Errorf("msgpack/rpc: invalid message length %d", messageLen)) 205 | } 206 | 207 | messageType, err := e.decodeUint("message type") 208 | if err != nil { 209 | return e.close(err) 210 | } 211 | 212 | switch kind(messageType) { 213 | case requestMessage: 214 | err = e.handleRequest(messageLen) 215 | case replyMessage: 216 | err = e.handleReply(messageLen) 217 | case notificationMessage: 218 | err = e.handleNotification(messageLen) 219 | default: 220 | err = fmt.Errorf("msgpack/rpc: unknown message type %d", messageType) 221 | } 222 | if err != nil { 223 | return e.close(err) 224 | } 225 | } 226 | } 227 | 228 | func (e *Endpoint) close(err error) error { 229 | e.mu.Lock() 230 | defer e.mu.Unlock() 231 | if e.state == stateClosed { 232 | return e.err 233 | } 234 | e.state = stateClosed 235 | e.err = err 236 | for _, call := range e.pending { 237 | call.done(e, ErrClosed) 238 | } 239 | e.pending = nil 240 | err = e.closer.Close() 241 | if e.err == nil { 242 | e.err = err 243 | } 244 | return e.err 245 | } 246 | 247 | // Close releases the resources used by endpoint. 248 | func (e *Endpoint) Close() error { 249 | return e.close(nil) 250 | } 251 | 252 | var errorType = reflect.ValueOf(new(error)).Elem().Type() 253 | 254 | // Register registers handler fn for the specified method name. 255 | // 256 | // When servicing a call, the arguments to fn are the values in args followed 257 | // by the values passed from the peer. 258 | func (e *Endpoint) Register(method string, fn any, args ...any) error { 259 | v := reflect.ValueOf(fn) 260 | t := v.Type() 261 | if t.Kind() != reflect.Func { 262 | return ErrHandlerNotFunction 263 | } 264 | if t.NumIn() < len(args) { 265 | return fmt.Errorf("msgpack/rpc: handler must have at least %d args", len(args)) 266 | } 267 | 268 | h := &handler{fn: v, args: make([]reflect.Value, len(args))} 269 | 270 | for i, arg := range args { 271 | if arg == nil { 272 | t := t.In(i) 273 | switch t.Kind() { 274 | case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: 275 | h.args[i] = reflect.New(t).Elem() 276 | default: 277 | return fmt.Errorf("msgpack/rpc: handler arg %d must be interface, pointer, map or slice", i) 278 | } 279 | } else { 280 | h.args[i] = reflect.ValueOf(arg) 281 | if t.In(i) != h.args[i].Type() { 282 | return fmt.Errorf("msgpack/rpc: handler arg %d must be type %T", i, arg) 283 | } 284 | } 285 | } 286 | 287 | if t.NumOut() > 2 || (t.NumOut() > 0 && t.Out(t.NumOut()-1) != errorType) { 288 | return ErrInvalidHandlerReturn 289 | } 290 | 291 | e.handlersMu.Lock() 292 | e.handlers[method] = h 293 | e.handlersMu.Unlock() 294 | return nil 295 | } 296 | 297 | // Call invokes the target method and waits for a response. 298 | func (e *Endpoint) Call(method string, reply any, args ...any) error { 299 | c := <-e.Go(method, make(chan *Call, 1), reply, args...).Done 300 | return c.Err 301 | } 302 | 303 | // Go append method call to queue and returns the new Call. 304 | func (e *Endpoint) Go(method string, done chan *Call, reply any, args ...any) *Call { 305 | if args == nil { 306 | args = []any{} 307 | } 308 | 309 | if done == nil { 310 | done = make(chan *Call, 1) 311 | } else if cap(done) == 0 { 312 | panic("unbuffered done channel") 313 | } 314 | 315 | call := &Call{ 316 | Method: method, 317 | Args: args, 318 | Reply: reply, 319 | Done: done, 320 | } 321 | 322 | e.mu.Lock() 323 | if e.state == stateClosed { 324 | call.done(e, ErrClosed) 325 | e.mu.Unlock() 326 | return call 327 | } 328 | e.id = (e.id + 1) & 0x7fffffff 329 | id := e.id 330 | e.pending[id] = call 331 | e.mu.Unlock() 332 | 333 | message := &struct { 334 | Kind kind `msgpack:",array"` 335 | ID uint64 336 | Method string 337 | Args []any 338 | }{ 339 | requestMessage, 340 | id, 341 | method, 342 | args, 343 | } 344 | 345 | e.encMu.Lock() 346 | err := e.enc.Encode(message) 347 | if e := e.bw.Flush(); err == nil { 348 | err = e 349 | } 350 | e.encMu.Unlock() 351 | 352 | if err != nil { 353 | e.mu.Lock() 354 | if _, pending := e.pending[id]; pending { 355 | delete(e.pending, id) 356 | call.done(e, err) 357 | } 358 | e.mu.Unlock() 359 | e.close(fmt.Errorf("msgpack/rpc: error encoding %s: %w", call.Method, err)) 360 | } 361 | 362 | return call 363 | } 364 | 365 | // Notify invokes the target method with non-blocking. 366 | func (e *Endpoint) Notify(method string, args ...any) error { 367 | if args == nil { 368 | args = []any{} 369 | } 370 | 371 | message := &struct { 372 | Kind kind `msgpack:",array"` 373 | Method string 374 | Args []any 375 | }{ 376 | notificationMessage, 377 | method, 378 | args, 379 | } 380 | 381 | e.encMu.Lock() 382 | err := e.enc.Encode(message) 383 | if e := e.bw.Flush(); err == nil { 384 | err = e 385 | } 386 | e.encMu.Unlock() 387 | if err != nil { 388 | e.close(fmt.Errorf("msgpack/rpc: error encoding %s: %w", method, err)) 389 | } 390 | return err 391 | } 392 | 393 | func (e *Endpoint) createCall(h *handler) (func([]reflect.Value) []reflect.Value, []reflect.Value, error) { 394 | t := h.fn.Type() 395 | args := make([]reflect.Value, t.NumIn()) 396 | for i := range h.args { 397 | args[i] = h.args[i] 398 | } 399 | if err := e.dec.Unpack(); err != nil { 400 | return nil, nil, err 401 | } 402 | if e.dec.Type() != msgpack.ArrayLen { 403 | e.dec.Skip() 404 | return nil, nil, fmt.Errorf("msgpack/rpc: expected args array, found %s", e.dec.Type()) 405 | } 406 | 407 | // Decode plain arguments. 408 | 409 | var savedErr error 410 | 411 | srcIndex := 0 412 | srcLen := e.dec.Len() 413 | 414 | dstIndex := len(h.args) 415 | dstLen := t.NumIn() 416 | if t.IsVariadic() { 417 | dstLen-- 418 | } 419 | 420 | for dstIndex < dstLen { 421 | v := reflect.New(t.In(dstIndex)) 422 | args[dstIndex] = v.Elem() 423 | dstIndex++ 424 | if srcIndex < srcLen { 425 | srcIndex++ 426 | err := e.dec.Decode(v.Interface()) 427 | if _, ok := err.(*msgpack.DecodeConvertError); ok { 428 | if savedErr == nil { 429 | savedErr = err 430 | } 431 | } else if err != nil { 432 | return nil, nil, err 433 | } 434 | } 435 | } 436 | 437 | if !t.IsVariadic() { 438 | // Skip extra arguments 439 | 440 | n := srcLen - srcIndex 441 | if n > 0 { 442 | err := e.skip(n) 443 | if err != nil { 444 | return nil, nil, err 445 | } 446 | } 447 | 448 | return h.fn.Call, args, savedErr 449 | } 450 | 451 | if srcIndex >= srcLen { 452 | args[dstIndex] = reflect.Zero(t.In(dstIndex)) 453 | return h.fn.CallSlice, args, savedErr 454 | } 455 | 456 | n := srcLen - srcIndex 457 | v := reflect.MakeSlice(t.In(dstIndex), n, n) 458 | args[dstIndex] = v 459 | 460 | for i := 0; i < n; i++ { 461 | err := e.dec.Decode(v.Index(i).Addr().Interface()) 462 | if _, ok := err.(*msgpack.DecodeConvertError); ok { 463 | if savedErr == nil { 464 | savedErr = err 465 | } 466 | } else if err != nil { 467 | return nil, nil, err 468 | } 469 | } 470 | 471 | return h.fn.CallSlice, args, nil 472 | } 473 | 474 | func (e *Endpoint) reply(id uint64, replyErr error, reply any) error { 475 | e.encMu.Lock() 476 | defer e.encMu.Unlock() 477 | 478 | err := e.enc.PackArrayLen(4) 479 | if err != nil { 480 | return err 481 | } 482 | 483 | err = e.enc.PackUint(uint64(replyMessage)) 484 | if err != nil { 485 | return err 486 | } 487 | 488 | err = e.enc.PackUint(id) 489 | if err != nil { 490 | return err 491 | } 492 | 493 | if replyErr == nil { 494 | err = e.enc.PackNil() 495 | } else if ee, ok := replyErr.(Error); ok { 496 | err = e.enc.Encode(ee.Value) 497 | } else if ee, ok := replyErr.(msgpack.Marshaler); ok { 498 | err = ee.MarshalMsgPack(e.enc) 499 | } else { 500 | err = e.enc.PackString(replyErr.Error()) 501 | } 502 | if err != nil { 503 | return err 504 | } 505 | 506 | err = e.enc.Encode(reply) 507 | if err != nil { 508 | return err 509 | } 510 | return e.bw.Flush() 511 | } 512 | 513 | func (e *Endpoint) handleRequest(messageLen int) error { 514 | if messageLen != 4 { 515 | // messageType, id, method, args 516 | return fmt.Errorf("msgpack/rpc: invalid request message length %d", messageLen) 517 | } 518 | 519 | id, err := e.decodeUint("request id") 520 | if err != nil { 521 | return err 522 | } 523 | 524 | method, err := e.decodeString("service method name") 525 | if err != nil { 526 | return err 527 | } 528 | 529 | e.handlersMu.RLock() 530 | h, ok := e.handlers[method] 531 | e.handlersMu.RUnlock() 532 | 533 | if !ok { 534 | if err := e.skip(1); err != nil { 535 | return err 536 | } 537 | e.logf("msgpack/rpc: request service method %s not found", method) 538 | return e.reply(id, fmt.Errorf("unknown request method: %s", method), nil) 539 | } 540 | 541 | call, args, err := e.createCall(h) 542 | if _, ok := err.(*msgpack.DecodeConvertError); ok { 543 | e.logf("msgpack/rpc: %s: %v", method, err) 544 | return e.reply(id, ErrInvalidArgument, nil) 545 | } else if err != nil { 546 | return err 547 | } 548 | 549 | go func() { 550 | out := call(args) 551 | var replyErr error 552 | var replyVal any 553 | switch h.fn.Type().NumOut() { 554 | case 1: 555 | replyErr, _ = out[0].Interface().(error) 556 | case 2: 557 | replyVal = out[0].Interface() 558 | replyErr, _ = out[1].Interface().(error) 559 | } 560 | if err := e.reply(id, replyErr, replyVal); err != nil { 561 | e.close(err) 562 | } 563 | }() 564 | 565 | return nil 566 | } 567 | 568 | func (e *Endpoint) handleReply(messageLen int) error { 569 | if messageLen != 4 { 570 | // messageType, id, error, reply 571 | return fmt.Errorf("msgpack/rpc: invalid reply message length %d", messageLen) 572 | } 573 | 574 | id, err := e.decodeUint("response id") 575 | if err != nil { 576 | return err 577 | } 578 | 579 | e.mu.Lock() 580 | call := e.pending[id] 581 | delete(e.pending, id) 582 | e.mu.Unlock() 583 | 584 | if call == nil { 585 | e.logf("msgpack/rpc: no pending call for reply %d", id) 586 | return e.skip(2) 587 | } 588 | 589 | var errorValue any 590 | if err := e.dec.Decode(&errorValue); err != nil { 591 | call.done(e, ErrInternal) 592 | return fmt.Errorf("msgpack/rpc: error decoding error value: %w", err) 593 | } 594 | 595 | if errorValue != nil { 596 | err := e.skip(1) 597 | call.done(e, Error{errorValue}) 598 | return err 599 | } 600 | 601 | if call.Reply == nil { 602 | err = e.skip(1) 603 | } else { 604 | err = e.dec.Decode(call.Reply) 605 | if cvterr, ok := err.(*msgpack.DecodeConvertError); ok { 606 | call.done(e, cvterr) 607 | return nil 608 | } 609 | } 610 | 611 | if err != nil { 612 | call.done(e, ErrInternal) 613 | return fmt.Errorf("msgpack/rpc: error decoding reply: %w", err) 614 | } 615 | 616 | call.done(e, nil) 617 | return nil 618 | } 619 | 620 | func (e *Endpoint) handleNotification(messageLen int) error { 621 | // messageType, method, args 622 | if messageLen != 3 { 623 | return fmt.Errorf("msgpack/rpc: invalid notification message length %d", messageLen) 624 | } 625 | 626 | method, err := e.decodeString("service method name") 627 | if err != nil { 628 | return err 629 | } 630 | 631 | e.handlersMu.RLock() 632 | h, ok := e.handlers[method] 633 | e.handlersMu.RUnlock() 634 | 635 | if !ok { 636 | e.logf("msgpack/rpc: notification service method %s not found", method) 637 | return e.skip(1) 638 | } 639 | 640 | call, args, err := e.createCall(h) 641 | if err != nil { 642 | return err 643 | } 644 | 645 | e.enqueNotification(¬ification{call: call, args: args, method: method}) 646 | return nil 647 | } 648 | 649 | func (e *Endpoint) enqueNotification(n *notification) { 650 | e.notificationsMu.Lock() 651 | e.notifications = append(e.notifications, n) 652 | e.notificationsCond.Signal() 653 | e.notificationsMu.Unlock() 654 | } 655 | 656 | func (e *Endpoint) dequeueNotifications() []*notification { 657 | e.notificationsMu.Lock() 658 | for e.notifications == nil { 659 | e.notificationsCond.Wait() 660 | } 661 | notifications := e.notifications 662 | e.notifications = nil 663 | e.notificationsMu.Unlock() 664 | return notifications 665 | } 666 | 667 | // runNotifications runs notifications in a single goroutine to ensure that the 668 | // notifications are processed in order by the application. 669 | func (e *Endpoint) runNotifications() { 670 | for { 671 | notifications := e.dequeueNotifications() 672 | for _, n := range notifications { 673 | if n == nil { 674 | // Serve() enqueues nil on return 675 | return 676 | } 677 | out := n.call(n.args) 678 | if len(out) > 0 { 679 | replyErr, _ := out[len(out)-1].Interface().(error) 680 | if replyErr != nil { 681 | e.logf("msgpack/rpc: service method %s returned %v", n.method, replyErr) 682 | } 683 | } 684 | } 685 | } 686 | } 687 | -------------------------------------------------------------------------------- /nvim/api_tool.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | // Command api_tool generates api.go from api_def.go. The command also has 5 | // an option to compare api_def.go to Nvim's current API meta data. 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "encoding/json" 11 | "flag" 12 | "fmt" 13 | "go/ast" 14 | "go/format" 15 | "go/parser" 16 | "go/token" 17 | "log" 18 | "os" 19 | "os/exec" 20 | "sort" 21 | "strconv" 22 | "strings" 23 | "text/template" 24 | 25 | "github.com/neovim/go-client/msgpack" 26 | ) 27 | 28 | // APIInfo represents the output from nvim --api-info 29 | type APIInfo struct { 30 | ErrorTypes map[string]ErrorType `msgpack:"error_types"` 31 | Types map[string]ExtensionType `msgpack:"types"` 32 | Functions []*Function `msgpack:"functions"` 33 | UIOptions UIOptions `msgpack:"ui_options"` 34 | Version Version `msgpack:"version"` 35 | } 36 | 37 | type ErrorType struct { 38 | ID int `msgpack:"id"` 39 | } 40 | 41 | type ExtensionType struct { 42 | ID int `msgpack:"id"` 43 | Doc string `msgpack:"-"` 44 | } 45 | 46 | type Function struct { 47 | Name string `msgpack:"name"` 48 | Parameters []*Field `msgpack:"parameters"` 49 | ReturnName string `msgpack:"_"` 50 | ReturnType string `msgpack:"return_type"` 51 | DeprecatedSince int `msgpack:"deprecated_since"` 52 | Doc string `msgpack:"-"` 53 | GoName string `msgpack:"-"` 54 | ReturnPtr bool `msgpack:"-"` 55 | } 56 | 57 | type Field struct { 58 | Type string `msgpack:",array"` 59 | Name string 60 | } 61 | 62 | type UIOptions []string 63 | 64 | type Version struct { 65 | APICompatible int `msgpack:"api_compatible"` 66 | APILevel int `msgpack:"api_level"` 67 | APIPrerelease bool `msgpack:"api_prerelease"` 68 | Major int `msgpack:"major"` 69 | Minor int `msgpack:"minor"` 70 | Patch int `msgpack:"patch"` 71 | } 72 | 73 | var errorTypes = map[string]ErrorType{ 74 | "Exception": { 75 | ID: 0, 76 | }, 77 | "Validation": { 78 | ID: 1, 79 | }, 80 | } 81 | 82 | var extensionTypes = map[string]ExtensionType{ 83 | "Buffer": { 84 | ID: 0, 85 | Doc: `// Buffer represents a Nvim buffer.`, 86 | }, 87 | "Window": { 88 | ID: 1, 89 | Doc: `// Window represents a Nvim window.`, 90 | }, 91 | "Tabpage": { 92 | ID: 2, 93 | Doc: `// Tabpage represents a Nvim tabpage.`, 94 | }, 95 | } 96 | 97 | func formatNode(fset *token.FileSet, node any) string { 98 | var buf strings.Builder 99 | if err := format.Node(&buf, fset, node); err != nil { 100 | panic(err) 101 | } 102 | return buf.String() 103 | } 104 | 105 | func parseFields(fset *token.FileSet, fl *ast.FieldList) []*Field { 106 | if fl == nil { 107 | return nil 108 | } 109 | var fields []*Field 110 | for _, f := range fl.List { 111 | typ := formatNode(fset, f.Type) 112 | if len(f.Names) == 0 { 113 | fields = append(fields, &Field{Type: typ}) 114 | } else { 115 | for _, id := range f.Names { 116 | fields = append(fields, &Field{Name: id.Name, Type: typ}) 117 | } 118 | } 119 | } 120 | return fields 121 | } 122 | 123 | // parseAPIDef parses the file api_def.go. 124 | func parseAPIDef() ([]*Function, []*Function, error) { 125 | fset := token.NewFileSet() 126 | file, err := parser.ParseFile(fset, "api_def.go", nil, parser.ParseComments) 127 | if err != nil { 128 | return nil, nil, err 129 | } 130 | 131 | var functions []*Function 132 | var deprecated []*Function 133 | 134 | for _, decl := range file.Decls { 135 | fdecl, ok := decl.(*ast.FuncDecl) 136 | if !ok { 137 | continue 138 | } 139 | var doc []byte 140 | if cg := fdecl.Doc; cg != nil { 141 | for i, c := range cg.List { 142 | if i > 0 { 143 | doc = append(doc, '\n') 144 | } 145 | doc = append(doc, c.Text...) 146 | } 147 | } 148 | m := &Function{ 149 | GoName: fdecl.Name.Name, 150 | Doc: string(doc), 151 | Parameters: parseFields(fset, fdecl.Type.Params), 152 | } 153 | 154 | fields := parseFields(fset, fdecl.Type.Results) 155 | if len(fields) > 1 { 156 | return nil, nil, fmt.Errorf("%s: more than one result for %s", fset.Position(fdecl.Pos()), m.Name) 157 | } 158 | 159 | if len(fields) == 1 { 160 | m.ReturnName = fields[0].Name 161 | m.ReturnType = fields[0].Type 162 | } 163 | for _, n := range fdecl.Body.List { 164 | if expr, ok := n.(*ast.ExprStmt); ok { 165 | if call, ok := expr.X.(*ast.CallExpr); ok { 166 | if id, ok := call.Fun.(*ast.Ident); ok { 167 | switch id.Name { 168 | case "name": 169 | if len(call.Args) == 1 { 170 | if id, ok := call.Args[0].(*ast.Ident); ok { 171 | m.Name = id.Name 172 | } 173 | } 174 | case "deprecatedSince": 175 | if lit, ok := call.Args[0].(*ast.BasicLit); ok && lit.Kind == token.INT { 176 | m.DeprecatedSince, _ = strconv.Atoi(lit.Value) 177 | } 178 | case "returnPtr": 179 | m.ReturnPtr = true 180 | } 181 | } 182 | } 183 | } 184 | } 185 | 186 | if m.Name == "" { 187 | return nil, nil, fmt.Errorf("%s: service method not specified for %s", fset.Position(fdecl.Pos()), m.Name) 188 | } 189 | 190 | if m.DeprecatedSince > 0 { 191 | deprecated = append(deprecated, m) 192 | continue 193 | } 194 | functions = append(functions, m) 195 | } 196 | 197 | return functions, deprecated, nil 198 | } 199 | 200 | const genTemplate = ` 201 | 202 | {{define "doc" -}} 203 | {{.Doc}} 204 | // 205 | // See: [{{.Name}}()] 206 | // 207 | // [{{.Name}}()]: https://neovim.io/doc/user/api.html#{{.Name}}() 208 | {{- end}} 209 | 210 | {{range .Functions}} 211 | {{if eq "any" .ReturnType}} 212 | {{template "doc" .}} 213 | func (v *Nvim) {{.GoName}}({{range .Parameters}}{{.Name}} {{.Type}},{{end}} result any) error { 214 | return v.call("{{.Name}}", result, {{range .Parameters}}{{.Name}},{{end}}) 215 | } 216 | 217 | {{template "doc" .}} 218 | func (b *Batch) {{.GoName}}({{range .Parameters}}{{.Name}} {{.Type}},{{end}} result any) { 219 | b.call("{{.Name}}", &result, {{range .Parameters}}{{.Name}},{{end}}) 220 | } 221 | 222 | {{else if and .ReturnName .ReturnPtr}} 223 | {{template "doc" .}} 224 | func (v *Nvim) {{.GoName}}({{range .Parameters}}{{.Name}} {{.Type}},{{end}}) ({{.ReturnName}} *{{.ReturnType}}, err error) { 225 | var result {{.ReturnType}} 226 | err = v.call("{{.Name}}", &result, {{range .Parameters}}{{.Name}},{{end}}) 227 | return &result, err 228 | } 229 | {{template "doc" .}} 230 | func (b *Batch) {{.GoName}}({{range .Parameters}}{{.Name}} {{.Type}},{{end}} {{.ReturnName}} *{{.ReturnType}}) { 231 | b.call("{{.Name}}", {{.ReturnName}}, {{range .Parameters}}{{.Name}},{{end}}) 232 | } 233 | 234 | {{else if and (.ReturnName) (not .ReturnPtr)}} 235 | {{template "doc" .}} 236 | func (v *Nvim) {{.GoName}}({{range .Parameters}}{{.Name}} {{.Type}},{{end}}) ({{.ReturnName}} {{.ReturnType}}, err error) { 237 | err = v.call("{{.Name}}", &{{.ReturnName}}, {{range .Parameters}}{{.Name}},{{end}}) 238 | return {{.ReturnName}}, err 239 | } 240 | {{template "doc" .}} 241 | func (b *Batch) {{.GoName}}({{range .Parameters}}{{.Name}} {{.Type}},{{end}} {{.ReturnName}} *{{.ReturnType}}) { 242 | b.call("{{.Name}}", {{.ReturnName}}, {{range .Parameters}}{{.Name}},{{end}}) 243 | } 244 | {{else if .ReturnType}} 245 | {{template "doc" .}} 246 | func (v *Nvim) {{.GoName}}({{range .Parameters}}{{.Name}} {{.Type}},{{end}}) ({{if .ReturnPtr}}*{{end}}{{.ReturnType}}, error) { 247 | var result {{.ReturnType}} 248 | err := v.call("{{.Name}}", &result, {{range .Parameters}}{{.Name}},{{end}}) 249 | return {{if .ReturnPtr}}&{{end}}result, err 250 | } 251 | {{template "doc" .}} 252 | func (b *Batch) {{.GoName}}({{range .Parameters}}{{.Name}} {{.Type}},{{end}} result *{{.ReturnType}}) { 253 | b.call("{{.Name}}", result, {{range .Parameters}}{{.Name}},{{end}}) 254 | } 255 | {{else}} 256 | {{template "doc" .}} 257 | func (v *Nvim) {{.GoName}}({{range .Parameters}}{{.Name}} {{.Type}},{{end}}) error { 258 | return v.call("{{.Name}}", nil, {{range .Parameters}}{{.Name}},{{end}}) 259 | } 260 | {{template "doc" .}} 261 | func (b *Batch) {{.GoName}}({{range .Parameters}}{{.Name}} {{.Type}},{{end}}) { 262 | b.call("{{.Name}}", nil, {{range .Parameters}}{{.Name}},{{end}}) 263 | } 264 | {{end}} 265 | {{end}} 266 | ` 267 | 268 | var implementationTemplate = template.Must(template.New("implementation").Funcs(template.FuncMap{ 269 | "lower": strings.ToLower, 270 | }).Parse(`// Code generated by running "go generate" in github.com/neovim/go-client/nvim. DO NOT EDIT. 271 | 272 | package nvim 273 | 274 | import ( 275 | "fmt" 276 | 277 | "github.com/neovim/go-client/msgpack" 278 | "github.com/neovim/go-client/msgpack/rpc" 279 | ) 280 | 281 | const ( 282 | {{- range $name, $type := .ErrorTypes}} 283 | {{- lower $name}}Error = {{$type.ID}} 284 | {{ end -}} 285 | ) 286 | 287 | func withExtensions() rpc.Option { 288 | return rpc.WithExtensions(msgpack.ExtensionMap{ 289 | {{- range $name, $type := .Types}} 290 | {{$type.ID}}: func(p []byte) (any, error) { 291 | x, err := decodeExt(p) 292 | return {{$name}}(x), err 293 | }, 294 | {{end -}} 295 | }) 296 | } 297 | 298 | {{range $name, $type := .Types}} 299 | {{$type.Doc}} 300 | type {{$name}} int 301 | 302 | // MarshalMsgPack implements msgpack.Marshaler. 303 | func (x {{$name}}) MarshalMsgPack(enc *msgpack.Encoder) error { 304 | return enc.PackExtension({{$type.ID}}, encodeExt(int(x))) 305 | } 306 | 307 | // UnmarshalMsgPack implements msgpack.Unmarshaler. 308 | func (x *{{$name}}) UnmarshalMsgPack(dec *msgpack.Decoder) error { 309 | n, err := unmarshalExt(dec, {{$type.ID}}, x) 310 | *x = {{$name}}(n) 311 | return err 312 | } 313 | 314 | // String returns a string representation of the {{$name}}. 315 | func (x {{$name}}) String() string { 316 | return fmt.Sprintf("{{$name}}:%d", int(x)) 317 | } 318 | {{end}} 319 | ` + genTemplate)) 320 | 321 | var deprecatedTemplate = template.Must(template.New("deprecated").Funcs(template.FuncMap{ 322 | "lower": strings.ToLower, 323 | }).Parse(`// Code generated by running "go generate" in github.com/neovim/go-client/nvim. DO NOT EDIT. 324 | 325 | package nvim 326 | 327 | // EmbedOptions specifies options for starting an embedded instance of Nvim. 328 | // 329 | // Deprecated: Use ChildProcessOption instead. 330 | type EmbedOptions struct { 331 | // Logf log function for rpc.WithLogf. 332 | Logf func(string, ...any) 333 | 334 | // Dir specifies the working directory of the command. The working 335 | // directory in the current process is used if Dir is "". 336 | Dir string 337 | 338 | // Path is the path of the command to run. If Path = "", then 339 | // StartEmbeddedNvim searches for "nvim" on $PATH. 340 | Path string 341 | 342 | // Args specifies the command line arguments. Do not include the program 343 | // name (the first argument) or the --embed option. 344 | Args []string 345 | 346 | // Env specifies the environment of the Nvim process. The current process 347 | // environment is used if Env is nil. 348 | Env []string 349 | } 350 | 351 | // NewEmbedded starts an embedded instance of Nvim using the specified options. 352 | // 353 | // The application must call Serve() to handle RPC requests and responses. 354 | // 355 | // Deprecated: Use NewChildProcess instead. 356 | func NewEmbedded(options *EmbedOptions) (*Nvim, error) { 357 | if options == nil { 358 | options = &EmbedOptions{} 359 | } 360 | path := options.Path 361 | if path == "" { 362 | path = "nvim" 363 | } 364 | 365 | return NewChildProcess( 366 | ChildProcessArgs(append([]string{"--embed"}, options.Args...)...), 367 | ChildProcessCommand(path), 368 | ChildProcessEnv(options.Env), 369 | ChildProcessDir(options.Dir), 370 | ChildProcessServe(false)) 371 | } 372 | 373 | // ExecuteLua executes a Lua block. 374 | // 375 | // Deprecated: Use ExecLua instead. 376 | func (v *Nvim) ExecuteLua(code string, result any, args ...any) error { 377 | if args == nil { 378 | args = emptyArgs 379 | } 380 | return v.call("nvim_execute_lua", result, code, args) 381 | } 382 | 383 | // ExecuteLua executes a Lua block. 384 | // 385 | // Deprecated: Use ExecLua instead. 386 | func (b *Batch) ExecuteLua(code string, result any, args ...any) { 387 | if args == nil { 388 | args = emptyArgs 389 | } 390 | b.call("nvim_execute_lua", result, code, args) 391 | } 392 | ` + genTemplate)) 393 | 394 | func printImplementation(functions []*Function, tmpl *template.Template, outFile string) error { 395 | var buf bytes.Buffer 396 | if err := tmpl.Execute(&buf, &APIInfo{ 397 | Functions: functions, 398 | Types: extensionTypes, 399 | ErrorTypes: errorTypes, 400 | }); err != nil { 401 | return fmt.Errorf("falied to Execute implementationTemplate: %w", err) 402 | } 403 | 404 | out, err := format.Source(buf.Bytes()) 405 | if err != nil { 406 | for i, p := range bytes.Split(buf.Bytes(), []byte("\n")) { 407 | fmt.Fprintf(os.Stderr, "%d: %s\n", i+1, p) 408 | } 409 | return fmt.Errorf("error formating source: %w", err) 410 | } 411 | 412 | if outFile != "" { 413 | return os.WriteFile(outFile, out, 0666) 414 | } 415 | _, err = os.Stdout.Write(out) 416 | return err 417 | } 418 | 419 | func readAPIInfo(cmdName string) (*APIInfo, error) { 420 | const cmdArgs = "--api-info" 421 | output, err := exec.Command(cmdName, cmdArgs).Output() 422 | if err != nil { 423 | return nil, fmt.Errorf("failed to execuce %s %s: %w", cmdName, cmdArgs, err) 424 | } 425 | 426 | var info APIInfo 427 | if err := msgpack.NewDecoder(bytes.NewReader(output)).Decode(&info); err != nil { 428 | return nil, fmt.Errorf("failed to decode APIInfo: %w", err) 429 | } 430 | return &info, nil 431 | } 432 | 433 | // nvimTypes maps Go types to Nvim API types. 434 | var nvimTypes = map[string]string{ 435 | "": "void", 436 | "[]byte": "String", 437 | "[]uint": "Array", 438 | "[]any": "Array", 439 | "bool": "Boolean", 440 | "int": "Integer", 441 | "any": "Object", 442 | "string": "String", 443 | "float64": "Float", 444 | 445 | "ClientType": "String", 446 | "Process": "Object", 447 | "UserCommand": "Object", 448 | 449 | "Cmd": "Dictionary", 450 | "*Cmd": "Dictionary", 451 | "Channel": "Dictionary", 452 | "*Channel": "Dictionary", 453 | "ClientVersion": "Dictionary", 454 | "HLAttrs": "Dictionary", 455 | "*HLAttrs": "Dictionary", 456 | "[]*HLAttrs": "Dictionary", 457 | "WindowConfig": "Dictionary", 458 | "*WindowConfig": "Dictionary", 459 | "ClientAttributes": "Dictionary", 460 | "ClientMethods": "Dictionary", 461 | "map[string]*ClientMethod": "Dictionary", 462 | "map[string]*Command": "Dictionary", 463 | "map[string][]string": "Dictionary", 464 | "map[string]bool": "Dictionary", 465 | "map[string]int": "Dictionary", 466 | "map[string]any": "Dictionary", 467 | "map[string]OptionValueScope": "Dictionary", 468 | "Mode": "Dictionary", 469 | "OptionInfo": "Dictionary", 470 | 471 | "[]*AutocmdType": "Array", 472 | "[]*Channel": "Array", 473 | "[]*Process": "Array", 474 | "[]*UI": "Array", 475 | "[]ExtMark": "Array", 476 | "[]TextChunk": "Array", 477 | "Mark": "Array", 478 | 479 | "[2]int": "ArrayOf(Integer, 2)", 480 | "[]*Mapping": "ArrayOf(Dictionary)", 481 | "[][]byte": "ArrayOf(String)", 482 | "[]Buffer": "ArrayOf(Buffer)", 483 | "[]int": "ArrayOf(Integer)", 484 | "[]string": "ArrayOf(String)", 485 | "[]Tabpage": "ArrayOf(Tabpage)", 486 | "[]Window": "ArrayOf(Window)", 487 | } 488 | 489 | func convertToNvimTypes(f *Function) *Function { 490 | if t, ok := nvimTypes[f.ReturnType]; ok { 491 | f.ReturnType = t 492 | } 493 | for _, p := range f.Parameters { 494 | if t, ok := nvimTypes[p.Type]; ok { 495 | p.Type = t 496 | } 497 | } 498 | return f 499 | } 500 | 501 | type byName []*Function 502 | 503 | func (a byName) Len() int { return len(a) } 504 | func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 505 | func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } 506 | 507 | var compareTemplate = template.Must(template.New("").Funcs(template.FuncMap{ 508 | "lower": strings.ToLower, 509 | }).Parse(` 510 | {{- range .Extra}}< {{template "f" .}}{{end}} 511 | {{- range .Missing}}> {{template "f" .}}{{end}} 512 | {{- range .Different}}---- 513 | < {{template "f" index . 0}}> {{template "f" index . 1}}{{end}} 514 | {{- define "f"}}{{.Name}}({{range $i, $p := .Parameters}}{{if $i}}, {{end}}{{$p.Name}} {{$p.Type}}{{end}}){{with .ReturnType}} {{.}}{{end}} 515 | {{- print " {"}} name({{.Name}}){{with .DeprecatedSince}}; deprecatedSince({{.}});{{end}}{{print " }"}} 516 | {{end}}`)) 517 | 518 | // hiddenAPIs list of hidden API. 519 | var hiddenAPIs = map[string]bool{ 520 | "nvim__set_hl_ns": true, 521 | } 522 | 523 | // specialAPIs lists API calls that are implemented by hand. 524 | var specialAPIs = map[string]bool{ 525 | "nvim_call_atomic": true, 526 | "nvim_call_function": true, 527 | "nvim_call_dict_function": true, 528 | "nvim_execute_lua": true, 529 | "nvim_exec_lua": true, 530 | "nvim_buf_call": true, 531 | "nvim_set_decoration_provider": true, 532 | "nvim_chan_send": true, // FUNC_API_LUA_ONLY 533 | "nvim_win_call": true, // FUNC_API_LUA_ONLY 534 | "nvim_notify": true, // implements underling nlua(vim.notify) 535 | "nvim_get_option_info": true, // deprecated 536 | } 537 | 538 | func compareFunctions(cmdName string, functions []*Function) error { 539 | info, err := readAPIInfo(cmdName) 540 | if err != nil { 541 | return fmt.Errorf("failed to real APIInfo :%w", err) 542 | } 543 | 544 | sort.Sort(byName(functions)) 545 | sort.Sort(byName(info.Functions)) 546 | 547 | var data struct { 548 | Extra []*Function 549 | Missing []*Function 550 | Different [][2]*Function 551 | } 552 | 553 | i := 0 554 | j := 0 555 | for i < len(functions) && j < len(info.Functions) { 556 | a := convertToNvimTypes(functions[i]) 557 | b := info.Functions[j] 558 | 559 | if a.Name < b.Name { 560 | if !hiddenAPIs[a.Name] { 561 | data.Extra = append(data.Extra, a) 562 | } 563 | i++ 564 | continue 565 | } 566 | if b.Name < a.Name { 567 | if b.DeprecatedSince == 0 && !specialAPIs[b.Name] { 568 | data.Missing = append(data.Missing, b) 569 | } 570 | j++ 571 | continue 572 | } 573 | 574 | equal := len(a.Parameters) == len(b.Parameters) && a.ReturnType == b.ReturnType && a.DeprecatedSince == b.DeprecatedSince 575 | if equal { 576 | for i := range a.Parameters { 577 | if a.Parameters[i].Type != b.Parameters[i].Type { 578 | equal = false 579 | break 580 | } 581 | } 582 | } 583 | if !equal { 584 | data.Different = append(data.Different, [2]*Function{a, b}) 585 | } 586 | i++ 587 | j++ 588 | } 589 | 590 | for i < len(functions) { 591 | a := convertToNvimTypes(functions[i]) 592 | data.Extra = append(data.Extra, a) 593 | i++ 594 | } 595 | 596 | for j < len(info.Functions) { 597 | b := info.Functions[j] 598 | if b.DeprecatedSince == 0 { 599 | data.Missing = append(data.Missing, b) 600 | } 601 | j++ 602 | } 603 | 604 | if err := compareTemplate.Execute(os.Stdout, &data); err != nil { 605 | return fmt.Errorf("falied to Execute compareTemplate: %w", err) 606 | } 607 | return nil 608 | } 609 | 610 | func dumpAPI(cmdName string) error { 611 | output, err := exec.Command(cmdName, "--api-info").Output() 612 | if err != nil { 613 | return fmt.Errorf("error getting API info: %w", err) 614 | } 615 | 616 | var v any 617 | if err := msgpack.NewDecoder(bytes.NewReader(output)).Decode(&v); err != nil { 618 | return fmt.Errorf("error parsing msppack: %w", err) 619 | } 620 | 621 | p, err := json.MarshalIndent(v, "", " ") 622 | if err != nil { 623 | return nil 624 | } 625 | 626 | os.Stdout.Write(append(p, '\n')) 627 | return nil 628 | } 629 | 630 | var ( 631 | flagNvim string 632 | flagGenerate string 633 | flagDeprecated string 634 | flagCompare bool 635 | flagDump bool 636 | ) 637 | 638 | func main() { 639 | log.SetFlags(log.Lshortfile) 640 | 641 | flag.StringVar(&flagNvim, "nvim", "nvim", "nvim binary path") 642 | flag.StringVar(&flagGenerate, "generate", "", "Generate implementation from api_def.go and write to `file`") 643 | flag.StringVar(&flagDeprecated, "deprecated", "", "Generate deprecated implementation from api_def.go and write to `file`") 644 | flag.BoolVar(&flagCompare, "compare", false, "Compare api_def.go to the output of nvim --api-info") 645 | flag.BoolVar(&flagDump, "dump", false, "Print nvim --api-info as JSON") 646 | flag.Parse() 647 | 648 | if flagDump { 649 | if err := dumpAPI(flagNvim); err != nil { 650 | log.Fatal(err) 651 | } 652 | return 653 | } 654 | 655 | functions, deprecated, err := parseAPIDef() 656 | if err != nil { 657 | log.Fatal(err) 658 | } 659 | 660 | switch { 661 | case flagCompare: 662 | functions = append(functions, deprecated...) 663 | if err := compareFunctions(flagNvim, functions); err != nil { 664 | log.Fatal(err) 665 | } 666 | 667 | case flagGenerate != "": 668 | if flagDeprecated == "" { 669 | functions = append(functions, deprecated...) 670 | } 671 | if err := printImplementation(functions, implementationTemplate, flagGenerate); err != nil { 672 | log.Fatal(err) 673 | } 674 | 675 | if flagDeprecated != "" { 676 | if err := printImplementation(deprecated, deprecatedTemplate, flagDeprecated); err != nil { 677 | log.Fatal(err) 678 | } 679 | } 680 | } 681 | } 682 | --------------------------------------------------------------------------------