├── .github └── workflows │ └── scip.yml ├── LICENSE ├── README.md ├── doc.go ├── go.mod ├── jsonrpc2.go ├── lspext ├── cache.go ├── context_exec.go ├── filesext.go ├── fs.go ├── implementation.go ├── lspext.go ├── partial.go ├── proxy_lspext.go ├── proxy_lspext_test.go └── workspace_packages.go ├── service.go ├── service_test.go └── structures.go /.github/workflows/scip.yml: -------------------------------------------------------------------------------- 1 | name: SCIP 2 | 'on': 3 | - push 4 | jobs: 5 | scip-go: 6 | runs-on: ubuntu-latest 7 | container: sourcegraph/scip-go 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: Get src-cli 11 | run: curl -L https://sourcegraph.com/.api/src-cli/src_linux_amd64 -o /usr/local/bin/src; 12 | chmod +x /usr/local/bin/src 13 | - name: Set directory to safe for git 14 | run: git config --global --add safe.directory $GITHUB_WORKSPACE 15 | - name: Generate SCIP data 16 | run: scip-go 17 | - name: Upload SCIP data 18 | run: src code-intel upload -github-token=${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Sourcegraph 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-lsp 2 | 3 | Package lsp contains Go types for the messages used in the Language Server 4 | Protocol. 5 | 6 | See 7 | https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md 8 | for more information. 9 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package lsp contains Go types for the messages used in the Language 2 | // Server Protocol. 3 | // 4 | // See https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md 5 | // for more information. 6 | package lsp 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sourcegraph/go-lsp 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /jsonrpc2.go: -------------------------------------------------------------------------------- 1 | package lsp 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | ) 7 | 8 | // ID represents a JSON-RPC 2.0 request ID, which may be either a 9 | // string or number (or null, which is unsupported). 10 | type ID struct { 11 | // At most one of Num or Str may be nonzero. If both are zero 12 | // valued, then IsNum specifies which field's value is to be used 13 | // as the ID. 14 | Num uint64 15 | Str string 16 | 17 | // IsString controls whether the Num or Str field's value should be 18 | // used as the ID, when both are zero valued. It must always be 19 | // set to true if the request ID is a string. 20 | IsString bool 21 | } 22 | 23 | func (id ID) String() string { 24 | if id.IsString { 25 | return strconv.Quote(id.Str) 26 | } 27 | return strconv.FormatUint(id.Num, 10) 28 | } 29 | 30 | // MarshalJSON implements json.Marshaler. 31 | func (id ID) MarshalJSON() ([]byte, error) { 32 | if id.IsString { 33 | return json.Marshal(id.Str) 34 | } 35 | return json.Marshal(id.Num) 36 | } 37 | 38 | // UnmarshalJSON implements json.Unmarshaler. 39 | func (id *ID) UnmarshalJSON(data []byte) error { 40 | // Support both uint64 and string IDs. 41 | var v uint64 42 | if err := json.Unmarshal(data, &v); err == nil { 43 | *id = ID{Num: v} 44 | return nil 45 | } 46 | var v2 string 47 | if err := json.Unmarshal(data, &v2); err != nil { 48 | return err 49 | } 50 | *id = ID{Str: v2, IsString: true} 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /lspext/cache.go: -------------------------------------------------------------------------------- 1 | package lspext 2 | 3 | import "encoding/json" 4 | 5 | // See https://github.com/sourcegraph/language-server-protocol/pull/14 6 | 7 | // CacheGetParams is the input for 'cache/get'. The response is any or null. 8 | type CacheGetParams struct { 9 | // Key is the cache key. The key namespace is shared for a language 10 | // server, but with other language servers. For example the PHP 11 | // language server on different workspaces share the same key 12 | // namespace, but does not share the namespace with a Go language 13 | // server. 14 | Key string `json:"key"` 15 | } 16 | 17 | // CacheSetParams is the input for the notification 'cache/set'. 18 | type CacheSetParams struct { 19 | // Key is the cache key. The key namespace is shared for a language 20 | // server, but with other language servers. For example the PHP 21 | // language server on different workspaces share the same key 22 | // namespace, but does not share the namespace with a Go language 23 | // server. 24 | Key string `json:"key"` 25 | 26 | // Value is type any. We use json.RawMessage since we expect caching 27 | // implementation to cache the raw bytes, and not bother with 28 | // Unmarshaling/Marshalling. 29 | Value *json.RawMessage `json:"value"` 30 | } 31 | -------------------------------------------------------------------------------- /lspext/context_exec.go: -------------------------------------------------------------------------------- 1 | package lspext 2 | 3 | // ExecParams contains the parameters for the exec LSP request. 4 | type ExecParams struct { 5 | Command string `json:"command"` 6 | Arguments []string `json:"arguments"` 7 | } 8 | 9 | // ExecResult contains the result for the exec LSP response. 10 | type ExecResult struct { 11 | Stdout string `json:"stdout"` 12 | Stderr string `json:"stderr"` 13 | ExitCode int `json:"exitCode"` 14 | } 15 | -------------------------------------------------------------------------------- /lspext/filesext.go: -------------------------------------------------------------------------------- 1 | package lspext 2 | 3 | import "github.com/sourcegraph/go-lsp" 4 | 5 | // See https://github.com/sourcegraph/language-server-protocol/pull/4. 6 | 7 | // ContentParams is the input for 'textDocument/content'. The response is a 8 | // 'TextDocumentItem'. 9 | type ContentParams struct { 10 | TextDocument lsp.TextDocumentIdentifier `json:"textDocument"` 11 | } 12 | 13 | // FilesParams is the input for 'workspace/xfiles'. The response is '[]TextDocumentIdentifier' 14 | type FilesParams struct { 15 | Base string `json:"base,omitempty"` 16 | } 17 | -------------------------------------------------------------------------------- /lspext/fs.go: -------------------------------------------------------------------------------- 1 | package lspext 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "time" 7 | ) 8 | 9 | // FileInfo is the map-based implementation of FileInfo. 10 | type FileInfo struct { 11 | Name_ string `json:"name"` 12 | Size_ int64 `json:"size"` 13 | Dir_ bool `json:"dir"` 14 | } 15 | 16 | func (fi FileInfo) IsDir() bool { return fi.Dir_ } 17 | func (fi FileInfo) ModTime() time.Time { return time.Time{} } 18 | func (fi FileInfo) Mode() os.FileMode { 19 | if fi.IsDir() { 20 | return 0755 | os.ModeDir 21 | } 22 | return 0444 23 | } 24 | func (fi FileInfo) Name() string { return path.Base(fi.Name_) } 25 | func (fi FileInfo) Size() int64 { return int64(fi.Size_) } 26 | func (fi FileInfo) Sys() interface{} { return nil } 27 | -------------------------------------------------------------------------------- /lspext/implementation.go: -------------------------------------------------------------------------------- 1 | package lspext 2 | 3 | import "github.com/sourcegraph/go-lsp" 4 | 5 | // ImplementationLocation is a superset of lsp.Location with additional Go-specific information 6 | // about the implementation. 7 | type ImplementationLocation struct { 8 | lsp.Location // the location of the implementation 9 | 10 | // Type is the type of implementation relationship described by this location. 11 | // 12 | // If a type T was queried, the set of possible values are: 13 | // 14 | // - "to": named or ptr-to-named types assignable to interface T 15 | // - "from": named interfaces assignable from T (or only from *T if Ptr == true) 16 | // 17 | // If a method M on type T was queried, the same set of values above is used, except they refer 18 | // to methods on the described type (not the described type itself). 19 | // 20 | // (This type description is taken from golang.org/x/tools/cmd/guru.) 21 | Type string `json:"type,omitempty"` 22 | 23 | // Ptr is whether this implementation location is only assignable from a pointer *T (where T is 24 | // the queried type). 25 | Ptr bool `json:"ptr,omitempty"` 26 | 27 | // Method is whether a method was queried. If so, then the implementation locations refer to the 28 | // corresponding methods on the types found by the implementation query (not the types 29 | // themselves). 30 | Method bool `json:"method"` 31 | } 32 | -------------------------------------------------------------------------------- /lspext/lspext.go: -------------------------------------------------------------------------------- 1 | package lspext 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | "github.com/sourcegraph/go-lsp" 9 | ) 10 | 11 | // WorkspaceSymbolParams is the extension workspace/symbol parameter type. 12 | type WorkspaceSymbolParams struct { 13 | Query string `json:"query,omitempty"` 14 | Limit int `json:"limit"` 15 | Symbol SymbolDescriptor `json:"symbol,omitempty"` 16 | } 17 | 18 | // WorkspaceReferencesParams is parameters for the `workspace/xreferences` extension 19 | // 20 | // See: https://github.com/sourcegraph/language-server-protocol/blob/master/extension-workspace-reference.md 21 | // 22 | type WorkspaceReferencesParams struct { 23 | // Query represents metadata about the symbol that is being searched for. 24 | Query SymbolDescriptor `json:"query"` 25 | 26 | // Hints provides optional hints about where the language server should 27 | // look in order to find the symbol (this is an optimization). It is up to 28 | // the language server to define the schema of this object. 29 | Hints map[string]interface{} `json:"hints,omitempty"` 30 | 31 | // Limit if positive will limit the number of results returned. 32 | Limit int `json:"limit,omitempty"` 33 | } 34 | 35 | // ReferenceInformation represents information about a reference to programming 36 | // constructs like variables, classes, interfaces etc. 37 | type ReferenceInformation struct { 38 | // Reference is the location in the workspace where the `symbol` has been 39 | // referenced. 40 | Reference lsp.Location `json:"reference"` 41 | 42 | // Symbol is metadata information describing the symbol being referenced. 43 | Symbol SymbolDescriptor `json:"symbol"` 44 | } 45 | 46 | // SymbolDescriptor represents information about a programming construct like a 47 | // variable, class, interface, etc that has a reference to it. It is up to the 48 | // language server to define the schema of this object. 49 | // 50 | // SymbolDescriptor usually uniquely identifies a symbol, but it is not 51 | // guaranteed to do so. 52 | type SymbolDescriptor map[string]interface{} 53 | 54 | // SymbolLocationInformation is the response type for the `textDocument/xdefinition` extension. 55 | type SymbolLocationInformation struct { 56 | // A concrete location at which the definition is located, if any. 57 | Location lsp.Location `json:"location,omitempty"` 58 | // Metadata about the definition. 59 | Symbol SymbolDescriptor `json:"symbol"` 60 | } 61 | 62 | // Contains tells if this SymbolDescriptor fully contains all of the keys and 63 | // values in the other symbol descriptor. 64 | func (s SymbolDescriptor) Contains(other SymbolDescriptor) bool { 65 | for k, v := range other { 66 | v2, ok := s[k] 67 | if !ok || v != v2 { 68 | return false 69 | } 70 | } 71 | return true 72 | } 73 | 74 | // String returns a consistently ordered string representation of the 75 | // SymbolDescriptor. It is useful for testing. 76 | func (s SymbolDescriptor) String() string { 77 | sm := make(sortedMap, 0, len(s)) 78 | for k, v := range s { 79 | sm = append(sm, mapValue{key: k, value: v}) 80 | } 81 | sort.Sort(sm) 82 | var str string 83 | for _, v := range sm { 84 | str += fmt.Sprintf("%s:%v ", v.key, v.value) 85 | } 86 | return strings.TrimSpace(str) 87 | } 88 | 89 | type mapValue struct { 90 | key string 91 | value interface{} 92 | } 93 | 94 | type sortedMap []mapValue 95 | 96 | func (s sortedMap) Len() int { return len(s) } 97 | func (s sortedMap) Less(i, j int) bool { return s[i].key < s[j].key } 98 | func (s sortedMap) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 99 | -------------------------------------------------------------------------------- /lspext/partial.go: -------------------------------------------------------------------------------- 1 | package lspext 2 | 3 | import "github.com/sourcegraph/go-lsp" 4 | 5 | // PartialResultParams is the input for "$/partialResult", a notification. 6 | type PartialResultParams struct { 7 | // ID is the jsonrpc2 ID of the request we are returning partial 8 | // results for. 9 | ID lsp.ID `json:"id"` 10 | 11 | // Patch is a JSON patch as specified at http://jsonpatch.com/ 12 | // 13 | // It is an interface{}, since our only requirement is that it JSON 14 | // marshals to a valid list of JSON Patch operations. 15 | Patch interface{} `json:"patch"` 16 | } 17 | -------------------------------------------------------------------------------- /lspext/proxy_lspext.go: -------------------------------------------------------------------------------- 1 | // Package lspx contains extensions to the LSP protocol. 2 | // 3 | // An overview of the different protocol variants: 4 | // 5 | // // vanilla LSP 6 | // github.com/sourcegraph/go-langserver/pkg/lsp 7 | // 8 | // // proxy (http gateway) server LSP extensions 9 | // github.com/sourcegraph/sourcegraph/xlang 10 | // 11 | // // (this package) build/lang server LSP extensions 12 | // github.com/sourcegraph/sourcegraph/xlang/lspx 13 | // 14 | package lspext 15 | 16 | import ( 17 | "reflect" 18 | "time" 19 | 20 | "github.com/sourcegraph/go-lsp" 21 | ) 22 | 23 | // DependencyReference represents a reference to a dependency. []DependencyReference 24 | // is the return type for the build server workspace/xdependencies method. 25 | type DependencyReference struct { 26 | // Attributes describing the dependency that is being referenced. It is up 27 | // to the language server to define the schema of this object. 28 | Attributes map[string]interface{} `json:"attributes,omitempty"` 29 | 30 | // Hints is treated as an opaque object and passed directly by Sourcegraph 31 | // into the language server's workspace/xreferences method to help narrow 32 | // down the search space for the references to the symbol. 33 | // 34 | // If a language server emits no hints, Sourcegraph will pass none as a 35 | // parameter to workspace/xreferences which means it must search the entire 36 | // repository (workspace) in order to find references. workspace/xdependencies 37 | // should emit sufficient hints here for making all workspace/xreference 38 | // queries complete in a reasonable amount of time (less than a few seconds 39 | // for very large repositories). For example, one may include the 40 | // containing "package" or other build-system level "code unit". Emitting 41 | // the exact file is not recommended in general as that would produce more 42 | // data for little performance gain in most situations. 43 | Hints map[string]interface{} `json:"hints,omitempty"` 44 | } 45 | 46 | // TelemetryEventParams is a telemetry/event message sent from a 47 | // build/lang server back to the proxy. The information here is 48 | // forwarded to our opentracing system. 49 | type TelemetryEventParams struct { 50 | Op string `json:"op"` // the operation name 51 | StartTime time.Time `json:"startTime"` // when the operation started 52 | EndTime time.Time `json:"endTime"` // when the operation ended 53 | Tags map[string]string `json:"tags,omitempty"` // other metadata 54 | } 55 | 56 | type InitializeParams struct { 57 | lsp.InitializeParams 58 | 59 | // OriginalRootURI is the original rootUri for this LSP session, 60 | // before any path rewriting occurred. It is typically a Git clone 61 | // URL of the form 62 | // "git://github.com/facebook/react.git?rev=master#lib". 63 | // 64 | // The Go lang/build server uses this to infer the import path 65 | // root (and directory structure) to use for a workspace. 66 | OriginalRootURI lsp.DocumentURI `json:"originalRootUri"` 67 | 68 | // Mode is the name of the language. It is used to determine the correct 69 | // language server to route a request to, and to inform a language server 70 | // what languages it should contribute. 71 | Mode string `json:"mode"` 72 | } 73 | 74 | // WalkURIFields walks the LSP params/result object for fields 75 | // containing document URIs. 76 | // 77 | // If collect is non-nil, it calls collect(uri) for every URI 78 | // encountered. Callers can use this to collect a list of all document 79 | // URIs referenced in the params/result. 80 | // 81 | // If update is non-nil, it updates all document URIs in an LSP 82 | // params/result with the value of f(existingURI). Callers can use 83 | // this to rewrite paths in the params/result. 84 | // 85 | // TODO(sqs): does not support WorkspaceEdit (with a field whose 86 | // TypeScript type is {[uri: string]: TextEdit[]}. 87 | func WalkURIFields(o interface{}, collect func(lsp.DocumentURI), update func(lsp.DocumentURI) lsp.DocumentURI) { 88 | var walk func(o interface{}) 89 | walk = func(o interface{}) { 90 | switch o := o.(type) { 91 | case map[string]interface{}: 92 | for k, v := range o { // Location, TextDocumentIdentifier, TextDocumentItem, etc. 93 | if k == "uri" { 94 | s, ok := v.(string) 95 | if !ok { 96 | s2, ok2 := v.(lsp.DocumentURI) 97 | s = string(s2) 98 | ok = ok2 99 | } 100 | if ok { 101 | if collect != nil { 102 | collect(lsp.DocumentURI(s)) 103 | } 104 | if update != nil { 105 | o[k] = update(lsp.DocumentURI(s)) 106 | } 107 | continue 108 | } 109 | } 110 | walk(v) 111 | } 112 | case []interface{}: // Location[] 113 | for _, v := range o { 114 | walk(v) 115 | } 116 | default: // structs with a "URI" field 117 | rv := reflect.ValueOf(o) 118 | if rv.Kind() == reflect.Ptr { 119 | rv = rv.Elem() 120 | } 121 | if rv.Kind() == reflect.Struct { 122 | if fv := rv.FieldByName("URI"); fv.Kind() == reflect.String { 123 | if collect != nil { 124 | collect(lsp.DocumentURI(fv.String())) 125 | } 126 | if update != nil { 127 | fv.SetString(string(update(lsp.DocumentURI(fv.String())))) 128 | } 129 | } 130 | for i := 0; i < rv.NumField(); i++ { 131 | fv := rv.Field(i) 132 | if fv.Kind() == reflect.Ptr || fv.Kind() == reflect.Struct || fv.Kind() == reflect.Array { 133 | walk(fv.Interface()) 134 | } 135 | } 136 | } 137 | } 138 | } 139 | walk(o) 140 | } 141 | 142 | // ClientProxyInitializeParams are sent by the client to the proxy in 143 | // the "initialize" request. It has a non-standard field "mode", which 144 | // is the name of the language (using vscode terminology); "go" or 145 | // "typescript", for example. 146 | type ClientProxyInitializeParams struct { 147 | lsp.InitializeParams 148 | InitializationOptions ClientProxyInitializationOptions `json:"initializationOptions"` 149 | 150 | // Mode is DEPRECATED; it was moved to the subfield 151 | // initializationOptions.Mode. It is still here for backward 152 | // compatibility until the xlang service is upgraded. 153 | Mode string `json:"mode,omitempty"` 154 | } 155 | 156 | // ClientProxyInitializationOptions is the "initializationOptions" 157 | // field of the "initialize" request params sent from the client to 158 | // the LSP client proxy. 159 | type ClientProxyInitializationOptions struct { 160 | Mode string `json:"mode"` 161 | 162 | // Same as, but takes precedence over, InitializeParams.rootUri. 163 | // vscode-languageserver-node's LanguageClient doesn't allow overriding 164 | // InitializeParams.rootUri, so clients that use vscode-languageserver-node 165 | // instead set InitializeParams.initializationOptions.rootUri. 166 | RootURI *lsp.DocumentURI `json:"rootUri,omitempty"` 167 | 168 | // Session, if set, causes this session to be isolated from other 169 | // LSP sessions using the same workspace and mode. See 170 | // (contextID).session for more information. 171 | Session string `json:"session,omitempty"` 172 | 173 | // ZipURL is the zip URL to forward to the language server. 174 | ZipURL string `json:"zipURL,omitempty"` 175 | } 176 | -------------------------------------------------------------------------------- /lspext/proxy_lspext_test.go: -------------------------------------------------------------------------------- 1 | package lspext 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/sourcegraph/go-lsp" 10 | ) 11 | 12 | func TestWalkURIFields(t *testing.T) { 13 | tests := map[string][]lsp.DocumentURI{ 14 | `{"textDocument":{"uri":"u1"}}`: []lsp.DocumentURI{"u1"}, 15 | `{"uri":"u1"}`: []lsp.DocumentURI{"u1"}, 16 | } 17 | for objStr, wantURIs := range tests { 18 | var obj interface{} 19 | if err := json.Unmarshal([]byte(objStr), &obj); err != nil { 20 | t.Error(err) 21 | continue 22 | } 23 | 24 | var uris []lsp.DocumentURI 25 | collect := func(uri lsp.DocumentURI) { uris = append(uris, uri) } 26 | update := func(uri lsp.DocumentURI) lsp.DocumentURI { return "XXX" } 27 | WalkURIFields(obj, collect, update) 28 | 29 | if !reflect.DeepEqual(uris, wantURIs) { 30 | t.Errorf("%s: got URIs %q, want %q", objStr, uris, wantURIs) 31 | } 32 | 33 | wantObj := objStr 34 | for _, uri := range uris { 35 | wantObj = strings.Replace(wantObj, string(uri), "XXX", -1) 36 | } 37 | gotObj, err := json.Marshal(obj) 38 | if err != nil { 39 | t.Error(err) 40 | continue 41 | } 42 | if string(gotObj) != wantObj { 43 | t.Errorf("%s: got obj %q, want %q after updating URI pointers", objStr, gotObj, wantObj) 44 | } 45 | } 46 | } 47 | 48 | func TestWalkURIFields_struct(t *testing.T) { 49 | v := lsp.PublishDiagnosticsParams{URI: "u1"} 50 | 51 | var uris []lsp.DocumentURI 52 | collect := func(uri lsp.DocumentURI) { uris = append(uris, uri) } 53 | update := func(uri lsp.DocumentURI) lsp.DocumentURI { return "XXX" } 54 | WalkURIFields(&v, collect, update) 55 | 56 | if want := []lsp.DocumentURI{"u1"}; !reflect.DeepEqual(uris, want) { 57 | t.Errorf("got %v, want %v", uris, want) 58 | } 59 | 60 | if want := "XXX"; string(v.URI) != want { 61 | t.Errorf("got %q, want %q", v.URI, want) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lspext/workspace_packages.go: -------------------------------------------------------------------------------- 1 | package lspext 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | // WorkspacePackagesParams is parameters for the `workspace/xpackages` extension. 10 | // 11 | // See: https://github.com/sourcegraph/language-server-protocol/blob/7fd3c1/extension-workspace-references.md 12 | type WorkspacePackagesParams struct{} 13 | 14 | // PackageInformation is the metadata associated with a build-system- 15 | // or package-manager-level package. Sometimes, languages have 16 | // abstractions called "packages" as well, but this refers 17 | // specifically to packages as defined by the build system or package 18 | // manager. E.g., Python pip packages (NOT Python language packages or 19 | // modules), Go packages, Maven packages (NOT Java language packages), 20 | // npm modules (NOT JavaScript language modules). PackageInformation 21 | // includes both attributes of the package itself and attributes of 22 | // the package's dependencies. 23 | type PackageInformation struct { 24 | // Package is the set of attributes of the package 25 | Package PackageDescriptor `json:"package,omitempty"` 26 | 27 | // Dependencies is the list of dependency attributes 28 | Dependencies []DependencyReference `json:"dependencies,omitempty"` 29 | } 30 | 31 | // PackageDescriptor identifies a package (usually but not always uniquely). 32 | type PackageDescriptor map[string]interface{} 33 | 34 | // String returns a consistently ordered string representation of the 35 | // PackageDescriptor. It is useful for testing. 36 | func (s PackageDescriptor) String() string { 37 | sm := make(sortedMap, 0, len(s)) 38 | for k, v := range s { 39 | sm = append(sm, mapValue{key: k, value: v}) 40 | } 41 | sort.Sort(sm) 42 | var str string 43 | for _, v := range sm { 44 | str += fmt.Sprintf("%s:%v ", v.key, v.value) 45 | } 46 | return strings.TrimSpace(str) 47 | } 48 | -------------------------------------------------------------------------------- /service.go: -------------------------------------------------------------------------------- 1 | package lsp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/binary" 7 | "encoding/json" 8 | "strings" 9 | ) 10 | 11 | type None struct{} 12 | 13 | type InitializeParams struct { 14 | ProcessID int `json:"processId,omitempty"` 15 | 16 | // RootPath is DEPRECATED in favor of the RootURI field. 17 | RootPath string `json:"rootPath,omitempty"` 18 | 19 | RootURI DocumentURI `json:"rootUri,omitempty"` 20 | ClientInfo ClientInfo `json:"clientInfo,omitempty"` 21 | Trace Trace `json:"trace,omitempty"` 22 | InitializationOptions interface{} `json:"initializationOptions,omitempty"` 23 | Capabilities ClientCapabilities `json:"capabilities"` 24 | 25 | WorkDoneToken string `json:"workDoneToken,omitempty"` 26 | } 27 | 28 | // Root returns the RootURI if set, or otherwise the RootPath with 'file://' prepended. 29 | func (p *InitializeParams) Root() DocumentURI { 30 | if p.RootURI != "" { 31 | return p.RootURI 32 | } 33 | if strings.HasPrefix(p.RootPath, "file://") { 34 | return DocumentURI(p.RootPath) 35 | } 36 | return DocumentURI("file://" + p.RootPath) 37 | } 38 | 39 | type DocumentURI string 40 | 41 | type ClientInfo struct { 42 | Name string `json:"name,omitempty"` 43 | Version string `json:"version,omitempty"` 44 | } 45 | 46 | type Trace string 47 | 48 | type ClientCapabilities struct { 49 | Workspace WorkspaceClientCapabilities `json:"workspace,omitempty"` 50 | TextDocument TextDocumentClientCapabilities `json:"textDocument,omitempty"` 51 | Window WindowClientCapabilities `json:"window,omitempty"` 52 | Experimental interface{} `json:"experimental,omitempty"` 53 | 54 | // Below are Sourcegraph extensions. They do not live in lspext since 55 | // they are extending the field InitializeParams.Capabilities 56 | 57 | // XFilesProvider indicates the client provides support for 58 | // workspace/xfiles. This is a Sourcegraph extension. 59 | XFilesProvider bool `json:"xfilesProvider,omitempty"` 60 | 61 | // XContentProvider indicates the client provides support for 62 | // textDocument/xcontent. This is a Sourcegraph extension. 63 | XContentProvider bool `json:"xcontentProvider,omitempty"` 64 | 65 | // XCacheProvider indicates the client provides support for cache/get 66 | // and cache/set. 67 | XCacheProvider bool `json:"xcacheProvider,omitempty"` 68 | } 69 | 70 | type WorkspaceClientCapabilities struct { 71 | WorkspaceEdit struct { 72 | DocumentChanges bool `json:"documentChanges,omitempty"` 73 | ResourceOperations []string `json:"resourceOperations,omitempty"` 74 | } `json:"workspaceEdit,omitempty"` 75 | 76 | ApplyEdit bool `json:"applyEdit,omitempty"` 77 | 78 | Symbol struct { 79 | SymbolKind struct { 80 | ValueSet []int `json:"valueSet,omitempty"` 81 | } `json:"symbolKind,omitEmpty"` 82 | } `json:"symbol,omitempty"` 83 | 84 | ExecuteCommand *struct { 85 | DynamicRegistration bool `json:"dynamicRegistration,omitempty"` 86 | } `json:"executeCommand,omitempty"` 87 | 88 | DidChangeWatchedFiles *struct { 89 | DynamicRegistration bool `json:"dynamicRegistration,omitempty"` 90 | } `json:"didChangeWatchedFiles,omitempty"` 91 | 92 | WorkspaceFolders bool `json:"workspaceFolders,omitempty"` 93 | 94 | Configuration bool `json:"configuration,omitempty"` 95 | } 96 | 97 | type TextDocumentClientCapabilities struct { 98 | Declaration *struct { 99 | LinkSupport bool `json:"linkSupport,omitempty"` 100 | } `json:"declaration,omitempty"` 101 | 102 | Definition *struct { 103 | LinkSupport bool `json:"linkSupport,omitempty"` 104 | } `json:"definition,omitempty"` 105 | 106 | Implementation *struct { 107 | LinkSupport bool `json:"linkSupport,omitempty"` 108 | 109 | DynamicRegistration bool `json:"dynamicRegistration,omitempty"` 110 | } `json:"implementation,omitempty"` 111 | 112 | TypeDefinition *struct { 113 | LinkSupport bool `json:"linkSupport,omitempty"` 114 | } `json:"typeDefinition,omitempty"` 115 | 116 | Synchronization *struct { 117 | WillSave bool `json:"willSave,omitempty"` 118 | DidSave bool `json:"didSave,omitempty"` 119 | WillSaveWaitUntil bool `json:"willSaveWaitUntil,omitempty"` 120 | } `json:"synchronization,omitempty"` 121 | 122 | DocumentSymbol struct { 123 | SymbolKind struct { 124 | ValueSet []int `json:"valueSet,omitempty"` 125 | } `json:"symbolKind,omitEmpty"` 126 | 127 | HierarchicalDocumentSymbolSupport bool `json:"hierarchicalDocumentSymbolSupport,omitempty"` 128 | } `json:"documentSymbol,omitempty"` 129 | 130 | Formatting *struct { 131 | DynamicRegistration bool `json:"dynamicRegistration,omitempty"` 132 | } `json:"formatting,omitempty"` 133 | 134 | RangeFormatting *struct { 135 | DynamicRegistration bool `json:"dynamicRegistration,omitempty"` 136 | } `json:"rangeFormatting,omitempty"` 137 | 138 | Rename *struct { 139 | DynamicRegistration bool `json:"dynamicRegistration,omitempty"` 140 | 141 | PrepareSupport bool `json:"prepareSupport,omitempty"` 142 | } `json:"rename,omitempty"` 143 | 144 | SemanticHighlightingCapabilities *struct { 145 | SemanticHighlighting bool `json:"semanticHighlighting,omitempty"` 146 | } `json:"semanticHighlightingCapabilities,omitempty"` 147 | 148 | CodeAction struct { 149 | DynamicRegistration bool `json:"dynamicRegistration,omitempty"` 150 | 151 | IsPreferredSupport bool `json:"isPreferredSupport,omitempty"` 152 | 153 | CodeActionLiteralSupport struct { 154 | CodeActionKind struct { 155 | ValueSet []CodeActionKind `json:"valueSet,omitempty"` 156 | } `json:"codeActionKind,omitempty"` 157 | } `json:"codeActionLiteralSupport,omitempty"` 158 | } `json:"codeAction,omitempty"` 159 | 160 | Completion struct { 161 | CompletionItem struct { 162 | DocumentationFormat []DocumentationFormat `json:"documentationFormat,omitempty"` 163 | SnippetSupport bool `json:"snippetSupport,omitempty"` 164 | } `json:"completionItem,omitempty"` 165 | 166 | CompletionItemKind struct { 167 | ValueSet []CompletionItemKind `json:"valueSet,omitempty"` 168 | } `json:"completionItemKind,omitempty"` 169 | 170 | ContextSupport bool `json:"contextSupport,omitempty"` 171 | } `json:"completion,omitempty"` 172 | 173 | SignatureHelp *struct { 174 | SignatureInformation struct { 175 | ParameterInformation struct { 176 | LabelOffsetSupport bool `json:"labelOffsetSupport,omitempty"` 177 | } `json:"parameterInformation,omitempty"` 178 | } `json:"signatureInformation,omitempty"` 179 | } `json:"signatureHelp,omitempty"` 180 | 181 | DocumentLink *struct { 182 | DynamicRegistration bool `json:"dynamicRegistration,omitempty"` 183 | 184 | TooltipSupport bool `json:"tooltipSupport,omitempty"` 185 | } `json:"documentLink,omitempty"` 186 | 187 | Hover *struct { 188 | ContentFormat []string `json:"contentFormat,omitempty"` 189 | } `json:"hover,omitempty"` 190 | 191 | FoldingRange *struct { 192 | DynamicRegistration bool `json:"dynamicRegistration,omitempty"` 193 | 194 | RangeLimit interface{} `json:"rangeLimit,omitempty"` 195 | 196 | LineFoldingOnly bool `json:"lineFoldingOnly,omitempty"` 197 | } `json:"foldingRange,omitempty"` 198 | 199 | CallHierarchy *struct { 200 | DynamicRegistration bool `json:"dynamicRegistration,omitempty"` 201 | } `json:"callHierarchy,omitempty"` 202 | 203 | ColorProvider *struct { 204 | DynamicRegistration bool `json:"dynamicRegistration,omitempty"` 205 | } `json:"colorProvider,omitempty"` 206 | } 207 | 208 | type WindowClientCapabilities struct { 209 | WorkDoneProgress bool `json:"workDoneProgress,omitempty"` 210 | } 211 | 212 | type InitializeResult struct { 213 | Capabilities ServerCapabilities `json:"capabilities,omitempty"` 214 | } 215 | 216 | type InitializeError struct { 217 | Retry bool `json:"retry"` 218 | } 219 | 220 | type ResourceOperation string 221 | 222 | const ( 223 | ROCreate ResourceOperation = "create" 224 | RODelete ResourceOperation = "delete" 225 | RORename ResourceOperation = "rename" 226 | ) 227 | 228 | // TextDocumentSyncKind is a DEPRECATED way to describe how text 229 | // document syncing works. Use TextDocumentSyncOptions instead (or the 230 | // Options field of TextDocumentSyncOptionsOrKind if you need to 231 | // support JSON-(un)marshaling both). 232 | type TextDocumentSyncKind int 233 | 234 | const ( 235 | TDSKNone TextDocumentSyncKind = 0 236 | TDSKFull TextDocumentSyncKind = 1 237 | TDSKIncremental TextDocumentSyncKind = 2 238 | ) 239 | 240 | type TextDocumentSyncOptions struct { 241 | OpenClose bool `json:"openClose,omitempty"` 242 | Change TextDocumentSyncKind `json:"change"` 243 | WillSave bool `json:"willSave,omitempty"` 244 | WillSaveWaitUntil bool `json:"willSaveWaitUntil,omitempty"` 245 | Save *SaveOptions `json:"save,omitempty"` 246 | } 247 | 248 | // TextDocumentSyncOptions holds either a TextDocumentSyncKind or 249 | // TextDocumentSyncOptions. The LSP API allows either to be specified 250 | // in the (ServerCapabilities).TextDocumentSync field. 251 | type TextDocumentSyncOptionsOrKind struct { 252 | Kind *TextDocumentSyncKind 253 | Options *TextDocumentSyncOptions 254 | } 255 | 256 | // MarshalJSON implements json.Marshaler. 257 | func (v *TextDocumentSyncOptionsOrKind) MarshalJSON() ([]byte, error) { 258 | if v == nil { 259 | return []byte("null"), nil 260 | } 261 | if v.Kind != nil { 262 | return json.Marshal(v.Kind) 263 | } 264 | return json.Marshal(v.Options) 265 | } 266 | 267 | // UnmarshalJSON implements json.Unmarshaler. 268 | func (v *TextDocumentSyncOptionsOrKind) UnmarshalJSON(data []byte) error { 269 | if bytes.Equal(data, []byte("null")) { 270 | *v = TextDocumentSyncOptionsOrKind{} 271 | return nil 272 | } 273 | var kind TextDocumentSyncKind 274 | if err := json.Unmarshal(data, &kind); err == nil { 275 | // Create equivalent TextDocumentSyncOptions using the same 276 | // logic as in vscode-languageclient. Also set the Kind field 277 | // so that JSON-marshaling and unmarshaling are inverse 278 | // operations (for backward compatibility, preserving the 279 | // original input but accepting both). 280 | *v = TextDocumentSyncOptionsOrKind{ 281 | Options: &TextDocumentSyncOptions{OpenClose: true, Change: kind}, 282 | Kind: &kind, 283 | } 284 | return nil 285 | } 286 | var tmp TextDocumentSyncOptions 287 | if err := json.Unmarshal(data, &tmp); err != nil { 288 | return err 289 | } 290 | *v = TextDocumentSyncOptionsOrKind{Options: &tmp} 291 | return nil 292 | } 293 | 294 | type SaveOptions struct { 295 | IncludeText bool `json:"includeText"` 296 | } 297 | 298 | type ServerCapabilities struct { 299 | TextDocumentSync *TextDocumentSyncOptionsOrKind `json:"textDocumentSync,omitempty"` 300 | HoverProvider bool `json:"hoverProvider,omitempty"` 301 | CompletionProvider *CompletionOptions `json:"completionProvider,omitempty"` 302 | SignatureHelpProvider *SignatureHelpOptions `json:"signatureHelpProvider,omitempty"` 303 | DefinitionProvider bool `json:"definitionProvider,omitempty"` 304 | TypeDefinitionProvider bool `json:"typeDefinitionProvider,omitempty"` 305 | ReferencesProvider bool `json:"referencesProvider,omitempty"` 306 | DocumentHighlightProvider bool `json:"documentHighlightProvider,omitempty"` 307 | DocumentSymbolProvider bool `json:"documentSymbolProvider,omitempty"` 308 | WorkspaceSymbolProvider bool `json:"workspaceSymbolProvider,omitempty"` 309 | ImplementationProvider bool `json:"implementationProvider,omitempty"` 310 | CodeActionProvider bool `json:"codeActionProvider,omitempty"` 311 | CodeLensProvider *CodeLensOptions `json:"codeLensProvider,omitempty"` 312 | DocumentFormattingProvider bool `json:"documentFormattingProvider,omitempty"` 313 | DocumentRangeFormattingProvider bool `json:"documentRangeFormattingProvider,omitempty"` 314 | DocumentOnTypeFormattingProvider *DocumentOnTypeFormattingOptions `json:"documentOnTypeFormattingProvider,omitempty"` 315 | RenameProvider bool `json:"renameProvider,omitempty"` 316 | ExecuteCommandProvider *ExecuteCommandOptions `json:"executeCommandProvider,omitempty"` 317 | SemanticHighlighting *SemanticHighlightingOptions `json:"semanticHighlighting,omitempty"` 318 | 319 | // XWorkspaceReferencesProvider indicates the server provides support for 320 | // xworkspace/references. This is a Sourcegraph extension. 321 | XWorkspaceReferencesProvider bool `json:"xworkspaceReferencesProvider,omitempty"` 322 | 323 | // XDefinitionProvider indicates the server provides support for 324 | // textDocument/xdefinition. This is a Sourcegraph extension. 325 | XDefinitionProvider bool `json:"xdefinitionProvider,omitempty"` 326 | 327 | // XWorkspaceSymbolByProperties indicates the server provides support for 328 | // querying symbols by properties with WorkspaceSymbolParams.symbol. This 329 | // is a Sourcegraph extension. 330 | XWorkspaceSymbolByProperties bool `json:"xworkspaceSymbolByProperties,omitempty"` 331 | 332 | Experimental interface{} `json:"experimental,omitempty"` 333 | } 334 | 335 | type CompletionOptions struct { 336 | ResolveProvider bool `json:"resolveProvider,omitempty"` 337 | TriggerCharacters []string `json:"triggerCharacters,omitempty"` 338 | } 339 | 340 | type DocumentOnTypeFormattingOptions struct { 341 | FirstTriggerCharacter string `json:"firstTriggerCharacter"` 342 | MoreTriggerCharacter []string `json:"moreTriggerCharacter,omitempty"` 343 | } 344 | 345 | type CodeLensOptions struct { 346 | ResolveProvider bool `json:"resolveProvider,omitempty"` 347 | } 348 | 349 | type SignatureHelpOptions struct { 350 | TriggerCharacters []string `json:"triggerCharacters,omitempty"` 351 | } 352 | 353 | type ExecuteCommandOptions struct { 354 | Commands []string `json:"commands"` 355 | } 356 | 357 | type ExecuteCommandParams struct { 358 | Command string `json:"command"` 359 | Arguments []interface{} `json:"arguments,omitempty"` 360 | } 361 | 362 | type SemanticHighlightingOptions struct { 363 | Scopes [][]string `json:"scopes,omitempty"` 364 | } 365 | 366 | type CompletionItemKind int 367 | 368 | const ( 369 | _ CompletionItemKind = iota 370 | CIKText 371 | CIKMethod 372 | CIKFunction 373 | CIKConstructor 374 | CIKField 375 | CIKVariable 376 | CIKClass 377 | CIKInterface 378 | CIKModule 379 | CIKProperty 380 | CIKUnit 381 | CIKValue 382 | CIKEnum 383 | CIKKeyword 384 | CIKSnippet 385 | CIKColor 386 | CIKFile 387 | CIKReference 388 | CIKFolder 389 | CIKEnumMember 390 | CIKConstant 391 | CIKStruct 392 | CIKEvent 393 | CIKOperator 394 | CIKTypeParameter 395 | ) 396 | 397 | func (c CompletionItemKind) String() string { 398 | return completionItemKindName[c] 399 | } 400 | 401 | var completionItemKindName = map[CompletionItemKind]string{ 402 | CIKText: "text", 403 | CIKMethod: "method", 404 | CIKFunction: "function", 405 | CIKConstructor: "constructor", 406 | CIKField: "field", 407 | CIKVariable: "variable", 408 | CIKClass: "class", 409 | CIKInterface: "interface", 410 | CIKModule: "module", 411 | CIKProperty: "property", 412 | CIKUnit: "unit", 413 | CIKValue: "value", 414 | CIKEnum: "enum", 415 | CIKKeyword: "keyword", 416 | CIKSnippet: "snippet", 417 | CIKColor: "color", 418 | CIKFile: "file", 419 | CIKReference: "reference", 420 | CIKFolder: "folder", 421 | CIKEnumMember: "enumMember", 422 | CIKConstant: "constant", 423 | CIKStruct: "struct", 424 | CIKEvent: "event", 425 | CIKOperator: "operator", 426 | CIKTypeParameter: "typeParameter", 427 | } 428 | 429 | type CompletionItem struct { 430 | Label string `json:"label"` 431 | Kind CompletionItemKind `json:"kind,omitempty"` 432 | Detail string `json:"detail,omitempty"` 433 | Documentation string `json:"documentation,omitempty"` 434 | SortText string `json:"sortText,omitempty"` 435 | FilterText string `json:"filterText,omitempty"` 436 | InsertText string `json:"insertText,omitempty"` 437 | InsertTextFormat InsertTextFormat `json:"insertTextFormat,omitempty"` 438 | TextEdit *TextEdit `json:"textEdit,omitempty"` 439 | Data interface{} `json:"data,omitempty"` 440 | } 441 | 442 | type CompletionList struct { 443 | IsIncomplete bool `json:"isIncomplete"` 444 | Items []CompletionItem `json:"items"` 445 | } 446 | 447 | type CompletionTriggerKind int 448 | 449 | const ( 450 | CTKInvoked CompletionTriggerKind = 1 451 | CTKTriggerCharacter = 2 452 | ) 453 | 454 | type DocumentationFormat string 455 | 456 | const ( 457 | DFPlainText DocumentationFormat = "plaintext" 458 | ) 459 | 460 | type CodeActionKind string 461 | 462 | const ( 463 | CAKEmpty CodeActionKind = "" 464 | CAKQuickFix CodeActionKind = "quickfix" 465 | CAKRefactor CodeActionKind = "refactor" 466 | CAKRefactorExtract CodeActionKind = "refactor.extract" 467 | CAKRefactorInline CodeActionKind = "refactor.inline" 468 | CAKRefactorRewrite CodeActionKind = "refactor.rewrite" 469 | CAKSource CodeActionKind = "source" 470 | CAKSourceOrganizeImports CodeActionKind = "source.organizeImports" 471 | ) 472 | 473 | type InsertTextFormat int 474 | 475 | const ( 476 | ITFPlainText InsertTextFormat = 1 477 | ITFSnippet = 2 478 | ) 479 | 480 | type CompletionContext struct { 481 | TriggerKind CompletionTriggerKind `json:"triggerKind"` 482 | TriggerCharacter string `json:"triggerCharacter,omitempty"` 483 | } 484 | 485 | type CompletionParams struct { 486 | TextDocumentPositionParams 487 | Context CompletionContext `json:"context,omitempty"` 488 | } 489 | 490 | type Hover struct { 491 | Contents []MarkedString `json:"contents"` 492 | Range *Range `json:"range,omitempty"` 493 | } 494 | 495 | type hover Hover 496 | 497 | func (h Hover) MarshalJSON() ([]byte, error) { 498 | if h.Contents == nil { 499 | return json.Marshal(hover{ 500 | Contents: []MarkedString{}, 501 | Range: h.Range, 502 | }) 503 | } 504 | return json.Marshal(hover(h)) 505 | } 506 | 507 | type MarkedString markedString 508 | 509 | type markedString struct { 510 | Language string `json:"language"` 511 | Value string `json:"value"` 512 | 513 | isRawString bool 514 | } 515 | 516 | func (m *MarkedString) UnmarshalJSON(data []byte) error { 517 | if d := strings.TrimSpace(string(data)); len(d) > 0 && d[0] == '"' { 518 | // Raw string 519 | var s string 520 | if err := json.Unmarshal(data, &s); err != nil { 521 | return err 522 | } 523 | m.Value = s 524 | m.isRawString = true 525 | return nil 526 | } 527 | // Language string 528 | ms := (*markedString)(m) 529 | return json.Unmarshal(data, ms) 530 | } 531 | 532 | func (m MarkedString) MarshalJSON() ([]byte, error) { 533 | if m.isRawString { 534 | return json.Marshal(m.Value) 535 | } 536 | return json.Marshal((markedString)(m)) 537 | } 538 | 539 | // RawMarkedString returns a MarkedString consisting of only a raw 540 | // string (i.e., "foo" instead of {"value":"foo", "language":"bar"}). 541 | func RawMarkedString(s string) MarkedString { 542 | return MarkedString{Value: s, isRawString: true} 543 | } 544 | 545 | type SignatureHelp struct { 546 | Signatures []SignatureInformation `json:"signatures"` 547 | ActiveSignature int `json:"activeSignature"` 548 | ActiveParameter int `json:"activeParameter"` 549 | } 550 | 551 | type SignatureInformation struct { 552 | Label string `json:"label"` 553 | Documentation string `json:"documentation,omitempty"` 554 | Parameters []ParameterInformation `json:"parameters,omitempty"` 555 | } 556 | 557 | type ParameterInformation struct { 558 | Label string `json:"label"` 559 | Documentation string `json:"documentation,omitempty"` 560 | } 561 | 562 | type ReferenceContext struct { 563 | IncludeDeclaration bool `json:"includeDeclaration"` 564 | 565 | // Sourcegraph extension 566 | XLimit int `json:"xlimit,omitempty"` 567 | } 568 | 569 | type ReferenceParams struct { 570 | TextDocumentPositionParams 571 | Context ReferenceContext `json:"context"` 572 | } 573 | 574 | type DocumentHighlightKind int 575 | 576 | const ( 577 | Text DocumentHighlightKind = 1 578 | Read = 2 579 | Write = 3 580 | ) 581 | 582 | type DocumentHighlight struct { 583 | Range Range `json:"range"` 584 | Kind int `json:"kind,omitempty"` 585 | } 586 | 587 | type DocumentSymbolParams struct { 588 | TextDocument TextDocumentIdentifier `json:"textDocument"` 589 | } 590 | 591 | type SymbolKind int 592 | 593 | // The SymbolKind values are defined at https://microsoft.github.io/language-server-protocol/specification. 594 | const ( 595 | SKFile SymbolKind = 1 596 | SKModule SymbolKind = 2 597 | SKNamespace SymbolKind = 3 598 | SKPackage SymbolKind = 4 599 | SKClass SymbolKind = 5 600 | SKMethod SymbolKind = 6 601 | SKProperty SymbolKind = 7 602 | SKField SymbolKind = 8 603 | SKConstructor SymbolKind = 9 604 | SKEnum SymbolKind = 10 605 | SKInterface SymbolKind = 11 606 | SKFunction SymbolKind = 12 607 | SKVariable SymbolKind = 13 608 | SKConstant SymbolKind = 14 609 | SKString SymbolKind = 15 610 | SKNumber SymbolKind = 16 611 | SKBoolean SymbolKind = 17 612 | SKArray SymbolKind = 18 613 | SKObject SymbolKind = 19 614 | SKKey SymbolKind = 20 615 | SKNull SymbolKind = 21 616 | SKEnumMember SymbolKind = 22 617 | SKStruct SymbolKind = 23 618 | SKEvent SymbolKind = 24 619 | SKOperator SymbolKind = 25 620 | SKTypeParameter SymbolKind = 26 621 | ) 622 | 623 | func (s SymbolKind) String() string { 624 | return symbolKindName[s] 625 | } 626 | 627 | var symbolKindName = map[SymbolKind]string{ 628 | SKFile: "File", 629 | SKModule: "Module", 630 | SKNamespace: "Namespace", 631 | SKPackage: "Package", 632 | SKClass: "Class", 633 | SKMethod: "Method", 634 | SKProperty: "Property", 635 | SKField: "Field", 636 | SKConstructor: "Constructor", 637 | SKEnum: "Enum", 638 | SKInterface: "Interface", 639 | SKFunction: "Function", 640 | SKVariable: "Variable", 641 | SKConstant: "Constant", 642 | SKString: "String", 643 | SKNumber: "Number", 644 | SKBoolean: "Boolean", 645 | SKArray: "Array", 646 | SKObject: "Object", 647 | SKKey: "Key", 648 | SKNull: "Null", 649 | SKEnumMember: "EnumMember", 650 | SKStruct: "Struct", 651 | SKEvent: "Event", 652 | SKOperator: "Operator", 653 | SKTypeParameter: "TypeParameter", 654 | } 655 | 656 | type SymbolInformation struct { 657 | Name string `json:"name"` 658 | Kind SymbolKind `json:"kind"` 659 | Location Location `json:"location"` 660 | ContainerName string `json:"containerName,omitempty"` 661 | } 662 | 663 | type WorkspaceSymbolParams struct { 664 | Query string `json:"query"` 665 | Limit int `json:"limit"` 666 | } 667 | 668 | type ConfigurationParams struct { 669 | Items []ConfigurationItem `json:"items"` 670 | } 671 | 672 | type ConfigurationItem struct { 673 | ScopeURI string `json:"scopeUri,omitempty"` 674 | Section string `json:"section,omitempty"` 675 | } 676 | 677 | type ConfigurationResult []interface{} 678 | 679 | type CodeActionContext struct { 680 | Diagnostics []Diagnostic `json:"diagnostics"` 681 | } 682 | 683 | type CodeActionParams struct { 684 | TextDocument TextDocumentIdentifier `json:"textDocument"` 685 | Range Range `json:"range"` 686 | Context CodeActionContext `json:"context"` 687 | } 688 | 689 | type CodeLensParams struct { 690 | TextDocument TextDocumentIdentifier `json:"textDocument"` 691 | } 692 | 693 | type CodeLens struct { 694 | Range Range `json:"range"` 695 | Command Command `json:"command,omitempty"` 696 | Data interface{} `json:"data,omitempty"` 697 | } 698 | 699 | type DocumentFormattingParams struct { 700 | TextDocument TextDocumentIdentifier `json:"textDocument"` 701 | Options FormattingOptions `json:"options"` 702 | } 703 | 704 | type FormattingOptions struct { 705 | TabSize int `json:"tabSize"` 706 | InsertSpaces bool `json:"insertSpaces"` 707 | Key string `json:"key"` 708 | } 709 | 710 | type RenameParams struct { 711 | TextDocument TextDocumentIdentifier `json:"textDocument"` 712 | Position Position `json:"position"` 713 | NewName string `json:"newName"` 714 | } 715 | 716 | type DidOpenTextDocumentParams struct { 717 | TextDocument TextDocumentItem `json:"textDocument"` 718 | } 719 | 720 | type DidChangeTextDocumentParams struct { 721 | TextDocument VersionedTextDocumentIdentifier `json:"textDocument"` 722 | ContentChanges []TextDocumentContentChangeEvent `json:"contentChanges"` 723 | } 724 | 725 | type TextDocumentContentChangeEvent struct { 726 | Range *Range `json:"range,omitEmpty"` 727 | RangeLength uint `json:"rangeLength,omitEmpty"` 728 | Text string `json:"text"` 729 | } 730 | 731 | type DidCloseTextDocumentParams struct { 732 | TextDocument TextDocumentIdentifier `json:"textDocument"` 733 | } 734 | 735 | type DidSaveTextDocumentParams struct { 736 | TextDocument TextDocumentIdentifier `json:"textDocument"` 737 | } 738 | 739 | type MessageType int 740 | 741 | const ( 742 | MTError MessageType = 1 743 | MTWarning = 2 744 | Info = 3 745 | Log = 4 746 | ) 747 | 748 | type ShowMessageParams struct { 749 | Type MessageType `json:"type"` 750 | Message string `json:"message"` 751 | } 752 | 753 | type MessageActionItem struct { 754 | Title string `json:"title"` 755 | } 756 | 757 | type ShowMessageRequestParams struct { 758 | Type MessageType `json:"type"` 759 | Message string `json:"message"` 760 | Actions []MessageActionItem `json:"actions"` 761 | } 762 | 763 | type LogMessageParams struct { 764 | Type MessageType `json:"type"` 765 | Message string `json:"message"` 766 | } 767 | 768 | type DidChangeConfigurationParams struct { 769 | Settings interface{} `json:"settings"` 770 | } 771 | 772 | type FileChangeType int 773 | 774 | const ( 775 | Created FileChangeType = 1 776 | Changed = 2 777 | Deleted = 3 778 | ) 779 | 780 | type FileEvent struct { 781 | URI DocumentURI `json:"uri"` 782 | Type int `json:"type"` 783 | } 784 | 785 | type DidChangeWatchedFilesParams struct { 786 | Changes []FileEvent `json:"changes"` 787 | } 788 | 789 | type PublishDiagnosticsParams struct { 790 | URI DocumentURI `json:"uri"` 791 | Diagnostics []Diagnostic `json:"diagnostics"` 792 | } 793 | 794 | type DocumentRangeFormattingParams struct { 795 | TextDocument TextDocumentIdentifier `json:"textDocument"` 796 | Range Range `json:"range"` 797 | Options FormattingOptions `json:"options"` 798 | } 799 | 800 | type DocumentOnTypeFormattingParams struct { 801 | TextDocument TextDocumentIdentifier `json:"textDocument"` 802 | Position Position `json:"position"` 803 | Ch string `json:"ch"` 804 | Options FormattingOptions `json:"formattingOptions"` 805 | } 806 | 807 | type CancelParams struct { 808 | ID ID `json:"id"` 809 | } 810 | 811 | type SemanticHighlightingParams struct { 812 | TextDocument VersionedTextDocumentIdentifier `json:"textDocument"` 813 | Lines []SemanticHighlightingInformation `json:"lines"` 814 | } 815 | 816 | // SemanticHighlightingInformation represents a semantic highlighting 817 | // information that has to be applied on a specific line of the text 818 | // document. 819 | type SemanticHighlightingInformation struct { 820 | // Line is the zero-based line position in the text document. 821 | Line int `json:"line"` 822 | 823 | // Tokens is a base64 encoded string representing every single highlighted 824 | // characters with its start position, length and the "lookup table" index of 825 | // the semantic highlighting [TextMate scopes](https://manual.macromates.com/en/language_grammars). 826 | // If the `tokens` is empty or not defined, then no highlighted positions are 827 | // available for the line. 828 | Tokens SemanticHighlightingTokens `json:"tokens,omitempty"` 829 | } 830 | 831 | type semanticHighlightingInformation struct { 832 | Line int `json:"line"` 833 | Tokens *string `json:"tokens"` 834 | } 835 | 836 | // MarshalJSON implements json.Marshaler. 837 | func (v *SemanticHighlightingInformation) MarshalJSON() ([]byte, error) { 838 | tokens := string(v.Tokens.Serialize()) 839 | return json.Marshal(&semanticHighlightingInformation{ 840 | Line: v.Line, 841 | Tokens: &tokens, 842 | }) 843 | } 844 | 845 | // UnmarshalJSON implements json.Unmarshaler. 846 | func (v *SemanticHighlightingInformation) UnmarshalJSON(data []byte) error { 847 | var info semanticHighlightingInformation 848 | err := json.Unmarshal(data, &info) 849 | if err != nil { 850 | return err 851 | } 852 | 853 | if info.Tokens != nil { 854 | v.Tokens, err = DeserializeSemanticHighlightingTokens([]byte(*info.Tokens)) 855 | if err != nil { 856 | return err 857 | } 858 | } 859 | 860 | v.Line = info.Line 861 | return nil 862 | } 863 | 864 | type SemanticHighlightingTokens []SemanticHighlightingToken 865 | 866 | func (v SemanticHighlightingTokens) Serialize() []byte { 867 | var chunks [][]byte 868 | 869 | // Writes each token to `tokens` in the byte format specified by the LSP 870 | // proposal. Described below: 871 | // |<---- 4 bytes ---->|<-- 2 bytes -->|<--- 2 bytes -->| 872 | // | character | length | index | 873 | for _, token := range v { 874 | chunk := make([]byte, 8) 875 | binary.BigEndian.PutUint32(chunk[:4], token.Character) 876 | binary.BigEndian.PutUint16(chunk[4:6], token.Length) 877 | binary.BigEndian.PutUint16(chunk[6:], token.Scope) 878 | chunks = append(chunks, chunk) 879 | } 880 | 881 | src := make([]byte, len(chunks)*8) 882 | for i, chunk := range chunks { 883 | copy(src[i*8:i*8+8], chunk) 884 | } 885 | 886 | dst := make([]byte, base64.StdEncoding.EncodedLen(len(src))) 887 | base64.StdEncoding.Encode(dst, src) 888 | return dst 889 | } 890 | 891 | func DeserializeSemanticHighlightingTokens(src []byte) (SemanticHighlightingTokens, error) { 892 | dst := make([]byte, base64.StdEncoding.DecodedLen(len(src))) 893 | n, err := base64.StdEncoding.Decode(dst, src) 894 | if err != nil { 895 | return nil, err 896 | } 897 | 898 | var chunks [][]byte 899 | for i := 7; i < len(dst[:n]); i += 8 { 900 | chunks = append(chunks, dst[i-7:i+1]) 901 | } 902 | 903 | var tokens SemanticHighlightingTokens 904 | for _, chunk := range chunks { 905 | tokens = append(tokens, SemanticHighlightingToken{ 906 | Character: binary.BigEndian.Uint32(chunk[:4]), 907 | Length: binary.BigEndian.Uint16(chunk[4:6]), 908 | Scope: binary.BigEndian.Uint16(chunk[6:]), 909 | }) 910 | } 911 | 912 | return tokens, nil 913 | } 914 | 915 | type SemanticHighlightingToken struct { 916 | Character uint32 917 | Length uint16 918 | Scope uint16 919 | } 920 | -------------------------------------------------------------------------------- /service_test.go: -------------------------------------------------------------------------------- 1 | package lsp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestTextDocumentSyncOptionsOrKind_MarshalUnmarshalJSON(t *testing.T) { 11 | kindPtr := func(kind TextDocumentSyncKind) *TextDocumentSyncKind { 12 | return &kind 13 | } 14 | 15 | tests := []struct { 16 | data []byte 17 | want *TextDocumentSyncOptionsOrKind 18 | }{ 19 | { 20 | data: []byte(`2`), 21 | want: &TextDocumentSyncOptionsOrKind{ 22 | Options: &TextDocumentSyncOptions{ 23 | OpenClose: true, 24 | Change: TDSKIncremental, 25 | }, 26 | Kind: kindPtr(2), 27 | }, 28 | }, 29 | { 30 | data: []byte(`{"openClose":true,"change":1,"save":{"includeText":true}}`), 31 | want: &TextDocumentSyncOptionsOrKind{ 32 | Options: &TextDocumentSyncOptions{ 33 | OpenClose: true, 34 | Change: TDSKFull, 35 | Save: &SaveOptions{IncludeText: true}, 36 | }, 37 | }, 38 | }, 39 | } 40 | for _, test := range tests { 41 | var got TextDocumentSyncOptionsOrKind 42 | if err := json.Unmarshal(test.data, &got); err != nil { 43 | t.Error(err) 44 | continue 45 | } 46 | if !reflect.DeepEqual(&got, test.want) { 47 | t.Errorf("got %+v, want %+v", got, test.want) 48 | continue 49 | } 50 | data, err := json.Marshal(&got) 51 | if err != nil { 52 | t.Error(err) 53 | continue 54 | } 55 | if !bytes.Equal(data, test.data) { 56 | t.Errorf("got JSON %q, want %q", data, test.data) 57 | } 58 | } 59 | } 60 | 61 | func TestMarkedString_MarshalUnmarshalJSON(t *testing.T) { 62 | tests := []struct { 63 | data []byte 64 | want MarkedString 65 | }{{ 66 | data: []byte(`{"language":"go","value":"foo"}`), 67 | want: MarkedString{Language: "go", Value: "foo", isRawString: false}, 68 | }, { 69 | data: []byte(`{"language":"","value":"foo"}`), 70 | want: MarkedString{Language: "", Value: "foo", isRawString: false}, 71 | }, { 72 | data: []byte(`"foo"`), 73 | want: MarkedString{Language: "", Value: "foo", isRawString: true}, 74 | }} 75 | 76 | for _, test := range tests { 77 | var m MarkedString 78 | if err := json.Unmarshal(test.data, &m); err != nil { 79 | t.Errorf("json.Unmarshal error: %s", err) 80 | continue 81 | } 82 | if !reflect.DeepEqual(test.want, m) { 83 | t.Errorf("Unmarshaled %q, expected %+v, but got %+v", string(test.data), test.want, m) 84 | continue 85 | } 86 | 87 | marshaled, err := json.Marshal(m) 88 | if err != nil { 89 | t.Errorf("json.Marshal error: %s", err) 90 | continue 91 | } 92 | if string(marshaled) != string(test.data) { 93 | t.Errorf("Marshaled result expected %s, but got %s", string(test.data), string(marshaled)) 94 | } 95 | } 96 | } 97 | 98 | func TestHover(t *testing.T) { 99 | tests := []struct { 100 | data []byte 101 | want Hover 102 | skipUnmarshal bool 103 | skipMarshal bool 104 | }{{ 105 | data: []byte(`{"contents":[{"language":"go","value":"foo"}]}`), 106 | want: Hover{Contents: []MarkedString{{Language: "go", Value: "foo", isRawString: false}}}, 107 | }, { 108 | data: []byte(`{"contents":[]}`), 109 | want: Hover{Contents: nil}, 110 | skipUnmarshal: true, // testing we don't marshal nil 111 | }} 112 | 113 | for _, test := range tests { 114 | if !test.skipUnmarshal { 115 | var h Hover 116 | if err := json.Unmarshal(test.data, &h); err != nil { 117 | t.Errorf("json.Unmarshal error: %s", err) 118 | continue 119 | } 120 | if !reflect.DeepEqual(test.want, h) { 121 | t.Errorf("Unmarshaled %q, expected %+v, but got %+v", string(test.data), test.want, h) 122 | continue 123 | } 124 | } 125 | 126 | if !test.skipMarshal { 127 | marshaled, err := json.Marshal(&test.want) 128 | if err != nil { 129 | t.Errorf("json.Marshal error: %s", err) 130 | continue 131 | } 132 | if string(marshaled) != string(test.data) { 133 | t.Errorf("Marshaled result expected %s, but got %s", string(test.data), string(marshaled)) 134 | } 135 | } 136 | } 137 | } 138 | 139 | func TestSemanticHighlightingTokens(t *testing.T) { 140 | tests := []struct { 141 | data SemanticHighlightingTokens 142 | want string 143 | }{{ 144 | data: nil, 145 | want: `{"line":0,"tokens":""}`, 146 | }, { 147 | data: SemanticHighlightingTokens{ 148 | { 149 | Character: 1, 150 | Length: 2, 151 | Scope: 3, 152 | }, 153 | }, 154 | want: `{"line":0,"tokens":"AAAAAQACAAM="}`, 155 | }, { 156 | // Double check correctness by adapting test from: 157 | // https://github.com/gluon-lang/lsp-types/blob/647f7013625c3cd45c1d3fe53a2e3656d091c36a/src/lib.rs#L4057 158 | data: SemanticHighlightingTokens{ 159 | { 160 | Character: 0x00000001, 161 | Length: 0x0002, 162 | Scope: 0x0003, 163 | }, 164 | { 165 | Character: 0x00112222, 166 | Length: 0x0FF0, 167 | Scope: 0x0202, 168 | }, 169 | }, 170 | want: `{"line":0,"tokens":"AAAAAQACAAMAESIiD/ACAg=="}`, 171 | }} 172 | 173 | for _, test := range tests { 174 | info := SemanticHighlightingInformation{ 175 | Tokens: test.data, 176 | } 177 | marshaled, err := json.Marshal(&info) 178 | if err != nil { 179 | t.Errorf("json.Marshal error: %s", err) 180 | continue 181 | } 182 | if string(marshaled) != test.want { 183 | t.Errorf("Marshaled result expected %s, but got %s", test.want, string(marshaled)) 184 | continue 185 | } 186 | var s SemanticHighlightingInformation 187 | if err := json.Unmarshal(marshaled, &s); err != nil { 188 | t.Errorf("json.Unmarshal error: %s", err) 189 | continue 190 | } 191 | if !reflect.DeepEqual(test.data, s.Tokens) { 192 | t.Errorf("Expected %+v, but got %+v", test.want, s) 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /structures.go: -------------------------------------------------------------------------------- 1 | package lsp 2 | 3 | import "fmt" 4 | 5 | type Position struct { 6 | /** 7 | * Line position in a document (zero-based). 8 | */ 9 | Line int `json:"line"` 10 | 11 | /** 12 | * Character offset on a line in a document (zero-based). 13 | */ 14 | Character int `json:"character"` 15 | } 16 | 17 | func (p Position) String() string { 18 | return fmt.Sprintf("%d:%d", p.Line, p.Character) 19 | } 20 | 21 | type Range struct { 22 | /** 23 | * The range's start position. 24 | */ 25 | Start Position `json:"start"` 26 | 27 | /** 28 | * The range's end position. 29 | */ 30 | End Position `json:"end"` 31 | } 32 | 33 | func (r Range) String() string { 34 | return fmt.Sprintf("%s-%s", r.Start, r.End) 35 | } 36 | 37 | type Location struct { 38 | URI DocumentURI `json:"uri"` 39 | Range Range `json:"range"` 40 | } 41 | 42 | type Diagnostic struct { 43 | /** 44 | * The range at which the message applies. 45 | */ 46 | Range Range `json:"range"` 47 | 48 | /** 49 | * The diagnostic's severity. Can be omitted. If omitted it is up to the 50 | * client to interpret diagnostics as error, warning, info or hint. 51 | */ 52 | Severity DiagnosticSeverity `json:"severity,omitempty"` 53 | 54 | /** 55 | * The diagnostic's code. Can be omitted. 56 | */ 57 | Code string `json:"code,omitempty"` 58 | 59 | /** 60 | * A human-readable string describing the source of this 61 | * diagnostic, e.g. 'typescript' or 'super lint'. 62 | */ 63 | Source string `json:"source,omitempty"` 64 | 65 | /** 66 | * The diagnostic's message. 67 | */ 68 | Message string `json:"message"` 69 | } 70 | 71 | type DiagnosticSeverity int 72 | 73 | const ( 74 | Error DiagnosticSeverity = 1 75 | Warning = 2 76 | Information = 3 77 | Hint = 4 78 | ) 79 | 80 | type Command struct { 81 | /** 82 | * Title of the command, like `save`. 83 | */ 84 | Title string `json:"title"` 85 | /** 86 | * The identifier of the actual command handler. 87 | */ 88 | Command string `json:"command"` 89 | /** 90 | * Arguments that the command handler should be 91 | * invoked with. 92 | */ 93 | Arguments []interface{} `json:"arguments"` 94 | } 95 | 96 | type TextEdit struct { 97 | /** 98 | * The range of the text document to be manipulated. To insert 99 | * text into a document create a range where start === end. 100 | */ 101 | Range Range `json:"range"` 102 | 103 | /** 104 | * The string to be inserted. For delete operations use an 105 | * empty string. 106 | */ 107 | NewText string `json:"newText"` 108 | } 109 | 110 | type WorkspaceEdit struct { 111 | /** 112 | * Holds changes to existing resources. 113 | */ 114 | Changes map[string][]TextEdit `json:"changes"` 115 | } 116 | 117 | type TextDocumentIdentifier struct { 118 | /** 119 | * The text document's URI. 120 | */ 121 | URI DocumentURI `json:"uri"` 122 | } 123 | 124 | type TextDocumentItem struct { 125 | /** 126 | * The text document's URI. 127 | */ 128 | URI DocumentURI `json:"uri"` 129 | 130 | /** 131 | * The text document's language identifier. 132 | */ 133 | LanguageID string `json:"languageId"` 134 | 135 | /** 136 | * The version number of this document (it will strictly increase after each 137 | * change, including undo/redo). 138 | */ 139 | Version int `json:"version"` 140 | 141 | /** 142 | * The content of the opened text document. 143 | */ 144 | Text string `json:"text"` 145 | } 146 | 147 | type VersionedTextDocumentIdentifier struct { 148 | TextDocumentIdentifier 149 | /** 150 | * The version number of this document. 151 | */ 152 | Version int `json:"version"` 153 | } 154 | 155 | type TextDocumentPositionParams struct { 156 | /** 157 | * The text document. 158 | */ 159 | TextDocument TextDocumentIdentifier `json:"textDocument"` 160 | 161 | /** 162 | * The position inside the text document. 163 | */ 164 | Position Position `json:"position"` 165 | } 166 | --------------------------------------------------------------------------------