6
6
"encoding/json"
7
7
"fmt"
8
8
"io"
9
- "io/ioutil"
10
9
"log"
11
10
"os"
12
11
"regexp"
@@ -48,7 +47,10 @@ func NewInoHandler(stdin io.ReadCloser, stdout io.WriteCloser, logStreams *Strea
48
47
stdStream := jsonrpc2 .NewBufferedStream (logStreams .AttachStdInOut (stdin , stdout ), jsonrpc2.VSCodeObjectCodec {})
49
48
var stdHandler jsonrpc2.Handler = jsonrpc2 .HandlerWithError (handler .FromStdio )
50
49
if asyncProcessing {
51
- stdHandler = jsonrpc2 .AsyncHandler (stdHandler )
50
+ stdHandler = AsyncHandler {
51
+ handler : stdHandler ,
52
+ synchronizer : & handler .synchronizer ,
53
+ }
52
54
}
53
55
handler .StdioConn = jsonrpc2 .NewConn (context .Background (), stdStream , stdHandler )
54
56
if enableLogging {
@@ -59,11 +61,12 @@ func NewInoHandler(stdin io.ReadCloser, stdout io.WriteCloser, logStreams *Strea
59
61
60
62
// InoHandler is a JSON-RPC handler that delegates messages to clangd.
61
63
type InoHandler struct {
62
- StdioConn * jsonrpc2.Conn
63
- ClangdConn * jsonrpc2.Conn
64
- clangdProc ClangdProc
65
- data map [lsp.DocumentURI ]* FileData
66
- config BoardConfig
64
+ StdioConn * jsonrpc2.Conn
65
+ ClangdConn * jsonrpc2.Conn
66
+ clangdProc ClangdProc
67
+ data map [lsp.DocumentURI ]* FileData
68
+ config BoardConfig
69
+ synchronizer Synchronizer
67
70
}
68
71
69
72
// ClangdProc contains the process input / output streams for clangd.
@@ -108,15 +111,7 @@ func (handler *InoHandler) FromStdio(ctx context.Context, conn *jsonrpc2.Conn, r
108
111
return
109
112
}
110
113
111
- // Handle special methods (non-LSP)
112
- switch req .Method {
113
- case "arduino/selectedBoard" :
114
- p := params .(* BoardConfig )
115
- err = handler .changeBoardConfig (ctx , p )
116
- return
117
- }
118
-
119
- // Handle LSP methods: transform and send to clangd
114
+ // Handle LSP methods: transform parameters and send to clangd
120
115
var uri lsp.DocumentURI
121
116
if params == nil {
122
117
params = req .Params
@@ -128,89 +123,53 @@ func (handler *InoHandler) FromStdio(ctx context.Context, conn *jsonrpc2.Conn, r
128
123
}
129
124
if req .Notif {
130
125
err = handler .ClangdConn .Notify (ctx , req .Method , params )
126
+ if enableLogging {
127
+ log .Println ("From stdio:" , req .Method )
128
+ }
131
129
} else {
132
130
ctx , cancel := context .WithTimeout (ctx , 800 * time .Millisecond )
133
131
defer cancel ()
134
132
result , err = sendRequest (ctx , handler .ClangdConn , req .Method , params )
133
+ if enableLogging {
134
+ log .Println ("From stdio:" , req .Method , "id" , req .ID )
135
+ }
135
136
}
136
137
if err != nil {
138
+ // Exit the process and trigger a restart by the client in case of a severe error
137
139
if err .Error () == "context deadline exceeded" {
138
- // Exit the process and trigger a restart by the client
139
- log .Println ("Timeout exceeded while waiting for a reply from clangd:" , req .Method )
140
- log .Println ("Please restart the language server." )
141
- handler .StopClangd ()
142
- os .Exit (1 )
140
+ log .Println ("Timeout exceeded while waiting for a reply from clangd." )
141
+ handler .exit ()
142
+ }
143
+ if strings .Contains (err .Error (), "non-added document" ) || strings .Contains (err .Error (), "non-added file" ) {
144
+ log .Println ("The clangd process has lost track of the open document." )
145
+ handler .exit ()
143
146
}
144
147
return
145
148
}
146
- if enableLogging {
147
- log .Println ("From stdio:" , req .Method )
148
- }
149
+
150
+ // Transform and return the result
149
151
if result != nil {
150
152
result = handler .transformClangdResult (req .Method , uri , result )
151
153
}
152
154
return
153
155
}
154
156
155
- func (handler * InoHandler ) changeBoardConfig (ctx context.Context , config * BoardConfig ) (resultErr error ) {
156
- previousFqbn := handler .config .SelectedBoard .Fqbn
157
- handler .config = * config
158
- if config .SelectedBoard .Fqbn == previousFqbn || len (handler .data ) == 0 {
159
- return
160
- }
161
- if enableLogging {
162
- log .Println ("New board configuration:" , * config )
163
- }
164
-
165
- // Stop the clangd process and update all file data with the new board
157
+ func (handler * InoHandler ) exit () {
158
+ log .Println ("Please restart the language server." )
166
159
handler .StopClangd ()
167
- openFileData := make (map [* FileData ][]byte )
168
- for uri , data := range handler .data {
169
- if uri != data .sourceURI {
170
- continue
171
- }
172
- targetBytes , err := updateCpp ([]byte (data .sourceText ), uriToPath (data .sourceURI ), config .SelectedBoard .Fqbn , true , uriToPath (data .targetURI ))
173
- if err != nil {
174
- if resultErr == nil {
175
- resultErr = handler .handleError (ctx , err )
176
- }
177
- targetBytes , _ = ioutil .ReadFile (uriToPath (data .targetURI ))
178
- }
179
- sourceLineMap , targetLineMap := createSourceMaps (bytes .NewReader (targetBytes ))
180
- data .sourceLineMap = sourceLineMap
181
- data .targetLineMap = targetLineMap
182
- openFileData [data ] = targetBytes
183
- }
184
-
185
- // Restart the clangd process, initialize it and reopen the files
186
- handler .startClangd ()
187
- initResult := new (lsp.InitializeResult )
188
- err := handler .ClangdConn .Call (ctx , "initialize" , & handler .clangdProc .initParams , initResult )
189
- if err != nil {
190
- resultErr = err
191
- return
192
- }
193
- for data , targetBytes := range openFileData {
194
- if enableLogging {
195
- log .Println ("Reopening file: " , data .sourceURI )
196
- }
197
- openParams := lsp.DidOpenTextDocumentParams {
198
- TextDocument : lsp.TextDocumentItem {
199
- LanguageID : "cpp" ,
200
- URI : data .targetURI ,
201
- Version : data .version ,
202
- Text : string (targetBytes ),
203
- },
204
- }
205
- err := handler .ClangdConn .Notify (ctx , "textDocument/didOpen" , openParams )
206
- if err != nil && resultErr == nil {
207
- resultErr = err
208
- }
209
- }
210
- return
160
+ os .Exit (1 )
211
161
}
212
162
213
163
func (handler * InoHandler ) transformParamsToClangd (ctx context.Context , method string , params interface {}) (uri lsp.DocumentURI , err error ) {
164
+ needsWriteLock := method == "textDocument/didOpen" || method == "textDocument/didChange" || method == "textDocument/didClose"
165
+ if needsWriteLock {
166
+ handler .synchronizer .DataMux .Lock ()
167
+ defer handler .synchronizer .DataMux .Unlock ()
168
+ } else {
169
+ handler .synchronizer .DataMux .RLock ()
170
+ defer handler .synchronizer .DataMux .RUnlock ()
171
+ }
172
+
214
173
switch method {
215
174
case "initialize" :
216
175
handler .clangdProc .initParams = * params .(* lsp.InitializeParams )
@@ -221,7 +180,7 @@ func (handler *InoHandler) transformParamsToClangd(ctx context.Context, method s
221
180
case "textDocument/didChange" :
222
181
p := params .(* lsp.DidChangeTextDocumentParams )
223
182
uri = p .TextDocument .URI
224
- err = handler .ino2cppDidChangeTextDocumentParams (p )
183
+ err = handler .ino2cppDidChangeTextDocumentParams (ctx , p )
225
184
case "textDocument/didSave" :
226
185
p := params .(* lsp.DidSaveTextDocumentParams )
227
186
uri = p .TextDocument .URI
@@ -317,7 +276,7 @@ func (handler *InoHandler) createFileData(ctx context.Context, sourceURI lsp.Doc
317
276
return data , targetBytes , nil
318
277
}
319
278
320
- func (handler * InoHandler ) updateFileData (data * FileData , change * lsp.TextDocumentContentChangeEvent ) (err error ) {
279
+ func (handler * InoHandler ) updateFileData (ctx context. Context , data * FileData , change * lsp.TextDocumentContentChangeEvent ) (err error ) {
321
280
rang := change .Range
322
281
if rang == nil || rang .Start .Line != rang .End .Line {
323
282
// Update the source text and regenerate the cpp code
@@ -439,11 +398,11 @@ func (handler *InoHandler) ino2cppTextDocumentItem(ctx context.Context, doc *lsp
439
398
return nil
440
399
}
441
400
442
- func (handler * InoHandler ) ino2cppDidChangeTextDocumentParams (params * lsp.DidChangeTextDocumentParams ) error {
401
+ func (handler * InoHandler ) ino2cppDidChangeTextDocumentParams (ctx context. Context , params * lsp.DidChangeTextDocumentParams ) error {
443
402
handler .ino2cppTextDocumentIdentifier (& params .TextDocument .TextDocumentIdentifier )
444
403
if data , ok := handler .data [params .TextDocument .URI ]; ok {
445
404
for index := range params .ContentChanges {
446
- err := handler .updateFileData (data , & params .ContentChanges [index ])
405
+ err := handler .updateFileData (ctx , data , & params .ContentChanges [index ])
447
406
if err != nil {
448
407
return err
449
408
}
@@ -551,6 +510,9 @@ func (handler *InoHandler) ino2cppWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *ls
551
510
}
552
511
553
512
func (handler * InoHandler ) transformClangdResult (method string , uri lsp.DocumentURI , result interface {}) interface {} {
513
+ handler .synchronizer .DataMux .RLock ()
514
+ defer handler .synchronizer .DataMux .RUnlock ()
515
+
554
516
switch method {
555
517
case "textDocument/completion" :
556
518
r := result .(* lsp.CompletionList )
@@ -798,20 +760,22 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2.
798
760
var result interface {}
799
761
if req .Notif {
800
762
err = handler .StdioConn .Notify (ctx , req .Method , params )
763
+ if enableLogging {
764
+ log .Println ("From clangd:" , req .Method )
765
+ }
801
766
} else {
802
767
result , err = sendRequest (ctx , handler .StdioConn , req .Method , params )
803
- }
804
- if err != nil {
805
- log .Println ("From clangd: Method:" , req .Method , "Error:" , err )
806
- return nil , err
807
- }
808
- if enableLogging {
809
- log .Println ("From clangd:" , req .Method )
768
+ if enableLogging {
769
+ log .Println ("From clangd:" , req .Method , "id" , req .ID )
770
+ }
810
771
}
811
772
return result , err
812
773
}
813
774
814
775
func (handler * InoHandler ) transformParamsToStdio (method string , raw * json.RawMessage ) (params interface {}, uri lsp.DocumentURI , err error ) {
776
+ handler .synchronizer .DataMux .RLock ()
777
+ defer handler .synchronizer .DataMux .RUnlock ()
778
+
815
779
params , err = readParams (method , raw )
816
780
if err != nil {
817
781
return
0 commit comments