├── .editorconfig ├── LICENSE ├── README.md ├── jsonrpc ├── split.go └── split_test.go ├── language_server.go ├── main.go ├── msg_types.go ├── msg_types_easyjson.go ├── uri.go └── uri_test.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | As a heads up, I'm not likely to work on this project anymore. 2 | 3 | # lspc 4 | 5 | lspc is a command-line client for the language server protocol ecosystem. 6 | lspc starts and runs language servers and lets you interact with them as if 7 | they were command line tools. 8 | 9 | ## Installation 10 | 11 | ```sh 12 | $ go get -u github.com/jacobdufault/lspc 13 | $ lspc # prints out help/usage 14 | # Use lspc help for more information, ie, lspc help start 15 | ``` 16 | 17 | ## Development status 18 | 19 | Still under active work. Managing language servers mostly works, but they 20 | cannot yet be easily interacted with. 21 | -------------------------------------------------------------------------------- /jsonrpc/split.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jacob Dufault 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package jsonrpc 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "strconv" 21 | "unicode" 22 | ) 23 | 24 | // SplitFunc is a bufio.SplitFunc implementation that splits JsonRPC messages. 25 | func SplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) { 26 | i := 0 27 | 28 | maybeAddEOFError := func() { 29 | if atEOF { 30 | err = errors.New("Expected more content") 31 | } 32 | } 33 | 34 | readString := func(content string) { 35 | for _, c := range content { 36 | // Not enough input yet; try again later. 37 | if i >= len(data) { 38 | maybeAddEOFError() 39 | return 40 | } 41 | 42 | if data[i] != byte(c) { 43 | err = fmt.Errorf("Unexpected token '%c'", data[i]) 44 | return 45 | } 46 | i++ 47 | } 48 | } 49 | 50 | // Read Content-Length: 51 | if readString("Content-Length: "); err != nil { 52 | return 53 | } 54 | 55 | // Read the number. 56 | digitStart := i 57 | for { 58 | // Not enough input yet; try again later. 59 | if i >= len(data) { 60 | maybeAddEOFError() 61 | return 62 | } 63 | 64 | // Read until we have a number 65 | if !unicode.IsDigit(rune(data[i])) { 66 | break 67 | } 68 | 69 | i++ 70 | } 71 | contentLength, err := strconv.Atoi(string(data[digitStart:i])) 72 | if err != nil { 73 | return 74 | } 75 | 76 | // Read \r\n\r\n 77 | if readString("\r\n\r\n"); err != nil { 78 | return 79 | } 80 | 81 | // Not enough input yet; try again later. 82 | if i+contentLength > len(data) { 83 | maybeAddEOFError() 84 | return 85 | } 86 | 87 | // Return the token 88 | advance = i + contentLength 89 | token = data[i : i+contentLength] 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /jsonrpc/split_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jacob Dufault 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package jsonrpc 16 | 17 | import ( 18 | "bufio" 19 | "strings" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestReadBadHeader(t *testing.T) { 26 | input := "foobar" 27 | scanner := bufio.NewScanner(strings.NewReader(input)) 28 | scanner.Split(SplitFunc) 29 | scanner.Scan() 30 | assert.Error(t, scanner.Err()) 31 | } 32 | 33 | func TestReadBadHeaderLength(t *testing.T) { 34 | input := "Content-Length: aa\r\n\r\n" 35 | scanner := bufio.NewScanner(strings.NewReader(input)) 36 | scanner.Split(SplitFunc) 37 | scanner.Scan() 38 | assert.Error(t, scanner.Err()) 39 | } 40 | 41 | func TestReadBadHeaderSeparator(t *testing.T) { 42 | input := "Content-Length: 5\r\r\n" 43 | scanner := bufio.NewScanner(strings.NewReader(input)) 44 | scanner.Split(SplitFunc) 45 | scanner.Scan() 46 | assert.Error(t, scanner.Err()) 47 | } 48 | 49 | func TestReadEmptyMessage(t *testing.T) { 50 | input := "Content-Length: 0\r\n\r\n" 51 | scanner := bufio.NewScanner(strings.NewReader(input)) 52 | scanner.Split(SplitFunc) 53 | scanner.Scan() 54 | assert.NoError(t, scanner.Err()) 55 | assert.Equal(t, "", scanner.Text()) 56 | } 57 | 58 | func TestReadSmallMessage(t *testing.T) { 59 | input := "Content-Length: 3\r\n\r\nabc" 60 | scanner := bufio.NewScanner(strings.NewReader(input)) 61 | scanner.Split(SplitFunc) 62 | scanner.Scan() 63 | assert.NoError(t, scanner.Err()) 64 | assert.Equal(t, "abc", scanner.Text()) 65 | } 66 | 67 | func TestReadMultipleMessages(t *testing.T) { 68 | input := "Content-Length: 3\r\n\r\nabc" + 69 | "Content-Length: 5\r\n\r\n12345" + 70 | "Content-Length: 2000\r\n\r\nabc" // Not enough content. 71 | 72 | scanner := bufio.NewScanner(strings.NewReader(input)) 73 | scanner.Split(SplitFunc) 74 | 75 | scanner.Scan() 76 | assert.NoError(t, scanner.Err()) 77 | assert.Equal(t, "abc", scanner.Text()) 78 | 79 | scanner.Scan() 80 | assert.NoError(t, scanner.Err()) 81 | assert.Equal(t, "12345", scanner.Text()) 82 | 83 | scanner.Scan() 84 | assert.Error(t, scanner.Err()) 85 | } 86 | -------------------------------------------------------------------------------- /language_server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jacob Dufault 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "bufio" 19 | "fmt" 20 | "io" 21 | "log" 22 | "os/exec" 23 | 24 | "github.com/jacobdufault/lspc/jsonrpc" 25 | easyjson "github.com/mailru/easyjson" 26 | "github.com/mailru/easyjson/jwriter" 27 | shellwords "github.com/mattn/go-shellwords" 28 | ) 29 | 30 | // When a language server has been closed it is sent to this channel. 31 | // If this ever blocks the daemon may deadlock. 32 | var languageServerClosed = make(chan *languageServer, 1000) 33 | 34 | type responseHandler func(json easyjson.RawMessage) 35 | 36 | type languageServer struct { 37 | cmd *exec.Cmd 38 | 39 | // Directory the language server is running in. Used to determine which 40 | // language server instance to send a message to. 41 | directory string 42 | nextRequestID RequestID 43 | onResponse map[RequestID]responseHandler 44 | 45 | err error 46 | 47 | stdin io.WriteCloser 48 | stdout io.ReadCloser 49 | stderr io.ReadCloser 50 | } 51 | 52 | func startLanguageServer(bin, directory string, initOpts easyjson.RawMessage) (*languageServer, error) { 53 | exe, e := shellwords.Parse(bin) 54 | if e != nil { 55 | return nil, fmt.Errorf("cannot parse <%s>; error=%s", bin, e.Error()) 56 | } 57 | 58 | ls := languageServer{ 59 | directory: directory, 60 | onResponse: make(map[RequestID]responseHandler), 61 | } 62 | 63 | // Start the binary. 64 | ls.cmd = exec.Command(exe[0], exe[1:]...) 65 | ls.cmd.Dir = directory 66 | ls.stdin, e = ls.cmd.StdinPipe() 67 | if e != nil { 68 | return nil, e 69 | } 70 | ls.stdout, e = ls.cmd.StdoutPipe() 71 | if e != nil { 72 | return nil, e 73 | } 74 | ls.stderr, e = ls.cmd.StderrPipe() 75 | if e != nil { 76 | return nil, e 77 | } 78 | e = ls.cmd.Start() 79 | if e != nil { 80 | log.Printf("Got error while starting %s", e.Error()) 81 | return nil, e 82 | } 83 | 84 | // Handle all process input/output on goroutines. 85 | go ls.stdoutReader() 86 | go ls.stderrReader() 87 | 88 | ls.writeInitialize(initOpts) 89 | 90 | return &ls, nil 91 | } 92 | 93 | // Write a request, which will have an associated response. 94 | func (l *languageServer) writeRequest(method string, params easyjson.RawMessage, onResponse responseHandler) { 95 | 96 | id := l.nextRequestID 97 | l.nextRequestID++ 98 | 99 | // Use a dummy handler if the user does not care about the result. This 100 | // prevents log spam from unexpected responses. 101 | if onResponse == nil { 102 | onResponse = func(_ easyjson.RawMessage) {} 103 | } 104 | 105 | l.onResponse[id] = onResponse 106 | l.rawWriteMsg(method, params, id) 107 | } 108 | 109 | func (l *languageServer) writeNotification(method string, params easyjson.RawMessage) { 110 | l.rawWriteMsg(method, params, -1) 111 | } 112 | 113 | // id will only be written to json if it is >= 0 114 | func (l *languageServer) rawWriteMsg(method string, params easyjson.RawMessage, id RequestID) { 115 | if l.err != nil { 116 | log.Printf("Attempt to write message while language server has error %s", l.err.Error()) 117 | return 118 | } 119 | 120 | // content.ID is not written if it is less than 0 121 | content := JSONRPCHeader{ 122 | JSONRPC: "2.0", 123 | ID: id, 124 | Method: method, 125 | Params: params, 126 | } 127 | 128 | if _, e := marshalToWriter(content, l.stdin); e != nil { 129 | l.err = e 130 | languageServerClosed <- l 131 | } 132 | 133 | // Uncomment to write the written request to stderr. 134 | // marshalToWriter(content, os.Stderr) 135 | } 136 | 137 | func marshalToWriter(v easyjson.Marshaler, w io.Writer) (written int, err error) { 138 | jw := jwriter.Writer{} 139 | jw.Flags = jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty 140 | v.MarshalEasyJSON(&jw) 141 | 142 | // Write header, and then the content 143 | fmt.Fprintf(w, "Content-Length: %d\r\n\r\n", jw.Size()) 144 | return jw.DumpTo(w) 145 | } 146 | 147 | func toJSON(m easyjson.Marshaler) easyjson.RawMessage { 148 | jw := jwriter.Writer{} 149 | jw.Flags = jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty 150 | m.MarshalEasyJSON(&jw) 151 | 152 | r, e := jw.BuildBytes() 153 | panicIfError(e) 154 | return r 155 | } 156 | 157 | func (l *languageServer) writeInitialize(initOpts easyjson.RawMessage) { 158 | // Send input. 159 | l.writeRequest("initialize", toJSON(LsInitializeParams{ 160 | RootURI: pathToURI(l.directory), 161 | InitializationOptions: initOpts, 162 | }), func(json easyjson.RawMessage) { 163 | log.Print("Got initialize response") 164 | }) 165 | } 166 | 167 | func (l *languageServer) stdoutReader() { 168 | // Build scanner which will process LSP messages. 169 | scanner := bufio.NewScanner(l.stdout) 170 | scanner.Split(jsonrpc.SplitFunc) 171 | // Increase maximum token length; the default is 64 * 1024, which is probably 172 | // too low. 173 | const maxScanTokenSize = 1024 * 1024 174 | scanner.Buffer(make([]byte, 0), maxScanTokenSize) 175 | 176 | for scanner.Scan() { 177 | header := JSONRPCHeader{} 178 | header.ID = -1 179 | header.UnmarshalJSON(scanner.Bytes()) 180 | if header.ID >= 0 { 181 | if response, has := l.onResponse[header.ID]; has { 182 | response(header.Params) 183 | } else { 184 | log.Printf("No handler for response id %d", header.ID) 185 | } 186 | } 187 | } 188 | 189 | if scanner.Err() != nil { 190 | l.err = scanner.Err() 191 | } 192 | languageServerClosed <- l 193 | } 194 | 195 | func (l *languageServer) stderrReader() { 196 | var buffer [256]byte 197 | for { 198 | n, e := l.stderr.Read(buffer[:]) 199 | if e != nil { 200 | l.err = e 201 | languageServerClosed <- l 202 | break 203 | } 204 | // For the time being just echo output to our stdout. 205 | fmt.Printf("stderr: %s", buffer[:n]) 206 | } 207 | 208 | languageServerClosed <- l 209 | } 210 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jacob Dufault 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "net" 21 | "net/rpc" 22 | "os" 23 | "os/exec" 24 | "path/filepath" 25 | "runtime" 26 | "time" 27 | 28 | "github.com/mailru/easyjson" 29 | 30 | "github.com/urfave/cli" 31 | ) 32 | 33 | func panicIfError(e error) { 34 | if e != nil { 35 | panic(e.Error()) 36 | } 37 | } 38 | 39 | func fileExists(path string) bool { 40 | _, err := os.Stat(path) 41 | return err == nil 42 | } 43 | 44 | func getSocketFilename() string { 45 | user := os.Getenv("USER") 46 | if user == "" { 47 | user = "all" 48 | } 49 | return filepath.Join(os.TempDir(), fmt.Sprintf("lspc.%s", user)) 50 | } 51 | 52 | // Server contains methods which the client can call over rpc. 53 | type Server struct { 54 | servers []*languageServer 55 | } 56 | 57 | func (s *Server) clean() { 58 | // This is likely not needed now that we properly shutdown language servers with the languageServerClosed channel. 59 | /* 60 | i := 0 61 | for i < len(s.servers) { 62 | if s.servers[i].err != nil { 63 | log.Printf("Removing language server %+v in %s", s.servers[i].cmd.Args, s.servers[i].directory) 64 | s.servers = append(s.servers[:i], s.servers[i+1:]...) 65 | } else { 66 | i++ 67 | } 68 | } 69 | */ 70 | } 71 | 72 | // KeepAlive ensures the server does not shutdown due to inactivity. 73 | func (s *Server) KeepAlive(_ bool, _ *bool) error { 74 | log.Print("CMD keep-alive") 75 | timeout := time.Duration(gTimeout) * time.Second 76 | countdown.Reset(timeout) 77 | return nil 78 | } 79 | 80 | var gShutdown bool 81 | 82 | // Kill shuts the server down after a short delay. 83 | // TODO: make kill configurable; kill a specific PID; ls should list the PID to kill (or maybe we want to do `lspc kill 0, lspc kill 1`, etc) 84 | func (s *Server) Kill(_ bool, _ *bool) error { 85 | log.Print("CMD kill") 86 | gShutdown = true 87 | return nil 88 | } 89 | 90 | // ServerPid returns the pid of the server. 91 | func (s *Server) ServerPid(_ bool, pid *int) error { 92 | log.Print("CMD server-pid") 93 | *pid = os.Getpid() 94 | return nil 95 | } 96 | 97 | // Ls lists running servers. 98 | func (s *Server) Ls(_ bool, servers *[]string) error { 99 | log.Print("CMD ls") 100 | s.clean() 101 | for _, server := range s.servers { 102 | *servers = append(*servers, fmt.Sprintf("%+v in %s", server.cmd.Args, server.directory)) 103 | } 104 | return nil 105 | } 106 | 107 | // StartArgs holds arguments for Start. 108 | type StartArgs struct { 109 | Bin string 110 | Directory string 111 | InitOpts easyjson.RawMessage 112 | } 113 | 114 | // Start runs a new language server. 115 | func (s *Server) Start(args StartArgs, _ *bool) error { 116 | log.Printf("CMD start %s in %s", args.Bin, args.Directory) 117 | 118 | ls, err := startLanguageServer(args.Bin, args.Directory, args.InitOpts) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | s.servers = append(s.servers, ls) 124 | return nil 125 | } 126 | 127 | var countdown *time.Timer 128 | 129 | func daemonMainLoop() { 130 | if gSocket == "" { 131 | gSocket = getSocketFilename() 132 | } 133 | 134 | // Register RPC 135 | server := new(Server) 136 | rpc.Register(server) 137 | 138 | // Open the socket. 139 | if !gDisableRemoveSocket { 140 | if err := os.Remove(gSocket); err == nil { 141 | log.Printf("Removed existing socket") 142 | } 143 | } 144 | log.Printf("Opening socket at %s", gSocket) 145 | listener, err := net.Listen("unix", gSocket) 146 | panicIfError(err) 147 | defer func() { 148 | panicIfError(listener.Close()) 149 | }() 150 | 151 | // goroutine that listens for new connections. 152 | conn := make(chan net.Conn) 153 | go func() { 154 | for { 155 | c, e := listener.Accept() 156 | if e != nil { 157 | if !gShutdown { 158 | log.Printf("%s", e.Error()) 159 | } 160 | gShutdown = true 161 | return 162 | } 163 | conn <- c 164 | } 165 | }() 166 | 167 | // Main loop. Handles incoming requests. 168 | timeout := time.Duration(gTimeout) * time.Second 169 | countdown = time.NewTimer(timeout) 170 | loop: 171 | for { 172 | select { 173 | case c := <-conn: 174 | rpc.ServeConn(c) 175 | 176 | if gShutdown { 177 | break loop 178 | } 179 | 180 | countdown.Reset(timeout) 181 | runtime.GC() 182 | 183 | case closed := <-languageServerClosed: 184 | i := 0 185 | for i < len(server.servers) { 186 | if server.servers[i] == closed { 187 | if closed.err == nil { 188 | log.Printf("Language server %+v in %s has closed", closed.cmd.Args, closed.directory) 189 | } else { 190 | log.Printf("Language server %+v in %s has closed (err=%s)", closed.cmd.Args, closed.directory, closed.err.Error()) 191 | } 192 | server.servers = append(server.servers[:i], server.servers[i+1:]...) 193 | } else { 194 | i++ 195 | } 196 | } 197 | 198 | case <-countdown.C: 199 | break loop 200 | } 201 | } 202 | } 203 | 204 | func ensureDaemon() { 205 | // FIXME: add logic for this 206 | 207 | // Early-exit if the socket exists already. 208 | if fileExists(gSocket) { 209 | return 210 | } 211 | 212 | log.Printf("Starting daemon") 213 | 214 | path, err := os.Executable() 215 | panicIfError(err) 216 | 217 | p := exec.Command(path, "-socket", gSocket, "daemon") 218 | err = p.Start() 219 | panicIfError(err) 220 | } 221 | 222 | func doRPC(serviceMethod string, args interface{}, reply interface{}) { 223 | // Fetch the socket filename if not specified 224 | if len(gSocket) == 0 { 225 | gSocket = getSocketFilename() 226 | } 227 | 228 | // Try to connect. If it fails, start a server. 229 | conn, e := rpc.Dial("unix", gSocket) 230 | if e != nil { 231 | ensureDaemon() 232 | 233 | // Try to connect again if we after waiting a bit for the server to start. 234 | // FIXME: is there a more robust approach here? 235 | time.Sleep(time.Millisecond * 250) 236 | 237 | conn, e = rpc.Dial("unix", gSocket) 238 | if e != nil { 239 | fmt.Printf("Unable to connect to socket: %s\n", e.Error()) 240 | os.Exit(2) 241 | } 242 | } 243 | 244 | e = conn.Call(serviceMethod, args, reply) 245 | conn.Close() 246 | 247 | if e != nil { 248 | fmt.Printf("error during rpc: %s", e.Error()) 249 | os.Exit(1) 250 | } 251 | } 252 | 253 | var gSocket string 254 | var gDisableRemoveSocket bool 255 | var gTimeout int 256 | 257 | func main() { 258 | app := cli.NewApp() 259 | app.Name = "lspc" 260 | app.Usage = "language server protocol client" 261 | app.Description = 262 | `lspc manages language servers and provides convenient APIs to interact with 263 | those language servers` 264 | 265 | app.Flags = []cli.Flag{ 266 | cli.StringFlag{ 267 | Name: "socket", 268 | Usage: "Path to a socket that lspc will use to communicate with the daemon.", 269 | EnvVar: "LSPC_SOCKET", 270 | Destination: &gSocket, 271 | }, 272 | cli.BoolFlag{ 273 | Name: "disable-remove-socket", 274 | Usage: "Do not try to remove the socket if it already exists.", 275 | EnvVar: "LSPC_REMOVE_SOCKET", 276 | Destination: &gDisableRemoveSocket, 277 | }, 278 | cli.IntFlag{ 279 | Name: "timeout", 280 | Usage: "Seconds until the server shuts down. Reset whenever the server receives a request", 281 | EnvVar: "LSPC_TIMEOUT", 282 | Value: 60 * 30, 283 | Destination: &gTimeout, 284 | }, 285 | } 286 | 287 | app.Commands = []cli.Command{ 288 | { 289 | Name: "keep-alive", 290 | Description: "Send a ping to the server to make sure it does not shut down", 291 | Action: func(c *cli.Context) error { 292 | doRPC("Server.KeepAlive", false, nil) 293 | return nil 294 | }, 295 | }, 296 | { 297 | Name: "kill", 298 | Description: "Shut the server down", 299 | Action: func(c *cli.Context) error { 300 | doRPC("Server.Kill", false, nil) 301 | return nil 302 | }, 303 | }, 304 | { 305 | Name: "server-pid", 306 | Description: "Shut the server down", 307 | Action: func(c *cli.Context) error { 308 | var pid int 309 | doRPC("Server.ServerPid", false, &pid) 310 | println(pid) 311 | return nil 312 | }, 313 | }, 314 | { 315 | Name: "ls", 316 | Description: "List all running language servers", 317 | Action: func(c *cli.Context) error { 318 | var servers []string 319 | doRPC("Server.Ls", false, &servers) 320 | for _, server := range servers { 321 | println(server) 322 | } 323 | return nil 324 | }, 325 | }, 326 | { 327 | Name: "start", 328 | Usage: "start a new language server", 329 | UsageText: "lspc start []", 330 | Description: ` can be a quoted string which will be parsed as shell words, ie, 331 | "cquery --log-file log.txt" will run cquery with the arguments [--log-file, log.txt] 332 | 333 | is the directory containing the content that the language server will 334 | analyze 335 | 336 | can be a raw json literal passed to the language server in the initialization 337 | message, ex, '{"cacheDirectory": "/ssd/cquery_cache/"}'. Defaults to {} 338 | 339 | Example: 340 | $ lspc start "cquery --log-all-to-stderr" /work/chrome '{"cacheDirectory": "/ssd/cquery_cache"}'`, 341 | Action: func(c *cli.Context) error { 342 | if c.NArg() != 2 && c.NArg() != 3 { 343 | return cli.ShowCommandHelp(c, "start") 344 | } 345 | 346 | init := c.Args().Get(2) 347 | if init == "" { 348 | init = "{}" 349 | } 350 | args := StartArgs{ 351 | Bin: c.Args().Get(0), 352 | Directory: c.Args().Get(1), 353 | InitOpts: easyjson.RawMessage(init), 354 | } 355 | 356 | doRPC("Server.Start", args, nil) 357 | return nil 358 | }, 359 | }, 360 | { 361 | Name: "daemon", 362 | Usage: "run the lspc daemon", 363 | UsageText: "lspc daemon", 364 | Description: "Run the lspc daemon. In typical operation lspc will start the daemon for you.", 365 | Action: func(c *cli.Context) error { 366 | daemonMainLoop() 367 | return nil 368 | }, 369 | }, 370 | } 371 | 372 | err := app.Run(os.Args) 373 | if err != nil { 374 | log.Fatal(err) 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /msg_types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jacob Dufault 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "github.com/mailru/easyjson" 19 | ) 20 | 21 | //go:generate easyjson -all 22 | 23 | type LsDocumentURI string 24 | 25 | type LsPosition struct { 26 | // Note: these are 0-based. 27 | Line int `json:"line"` 28 | Character int `json:"character"` 29 | } 30 | 31 | type LsRange struct { 32 | Start LsPosition `json:"start"` 33 | End LsPosition `json:"end"` 34 | } 35 | 36 | type LsLocation struct { 37 | URI LsDocumentURI `json:"uri"` 38 | Range LsRange `json:"range"` 39 | } 40 | 41 | type LsSymbolKind int 42 | 43 | const ( 44 | Unknown LsSymbolKind = 0 45 | File LsSymbolKind = 1 46 | Module LsSymbolKind = 2 47 | Namespace LsSymbolKind = 3 48 | Package LsSymbolKind = 4 49 | Class LsSymbolKind = 5 50 | Method LsSymbolKind = 6 51 | Property LsSymbolKind = 7 52 | Field LsSymbolKind = 8 53 | Constructor LsSymbolKind = 9 54 | Enum LsSymbolKind = 10 55 | Interface LsSymbolKind = 11 56 | Function LsSymbolKind = 12 57 | Variable LsSymbolKind = 13 58 | Constant LsSymbolKind = 14 59 | String LsSymbolKind = 15 60 | Number LsSymbolKind = 16 61 | Boolean LsSymbolKind = 17 62 | Array LsSymbolKind = 18 63 | Object LsSymbolKind = 19 64 | Key LsSymbolKind = 20 65 | Null LsSymbolKind = 21 66 | EnumMember LsSymbolKind = 22 67 | Struct LsSymbolKind = 23 68 | Event LsSymbolKind = 24 69 | Operator LsSymbolKind = 25 70 | TypeParameter LsSymbolKind = 26 71 | ) 72 | 73 | type LsTextDocumentIdentifier struct { 74 | URI LsDocumentURI `json:"uri"` 75 | } 76 | 77 | type LsVersionedTextDocumentIdentifier struct { 78 | URI LsDocumentURI `json:"uri"` 79 | // Version number of this document. number | null 80 | Version *int `json:"version"` 81 | } 82 | 83 | type LsTextDocumentPositionParams struct { 84 | // The text document. 85 | TextDocument LsTextDocumentIdentifier `json:"textDocument"` 86 | // The position inside the text document. 87 | Position LsPosition `json:"position"` 88 | } 89 | 90 | type LsTextEdit struct { 91 | // The range of the text document to be manipulated. To insert 92 | // text into a document create a range where start === end. 93 | Range LsRange `json:"range"` 94 | 95 | // The string to be inserted. For delete operations use an 96 | // empty string. 97 | NewText string `json:"newText"` 98 | } 99 | 100 | type LsTextDocumentItem struct { 101 | // The text document's URI. 102 | URI LsDocumentURI `json:"uri"` 103 | 104 | // The text document's language identifier. 105 | LanguageID string `json:"languageId"` 106 | 107 | // The version number of this document (it will strictly increase after each 108 | // change, including undo/redo). 109 | Version int `json:"version"` 110 | 111 | // The content of the opened text document. 112 | Text string `json:"text"` 113 | } 114 | 115 | type LsInitializeParams struct { 116 | /** 117 | * The process Id of the parent process that started 118 | * the server. Is null if the process has not been started by another process. 119 | * If the parent process is not alive then the server should exit (see exit notification) its process. 120 | */ 121 | // processId number | null; 122 | 123 | /** 124 | * The rootPath of the workspace. Is null 125 | * if no folder is open. 126 | * 127 | * @deprecated in favour of rootUri. 128 | */ 129 | // rootPath?: string | null; 130 | 131 | /** 132 | * The rootUri of the workspace. Is null if no 133 | * folder is open. If both `rootPath` and `rootUri` are set 134 | * `rootUri` wins. 135 | */ 136 | RootURI LsDocumentURI `json:"rootUri"` 137 | 138 | /** 139 | * User provided initialization options. 140 | */ 141 | InitializationOptions easyjson.RawMessage `json:"initializationOptions"` 142 | 143 | /** 144 | * The capabilities provided by the client (editor or tool) 145 | */ 146 | // capabilities: ClientCapabilities; 147 | 148 | /** 149 | * The initial trace setting. If omitted trace is disabled ('off'). 150 | */ 151 | // trace?: 'off' | 'messages' | 'verbose'; 152 | 153 | /** 154 | * The workspace folders configured in the client when the server starts. 155 | * This property is only available if the client supports workspace folders. 156 | * It can be `null` if the client supports workspace folders but none are 157 | * configured. 158 | * 159 | * Since 3.6.0 160 | */ 161 | // workspaceFolders?: WorkspaceFolder[] | null; 162 | } 163 | 164 | // RequestID is the id of a request/response 165 | type RequestID int 166 | 167 | // IsDefined implements easyjson.Optional which handles the omitempty logic. 168 | func (r *RequestID) IsDefined() bool { 169 | return *r >= 0 170 | } 171 | 172 | // JSONRPCHeader is used to identify a message. 173 | type JSONRPCHeader struct { 174 | JSONRPC string `json:"jsonrpc"` // Should be "2.0" 175 | Method string `json:"method"` // ie, "textDocument/codeLens" 176 | ID RequestID `json:"id,omitempty"` 177 | Params easyjson.RawMessage `json:"params"` 178 | } 179 | 180 | // NotificationInitialized is sent from the server to the client after the 181 | // client is ready to go. 182 | type NotificationInitialized struct{} 183 | 184 | /* 185 | 186 | struct lsResponseError { 187 | enum class lsErrorCodes : int { 188 | ParseError = -32700, 189 | InvalidRequest = -32600, 190 | MethodNotFound = -32601, 191 | InvalidParams = -32602, 192 | InternalError = -32603, 193 | serverErrorStart = -32099, 194 | serverErrorEnd = -32000, 195 | ServerNotInitialized = -32002, 196 | UnknownErrorCode = -32001, 197 | RequestCancelled = -32800, 198 | }; 199 | 200 | lsErrorCodes code; 201 | // Short description. 202 | std::string message; 203 | 204 | void Write(Writer& visitor); 205 | }; 206 | 207 | 208 | // cquery extension 209 | struct lsLocationEx : lsLocation { 210 | optional containerName; 211 | optional parentKind; 212 | // Avoid circular dependency on symbol.h 213 | optional role; 214 | }; 215 | MAKE_REFLECT_STRUCT(lsLocationEx, uri, range, containerName, parentKind, role); 216 | 217 | template 218 | struct lsCommand { 219 | // Title of the command (ie, 'save') 220 | std::string title; 221 | // Actual command identifier. 222 | std::string command; 223 | // Arguments to run the command with. 224 | // **NOTE** This must be serialized as an array. Use 225 | // MAKE_REFLECT_STRUCT_WRITER_AS_ARRAY. 226 | T arguments; 227 | }; 228 | 229 | template 230 | struct lsCodeLens { 231 | // The range in which this code lens is valid. Should only span a single line. 232 | lsRange range; 233 | // The command this code lens represents. 234 | optional> command; 235 | // A data entry field that is preserved on a code lens item between 236 | // a code lens and a code lens resolve request. 237 | TData data; 238 | }; 239 | 240 | struct lsTextDocumentEdit { 241 | // The text document to change. 242 | lsVersionedTextDocumentIdentifier textDocument; 243 | 244 | // The edits to be applied. 245 | std::vector edits; 246 | }; 247 | MAKE_REFLECT_STRUCT(lsTextDocumentEdit, textDocument, edits); 248 | 249 | struct lsWorkspaceEdit { 250 | // Holds changes to existing resources. 251 | // changes ? : { [uri:string]: TextEdit[]; }; 252 | // std::unordered_map> changes; 253 | 254 | // An array of `TextDocumentEdit`s to express changes to specific a specific 255 | // version of a text document. Whether a client supports versioned document 256 | // edits is expressed via `WorkspaceClientCapabilites.versionedWorkspaceEdit`. 257 | std::vector documentChanges; 258 | }; 259 | MAKE_REFLECT_STRUCT(lsWorkspaceEdit, documentChanges); 260 | 261 | struct lsFormattingOptions { 262 | // Size of a tab in spaces. 263 | int tabSize; 264 | // Prefer spaces over tabs. 265 | bool insertSpaces; 266 | }; 267 | MAKE_REFLECT_STRUCT(lsFormattingOptions, tabSize, insertSpaces); 268 | 269 | // MarkedString can be used to render human readable text. It is either a 270 | // markdown string or a code-block that provides a language and a code snippet. 271 | // The language identifier is sematically equal to the optional language 272 | // identifier in fenced code blocks in GitHub issues. See 273 | // https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting 274 | // 275 | // The pair of a language and a value is an equivalent to markdown: 276 | // ```${language} 277 | // ${value} 278 | // ``` 279 | // 280 | // Note that markdown strings will be sanitized - that means html will be 281 | // escaped. 282 | struct lsMarkedString { 283 | optional language; 284 | std::string value; 285 | }; 286 | void Reflect(Writer& visitor, lsMarkedString& value); 287 | 288 | struct lsTextDocumentContentChangeEvent { 289 | // The range of the document that changed. 290 | optional range; 291 | // The length of the range that got replaced. 292 | optional rangeLength; 293 | // The new text of the range/document. 294 | std::string text; 295 | }; 296 | MAKE_REFLECT_STRUCT(lsTextDocumentContentChangeEvent, range, rangeLength, text); 297 | 298 | struct lsTextDocumentDidChangeParams { 299 | lsVersionedTextDocumentIdentifier textDocument; 300 | std::vector contentChanges; 301 | }; 302 | MAKE_REFLECT_STRUCT(lsTextDocumentDidChangeParams, 303 | textDocument, 304 | contentChanges); 305 | 306 | // Show a message to the user. 307 | enum class lsMessageType : int { Error = 1, Warning = 2, Info = 3, Log = 4 }; 308 | MAKE_REFLECT_TYPE_PROXY(lsMessageType) 309 | struct Out_ShowLogMessageParams { 310 | lsMessageType type = lsMessageType::Error; 311 | std::string message; 312 | }; 313 | MAKE_REFLECT_STRUCT(Out_ShowLogMessageParams, type, message); 314 | struct Out_ShowLogMessage : public lsOutMessage { 315 | enum class DisplayType { Show, Log }; 316 | DisplayType display_type = DisplayType::Show; 317 | 318 | std::string method(); 319 | Out_ShowLogMessageParams params; 320 | }; 321 | template 322 | void Reflect(TVisitor& visitor, Out_ShowLogMessage& value) { 323 | REFLECT_MEMBER_START(); 324 | REFLECT_MEMBER(jsonrpc); 325 | std::string method = value.method(); 326 | REFLECT_MEMBER2("method", method); 327 | REFLECT_MEMBER(params); 328 | REFLECT_MEMBER_END(); 329 | } 330 | 331 | struct Out_LocationList : public lsOutMessage { 332 | lsRequestId id; 333 | std::vector result; 334 | }; 335 | MAKE_REFLECT_STRUCT(Out_LocationList, jsonrpc, id, result); 336 | 337 | 338 | */ 339 | -------------------------------------------------------------------------------- /msg_types_easyjson.go: -------------------------------------------------------------------------------- 1 | // Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | json "encoding/json" 7 | easyjson "github.com/mailru/easyjson" 8 | jlexer "github.com/mailru/easyjson/jlexer" 9 | jwriter "github.com/mailru/easyjson/jwriter" 10 | ) 11 | 12 | // suppress unused package warning 13 | var ( 14 | _ *json.RawMessage 15 | _ *jlexer.Lexer 16 | _ *jwriter.Writer 17 | _ easyjson.Marshaler 18 | ) 19 | 20 | func easyjsonC38a4abDecodeGithubComJacobdufaultLspc(in *jlexer.Lexer, out *NotificationInitialized) { 21 | isTopLevel := in.IsStart() 22 | if in.IsNull() { 23 | if isTopLevel { 24 | in.Consumed() 25 | } 26 | in.Skip() 27 | return 28 | } 29 | in.Delim('{') 30 | for !in.IsDelim('}') { 31 | key := in.UnsafeString() 32 | in.WantColon() 33 | if in.IsNull() { 34 | in.Skip() 35 | in.WantComma() 36 | continue 37 | } 38 | switch key { 39 | default: 40 | in.SkipRecursive() 41 | } 42 | in.WantComma() 43 | } 44 | in.Delim('}') 45 | if isTopLevel { 46 | in.Consumed() 47 | } 48 | } 49 | func easyjsonC38a4abEncodeGithubComJacobdufaultLspc(out *jwriter.Writer, in NotificationInitialized) { 50 | out.RawByte('{') 51 | first := true 52 | _ = first 53 | out.RawByte('}') 54 | } 55 | 56 | // MarshalJSON supports json.Marshaler interface 57 | func (v NotificationInitialized) MarshalJSON() ([]byte, error) { 58 | w := jwriter.Writer{} 59 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc(&w, v) 60 | return w.Buffer.BuildBytes(), w.Error 61 | } 62 | 63 | // MarshalEasyJSON supports easyjson.Marshaler interface 64 | func (v NotificationInitialized) MarshalEasyJSON(w *jwriter.Writer) { 65 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc(w, v) 66 | } 67 | 68 | // UnmarshalJSON supports json.Unmarshaler interface 69 | func (v *NotificationInitialized) UnmarshalJSON(data []byte) error { 70 | r := jlexer.Lexer{Data: data} 71 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc(&r, v) 72 | return r.Error() 73 | } 74 | 75 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 76 | func (v *NotificationInitialized) UnmarshalEasyJSON(l *jlexer.Lexer) { 77 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc(l, v) 78 | } 79 | func easyjsonC38a4abDecodeGithubComJacobdufaultLspc1(in *jlexer.Lexer, out *LsVersionedTextDocumentIdentifier) { 80 | isTopLevel := in.IsStart() 81 | if in.IsNull() { 82 | if isTopLevel { 83 | in.Consumed() 84 | } 85 | in.Skip() 86 | return 87 | } 88 | in.Delim('{') 89 | for !in.IsDelim('}') { 90 | key := in.UnsafeString() 91 | in.WantColon() 92 | if in.IsNull() { 93 | in.Skip() 94 | in.WantComma() 95 | continue 96 | } 97 | switch key { 98 | case "uri": 99 | out.URI = LsDocumentURI(in.String()) 100 | case "version": 101 | if in.IsNull() { 102 | in.Skip() 103 | out.Version = nil 104 | } else { 105 | if out.Version == nil { 106 | out.Version = new(int) 107 | } 108 | *out.Version = int(in.Int()) 109 | } 110 | default: 111 | in.SkipRecursive() 112 | } 113 | in.WantComma() 114 | } 115 | in.Delim('}') 116 | if isTopLevel { 117 | in.Consumed() 118 | } 119 | } 120 | func easyjsonC38a4abEncodeGithubComJacobdufaultLspc1(out *jwriter.Writer, in LsVersionedTextDocumentIdentifier) { 121 | out.RawByte('{') 122 | first := true 123 | _ = first 124 | { 125 | const prefix string = ",\"uri\":" 126 | if first { 127 | first = false 128 | out.RawString(prefix[1:]) 129 | } else { 130 | out.RawString(prefix) 131 | } 132 | out.String(string(in.URI)) 133 | } 134 | { 135 | const prefix string = ",\"version\":" 136 | if first { 137 | first = false 138 | out.RawString(prefix[1:]) 139 | } else { 140 | out.RawString(prefix) 141 | } 142 | if in.Version == nil { 143 | out.RawString("null") 144 | } else { 145 | out.Int(int(*in.Version)) 146 | } 147 | } 148 | out.RawByte('}') 149 | } 150 | 151 | // MarshalJSON supports json.Marshaler interface 152 | func (v LsVersionedTextDocumentIdentifier) MarshalJSON() ([]byte, error) { 153 | w := jwriter.Writer{} 154 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc1(&w, v) 155 | return w.Buffer.BuildBytes(), w.Error 156 | } 157 | 158 | // MarshalEasyJSON supports easyjson.Marshaler interface 159 | func (v LsVersionedTextDocumentIdentifier) MarshalEasyJSON(w *jwriter.Writer) { 160 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc1(w, v) 161 | } 162 | 163 | // UnmarshalJSON supports json.Unmarshaler interface 164 | func (v *LsVersionedTextDocumentIdentifier) UnmarshalJSON(data []byte) error { 165 | r := jlexer.Lexer{Data: data} 166 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc1(&r, v) 167 | return r.Error() 168 | } 169 | 170 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 171 | func (v *LsVersionedTextDocumentIdentifier) UnmarshalEasyJSON(l *jlexer.Lexer) { 172 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc1(l, v) 173 | } 174 | func easyjsonC38a4abDecodeGithubComJacobdufaultLspc2(in *jlexer.Lexer, out *LsTextEdit) { 175 | isTopLevel := in.IsStart() 176 | if in.IsNull() { 177 | if isTopLevel { 178 | in.Consumed() 179 | } 180 | in.Skip() 181 | return 182 | } 183 | in.Delim('{') 184 | for !in.IsDelim('}') { 185 | key := in.UnsafeString() 186 | in.WantColon() 187 | if in.IsNull() { 188 | in.Skip() 189 | in.WantComma() 190 | continue 191 | } 192 | switch key { 193 | case "range": 194 | (out.Range).UnmarshalEasyJSON(in) 195 | case "newText": 196 | out.NewText = string(in.String()) 197 | default: 198 | in.SkipRecursive() 199 | } 200 | in.WantComma() 201 | } 202 | in.Delim('}') 203 | if isTopLevel { 204 | in.Consumed() 205 | } 206 | } 207 | func easyjsonC38a4abEncodeGithubComJacobdufaultLspc2(out *jwriter.Writer, in LsTextEdit) { 208 | out.RawByte('{') 209 | first := true 210 | _ = first 211 | { 212 | const prefix string = ",\"range\":" 213 | if first { 214 | first = false 215 | out.RawString(prefix[1:]) 216 | } else { 217 | out.RawString(prefix) 218 | } 219 | (in.Range).MarshalEasyJSON(out) 220 | } 221 | { 222 | const prefix string = ",\"newText\":" 223 | if first { 224 | first = false 225 | out.RawString(prefix[1:]) 226 | } else { 227 | out.RawString(prefix) 228 | } 229 | out.String(string(in.NewText)) 230 | } 231 | out.RawByte('}') 232 | } 233 | 234 | // MarshalJSON supports json.Marshaler interface 235 | func (v LsTextEdit) MarshalJSON() ([]byte, error) { 236 | w := jwriter.Writer{} 237 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc2(&w, v) 238 | return w.Buffer.BuildBytes(), w.Error 239 | } 240 | 241 | // MarshalEasyJSON supports easyjson.Marshaler interface 242 | func (v LsTextEdit) MarshalEasyJSON(w *jwriter.Writer) { 243 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc2(w, v) 244 | } 245 | 246 | // UnmarshalJSON supports json.Unmarshaler interface 247 | func (v *LsTextEdit) UnmarshalJSON(data []byte) error { 248 | r := jlexer.Lexer{Data: data} 249 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc2(&r, v) 250 | return r.Error() 251 | } 252 | 253 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 254 | func (v *LsTextEdit) UnmarshalEasyJSON(l *jlexer.Lexer) { 255 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc2(l, v) 256 | } 257 | func easyjsonC38a4abDecodeGithubComJacobdufaultLspc3(in *jlexer.Lexer, out *LsTextDocumentPositionParams) { 258 | isTopLevel := in.IsStart() 259 | if in.IsNull() { 260 | if isTopLevel { 261 | in.Consumed() 262 | } 263 | in.Skip() 264 | return 265 | } 266 | in.Delim('{') 267 | for !in.IsDelim('}') { 268 | key := in.UnsafeString() 269 | in.WantColon() 270 | if in.IsNull() { 271 | in.Skip() 272 | in.WantComma() 273 | continue 274 | } 275 | switch key { 276 | case "textDocument": 277 | (out.TextDocument).UnmarshalEasyJSON(in) 278 | case "position": 279 | (out.Position).UnmarshalEasyJSON(in) 280 | default: 281 | in.SkipRecursive() 282 | } 283 | in.WantComma() 284 | } 285 | in.Delim('}') 286 | if isTopLevel { 287 | in.Consumed() 288 | } 289 | } 290 | func easyjsonC38a4abEncodeGithubComJacobdufaultLspc3(out *jwriter.Writer, in LsTextDocumentPositionParams) { 291 | out.RawByte('{') 292 | first := true 293 | _ = first 294 | { 295 | const prefix string = ",\"textDocument\":" 296 | if first { 297 | first = false 298 | out.RawString(prefix[1:]) 299 | } else { 300 | out.RawString(prefix) 301 | } 302 | (in.TextDocument).MarshalEasyJSON(out) 303 | } 304 | { 305 | const prefix string = ",\"position\":" 306 | if first { 307 | first = false 308 | out.RawString(prefix[1:]) 309 | } else { 310 | out.RawString(prefix) 311 | } 312 | (in.Position).MarshalEasyJSON(out) 313 | } 314 | out.RawByte('}') 315 | } 316 | 317 | // MarshalJSON supports json.Marshaler interface 318 | func (v LsTextDocumentPositionParams) MarshalJSON() ([]byte, error) { 319 | w := jwriter.Writer{} 320 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc3(&w, v) 321 | return w.Buffer.BuildBytes(), w.Error 322 | } 323 | 324 | // MarshalEasyJSON supports easyjson.Marshaler interface 325 | func (v LsTextDocumentPositionParams) MarshalEasyJSON(w *jwriter.Writer) { 326 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc3(w, v) 327 | } 328 | 329 | // UnmarshalJSON supports json.Unmarshaler interface 330 | func (v *LsTextDocumentPositionParams) UnmarshalJSON(data []byte) error { 331 | r := jlexer.Lexer{Data: data} 332 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc3(&r, v) 333 | return r.Error() 334 | } 335 | 336 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 337 | func (v *LsTextDocumentPositionParams) UnmarshalEasyJSON(l *jlexer.Lexer) { 338 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc3(l, v) 339 | } 340 | func easyjsonC38a4abDecodeGithubComJacobdufaultLspc4(in *jlexer.Lexer, out *LsTextDocumentItem) { 341 | isTopLevel := in.IsStart() 342 | if in.IsNull() { 343 | if isTopLevel { 344 | in.Consumed() 345 | } 346 | in.Skip() 347 | return 348 | } 349 | in.Delim('{') 350 | for !in.IsDelim('}') { 351 | key := in.UnsafeString() 352 | in.WantColon() 353 | if in.IsNull() { 354 | in.Skip() 355 | in.WantComma() 356 | continue 357 | } 358 | switch key { 359 | case "uri": 360 | out.URI = LsDocumentURI(in.String()) 361 | case "languageId": 362 | out.LanguageID = string(in.String()) 363 | case "version": 364 | out.Version = int(in.Int()) 365 | case "text": 366 | out.Text = string(in.String()) 367 | default: 368 | in.SkipRecursive() 369 | } 370 | in.WantComma() 371 | } 372 | in.Delim('}') 373 | if isTopLevel { 374 | in.Consumed() 375 | } 376 | } 377 | func easyjsonC38a4abEncodeGithubComJacobdufaultLspc4(out *jwriter.Writer, in LsTextDocumentItem) { 378 | out.RawByte('{') 379 | first := true 380 | _ = first 381 | { 382 | const prefix string = ",\"uri\":" 383 | if first { 384 | first = false 385 | out.RawString(prefix[1:]) 386 | } else { 387 | out.RawString(prefix) 388 | } 389 | out.String(string(in.URI)) 390 | } 391 | { 392 | const prefix string = ",\"languageId\":" 393 | if first { 394 | first = false 395 | out.RawString(prefix[1:]) 396 | } else { 397 | out.RawString(prefix) 398 | } 399 | out.String(string(in.LanguageID)) 400 | } 401 | { 402 | const prefix string = ",\"version\":" 403 | if first { 404 | first = false 405 | out.RawString(prefix[1:]) 406 | } else { 407 | out.RawString(prefix) 408 | } 409 | out.Int(int(in.Version)) 410 | } 411 | { 412 | const prefix string = ",\"text\":" 413 | if first { 414 | first = false 415 | out.RawString(prefix[1:]) 416 | } else { 417 | out.RawString(prefix) 418 | } 419 | out.String(string(in.Text)) 420 | } 421 | out.RawByte('}') 422 | } 423 | 424 | // MarshalJSON supports json.Marshaler interface 425 | func (v LsTextDocumentItem) MarshalJSON() ([]byte, error) { 426 | w := jwriter.Writer{} 427 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc4(&w, v) 428 | return w.Buffer.BuildBytes(), w.Error 429 | } 430 | 431 | // MarshalEasyJSON supports easyjson.Marshaler interface 432 | func (v LsTextDocumentItem) MarshalEasyJSON(w *jwriter.Writer) { 433 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc4(w, v) 434 | } 435 | 436 | // UnmarshalJSON supports json.Unmarshaler interface 437 | func (v *LsTextDocumentItem) UnmarshalJSON(data []byte) error { 438 | r := jlexer.Lexer{Data: data} 439 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc4(&r, v) 440 | return r.Error() 441 | } 442 | 443 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 444 | func (v *LsTextDocumentItem) UnmarshalEasyJSON(l *jlexer.Lexer) { 445 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc4(l, v) 446 | } 447 | func easyjsonC38a4abDecodeGithubComJacobdufaultLspc5(in *jlexer.Lexer, out *LsTextDocumentIdentifier) { 448 | isTopLevel := in.IsStart() 449 | if in.IsNull() { 450 | if isTopLevel { 451 | in.Consumed() 452 | } 453 | in.Skip() 454 | return 455 | } 456 | in.Delim('{') 457 | for !in.IsDelim('}') { 458 | key := in.UnsafeString() 459 | in.WantColon() 460 | if in.IsNull() { 461 | in.Skip() 462 | in.WantComma() 463 | continue 464 | } 465 | switch key { 466 | case "uri": 467 | out.URI = LsDocumentURI(in.String()) 468 | default: 469 | in.SkipRecursive() 470 | } 471 | in.WantComma() 472 | } 473 | in.Delim('}') 474 | if isTopLevel { 475 | in.Consumed() 476 | } 477 | } 478 | func easyjsonC38a4abEncodeGithubComJacobdufaultLspc5(out *jwriter.Writer, in LsTextDocumentIdentifier) { 479 | out.RawByte('{') 480 | first := true 481 | _ = first 482 | { 483 | const prefix string = ",\"uri\":" 484 | if first { 485 | first = false 486 | out.RawString(prefix[1:]) 487 | } else { 488 | out.RawString(prefix) 489 | } 490 | out.String(string(in.URI)) 491 | } 492 | out.RawByte('}') 493 | } 494 | 495 | // MarshalJSON supports json.Marshaler interface 496 | func (v LsTextDocumentIdentifier) MarshalJSON() ([]byte, error) { 497 | w := jwriter.Writer{} 498 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc5(&w, v) 499 | return w.Buffer.BuildBytes(), w.Error 500 | } 501 | 502 | // MarshalEasyJSON supports easyjson.Marshaler interface 503 | func (v LsTextDocumentIdentifier) MarshalEasyJSON(w *jwriter.Writer) { 504 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc5(w, v) 505 | } 506 | 507 | // UnmarshalJSON supports json.Unmarshaler interface 508 | func (v *LsTextDocumentIdentifier) UnmarshalJSON(data []byte) error { 509 | r := jlexer.Lexer{Data: data} 510 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc5(&r, v) 511 | return r.Error() 512 | } 513 | 514 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 515 | func (v *LsTextDocumentIdentifier) UnmarshalEasyJSON(l *jlexer.Lexer) { 516 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc5(l, v) 517 | } 518 | func easyjsonC38a4abDecodeGithubComJacobdufaultLspc6(in *jlexer.Lexer, out *LsRange) { 519 | isTopLevel := in.IsStart() 520 | if in.IsNull() { 521 | if isTopLevel { 522 | in.Consumed() 523 | } 524 | in.Skip() 525 | return 526 | } 527 | in.Delim('{') 528 | for !in.IsDelim('}') { 529 | key := in.UnsafeString() 530 | in.WantColon() 531 | if in.IsNull() { 532 | in.Skip() 533 | in.WantComma() 534 | continue 535 | } 536 | switch key { 537 | case "start": 538 | (out.Start).UnmarshalEasyJSON(in) 539 | case "end": 540 | (out.End).UnmarshalEasyJSON(in) 541 | default: 542 | in.SkipRecursive() 543 | } 544 | in.WantComma() 545 | } 546 | in.Delim('}') 547 | if isTopLevel { 548 | in.Consumed() 549 | } 550 | } 551 | func easyjsonC38a4abEncodeGithubComJacobdufaultLspc6(out *jwriter.Writer, in LsRange) { 552 | out.RawByte('{') 553 | first := true 554 | _ = first 555 | { 556 | const prefix string = ",\"start\":" 557 | if first { 558 | first = false 559 | out.RawString(prefix[1:]) 560 | } else { 561 | out.RawString(prefix) 562 | } 563 | (in.Start).MarshalEasyJSON(out) 564 | } 565 | { 566 | const prefix string = ",\"end\":" 567 | if first { 568 | first = false 569 | out.RawString(prefix[1:]) 570 | } else { 571 | out.RawString(prefix) 572 | } 573 | (in.End).MarshalEasyJSON(out) 574 | } 575 | out.RawByte('}') 576 | } 577 | 578 | // MarshalJSON supports json.Marshaler interface 579 | func (v LsRange) MarshalJSON() ([]byte, error) { 580 | w := jwriter.Writer{} 581 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc6(&w, v) 582 | return w.Buffer.BuildBytes(), w.Error 583 | } 584 | 585 | // MarshalEasyJSON supports easyjson.Marshaler interface 586 | func (v LsRange) MarshalEasyJSON(w *jwriter.Writer) { 587 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc6(w, v) 588 | } 589 | 590 | // UnmarshalJSON supports json.Unmarshaler interface 591 | func (v *LsRange) UnmarshalJSON(data []byte) error { 592 | r := jlexer.Lexer{Data: data} 593 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc6(&r, v) 594 | return r.Error() 595 | } 596 | 597 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 598 | func (v *LsRange) UnmarshalEasyJSON(l *jlexer.Lexer) { 599 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc6(l, v) 600 | } 601 | func easyjsonC38a4abDecodeGithubComJacobdufaultLspc7(in *jlexer.Lexer, out *LsPosition) { 602 | isTopLevel := in.IsStart() 603 | if in.IsNull() { 604 | if isTopLevel { 605 | in.Consumed() 606 | } 607 | in.Skip() 608 | return 609 | } 610 | in.Delim('{') 611 | for !in.IsDelim('}') { 612 | key := in.UnsafeString() 613 | in.WantColon() 614 | if in.IsNull() { 615 | in.Skip() 616 | in.WantComma() 617 | continue 618 | } 619 | switch key { 620 | case "line": 621 | out.Line = int(in.Int()) 622 | case "character": 623 | out.Character = int(in.Int()) 624 | default: 625 | in.SkipRecursive() 626 | } 627 | in.WantComma() 628 | } 629 | in.Delim('}') 630 | if isTopLevel { 631 | in.Consumed() 632 | } 633 | } 634 | func easyjsonC38a4abEncodeGithubComJacobdufaultLspc7(out *jwriter.Writer, in LsPosition) { 635 | out.RawByte('{') 636 | first := true 637 | _ = first 638 | { 639 | const prefix string = ",\"line\":" 640 | if first { 641 | first = false 642 | out.RawString(prefix[1:]) 643 | } else { 644 | out.RawString(prefix) 645 | } 646 | out.Int(int(in.Line)) 647 | } 648 | { 649 | const prefix string = ",\"character\":" 650 | if first { 651 | first = false 652 | out.RawString(prefix[1:]) 653 | } else { 654 | out.RawString(prefix) 655 | } 656 | out.Int(int(in.Character)) 657 | } 658 | out.RawByte('}') 659 | } 660 | 661 | // MarshalJSON supports json.Marshaler interface 662 | func (v LsPosition) MarshalJSON() ([]byte, error) { 663 | w := jwriter.Writer{} 664 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc7(&w, v) 665 | return w.Buffer.BuildBytes(), w.Error 666 | } 667 | 668 | // MarshalEasyJSON supports easyjson.Marshaler interface 669 | func (v LsPosition) MarshalEasyJSON(w *jwriter.Writer) { 670 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc7(w, v) 671 | } 672 | 673 | // UnmarshalJSON supports json.Unmarshaler interface 674 | func (v *LsPosition) UnmarshalJSON(data []byte) error { 675 | r := jlexer.Lexer{Data: data} 676 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc7(&r, v) 677 | return r.Error() 678 | } 679 | 680 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 681 | func (v *LsPosition) UnmarshalEasyJSON(l *jlexer.Lexer) { 682 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc7(l, v) 683 | } 684 | func easyjsonC38a4abDecodeGithubComJacobdufaultLspc8(in *jlexer.Lexer, out *LsLocation) { 685 | isTopLevel := in.IsStart() 686 | if in.IsNull() { 687 | if isTopLevel { 688 | in.Consumed() 689 | } 690 | in.Skip() 691 | return 692 | } 693 | in.Delim('{') 694 | for !in.IsDelim('}') { 695 | key := in.UnsafeString() 696 | in.WantColon() 697 | if in.IsNull() { 698 | in.Skip() 699 | in.WantComma() 700 | continue 701 | } 702 | switch key { 703 | case "uri": 704 | out.URI = LsDocumentURI(in.String()) 705 | case "range": 706 | (out.Range).UnmarshalEasyJSON(in) 707 | default: 708 | in.SkipRecursive() 709 | } 710 | in.WantComma() 711 | } 712 | in.Delim('}') 713 | if isTopLevel { 714 | in.Consumed() 715 | } 716 | } 717 | func easyjsonC38a4abEncodeGithubComJacobdufaultLspc8(out *jwriter.Writer, in LsLocation) { 718 | out.RawByte('{') 719 | first := true 720 | _ = first 721 | { 722 | const prefix string = ",\"uri\":" 723 | if first { 724 | first = false 725 | out.RawString(prefix[1:]) 726 | } else { 727 | out.RawString(prefix) 728 | } 729 | out.String(string(in.URI)) 730 | } 731 | { 732 | const prefix string = ",\"range\":" 733 | if first { 734 | first = false 735 | out.RawString(prefix[1:]) 736 | } else { 737 | out.RawString(prefix) 738 | } 739 | (in.Range).MarshalEasyJSON(out) 740 | } 741 | out.RawByte('}') 742 | } 743 | 744 | // MarshalJSON supports json.Marshaler interface 745 | func (v LsLocation) MarshalJSON() ([]byte, error) { 746 | w := jwriter.Writer{} 747 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc8(&w, v) 748 | return w.Buffer.BuildBytes(), w.Error 749 | } 750 | 751 | // MarshalEasyJSON supports easyjson.Marshaler interface 752 | func (v LsLocation) MarshalEasyJSON(w *jwriter.Writer) { 753 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc8(w, v) 754 | } 755 | 756 | // UnmarshalJSON supports json.Unmarshaler interface 757 | func (v *LsLocation) UnmarshalJSON(data []byte) error { 758 | r := jlexer.Lexer{Data: data} 759 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc8(&r, v) 760 | return r.Error() 761 | } 762 | 763 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 764 | func (v *LsLocation) UnmarshalEasyJSON(l *jlexer.Lexer) { 765 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc8(l, v) 766 | } 767 | func easyjsonC38a4abDecodeGithubComJacobdufaultLspc9(in *jlexer.Lexer, out *LsInitializeParams) { 768 | isTopLevel := in.IsStart() 769 | if in.IsNull() { 770 | if isTopLevel { 771 | in.Consumed() 772 | } 773 | in.Skip() 774 | return 775 | } 776 | in.Delim('{') 777 | for !in.IsDelim('}') { 778 | key := in.UnsafeString() 779 | in.WantColon() 780 | if in.IsNull() { 781 | in.Skip() 782 | in.WantComma() 783 | continue 784 | } 785 | switch key { 786 | case "rootUri": 787 | out.RootURI = LsDocumentURI(in.String()) 788 | case "initializationOptions": 789 | (out.InitializationOptions).UnmarshalEasyJSON(in) 790 | default: 791 | in.SkipRecursive() 792 | } 793 | in.WantComma() 794 | } 795 | in.Delim('}') 796 | if isTopLevel { 797 | in.Consumed() 798 | } 799 | } 800 | func easyjsonC38a4abEncodeGithubComJacobdufaultLspc9(out *jwriter.Writer, in LsInitializeParams) { 801 | out.RawByte('{') 802 | first := true 803 | _ = first 804 | { 805 | const prefix string = ",\"rootUri\":" 806 | if first { 807 | first = false 808 | out.RawString(prefix[1:]) 809 | } else { 810 | out.RawString(prefix) 811 | } 812 | out.String(string(in.RootURI)) 813 | } 814 | { 815 | const prefix string = ",\"initializationOptions\":" 816 | if first { 817 | first = false 818 | out.RawString(prefix[1:]) 819 | } else { 820 | out.RawString(prefix) 821 | } 822 | (in.InitializationOptions).MarshalEasyJSON(out) 823 | } 824 | out.RawByte('}') 825 | } 826 | 827 | // MarshalJSON supports json.Marshaler interface 828 | func (v LsInitializeParams) MarshalJSON() ([]byte, error) { 829 | w := jwriter.Writer{} 830 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc9(&w, v) 831 | return w.Buffer.BuildBytes(), w.Error 832 | } 833 | 834 | // MarshalEasyJSON supports easyjson.Marshaler interface 835 | func (v LsInitializeParams) MarshalEasyJSON(w *jwriter.Writer) { 836 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc9(w, v) 837 | } 838 | 839 | // UnmarshalJSON supports json.Unmarshaler interface 840 | func (v *LsInitializeParams) UnmarshalJSON(data []byte) error { 841 | r := jlexer.Lexer{Data: data} 842 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc9(&r, v) 843 | return r.Error() 844 | } 845 | 846 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 847 | func (v *LsInitializeParams) UnmarshalEasyJSON(l *jlexer.Lexer) { 848 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc9(l, v) 849 | } 850 | func easyjsonC38a4abDecodeGithubComJacobdufaultLspc10(in *jlexer.Lexer, out *JSONRPCHeader) { 851 | isTopLevel := in.IsStart() 852 | if in.IsNull() { 853 | if isTopLevel { 854 | in.Consumed() 855 | } 856 | in.Skip() 857 | return 858 | } 859 | in.Delim('{') 860 | for !in.IsDelim('}') { 861 | key := in.UnsafeString() 862 | in.WantColon() 863 | if in.IsNull() { 864 | in.Skip() 865 | in.WantComma() 866 | continue 867 | } 868 | switch key { 869 | case "jsonrpc": 870 | out.JSONRPC = string(in.String()) 871 | case "method": 872 | out.Method = string(in.String()) 873 | case "id": 874 | out.ID = RequestID(in.Int()) 875 | case "params": 876 | (out.Params).UnmarshalEasyJSON(in) 877 | default: 878 | in.SkipRecursive() 879 | } 880 | in.WantComma() 881 | } 882 | in.Delim('}') 883 | if isTopLevel { 884 | in.Consumed() 885 | } 886 | } 887 | func easyjsonC38a4abEncodeGithubComJacobdufaultLspc10(out *jwriter.Writer, in JSONRPCHeader) { 888 | out.RawByte('{') 889 | first := true 890 | _ = first 891 | { 892 | const prefix string = ",\"jsonrpc\":" 893 | if first { 894 | first = false 895 | out.RawString(prefix[1:]) 896 | } else { 897 | out.RawString(prefix) 898 | } 899 | out.String(string(in.JSONRPC)) 900 | } 901 | { 902 | const prefix string = ",\"method\":" 903 | if first { 904 | first = false 905 | out.RawString(prefix[1:]) 906 | } else { 907 | out.RawString(prefix) 908 | } 909 | out.String(string(in.Method)) 910 | } 911 | if (in.ID).IsDefined() { 912 | const prefix string = ",\"id\":" 913 | if first { 914 | first = false 915 | out.RawString(prefix[1:]) 916 | } else { 917 | out.RawString(prefix) 918 | } 919 | out.Int(int(in.ID)) 920 | } 921 | { 922 | const prefix string = ",\"params\":" 923 | if first { 924 | first = false 925 | out.RawString(prefix[1:]) 926 | } else { 927 | out.RawString(prefix) 928 | } 929 | (in.Params).MarshalEasyJSON(out) 930 | } 931 | out.RawByte('}') 932 | } 933 | 934 | // MarshalJSON supports json.Marshaler interface 935 | func (v JSONRPCHeader) MarshalJSON() ([]byte, error) { 936 | w := jwriter.Writer{} 937 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc10(&w, v) 938 | return w.Buffer.BuildBytes(), w.Error 939 | } 940 | 941 | // MarshalEasyJSON supports easyjson.Marshaler interface 942 | func (v JSONRPCHeader) MarshalEasyJSON(w *jwriter.Writer) { 943 | easyjsonC38a4abEncodeGithubComJacobdufaultLspc10(w, v) 944 | } 945 | 946 | // UnmarshalJSON supports json.Unmarshaler interface 947 | func (v *JSONRPCHeader) UnmarshalJSON(data []byte) error { 948 | r := jlexer.Lexer{Data: data} 949 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc10(&r, v) 950 | return r.Error() 951 | } 952 | 953 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 954 | func (v *JSONRPCHeader) UnmarshalEasyJSON(l *jlexer.Lexer) { 955 | easyjsonC38a4abDecodeGithubComJacobdufaultLspc10(l, v) 956 | } 957 | -------------------------------------------------------------------------------- /uri.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jacob Dufault 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import "strings" 18 | 19 | func pathToURI(absolutePath string) LsDocumentURI { 20 | m := map[rune](string){ 21 | ' ': "%20", 22 | '#': "%23", 23 | '$': "%24", 24 | '&': "%26", 25 | '(': "%28", 26 | ')': "%29", 27 | '+': "%2B", 28 | ',': "%2C", 29 | ':': "%3A", 30 | ';': "%3B", 31 | '?': "%3F", 32 | '@': "%40", 33 | } 34 | 35 | result := "" 36 | for _, c := range absolutePath { 37 | if _, has := m[c]; has { 38 | result += m[c] 39 | } else { 40 | result += string(c) 41 | } 42 | } 43 | 44 | return LsDocumentURI("file://" + strings.Replace(result, "\\", "/", -1)) 45 | } 46 | -------------------------------------------------------------------------------- /uri_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Jacob Dufault 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestURINix(t *testing.T) { 24 | assert.Equal(t, LsDocumentURI("file:///a/b/c"), pathToURI("/a/b/c")) 25 | assert.Equal(t, LsDocumentURI("file:///a/b/c/"), pathToURI("/a/b/c/")) 26 | assert.Equal(t, LsDocumentURI("file:///a/b/c/"), pathToURI("\\a/b\\c\\")) 27 | assert.Equal(t, LsDocumentURI("file:///"), pathToURI("/")) 28 | } 29 | 30 | func TestURISlashConversion(t *testing.T) { 31 | assert.Equal(t, LsDocumentURI("file:///a%20b"), pathToURI("/a b")) 32 | } 33 | 34 | // TODO: add windows-style path tests 35 | --------------------------------------------------------------------------------