66 "encoding/json"
77 "fmt"
88 "io"
9- "io/ioutil"
109 "log"
1110 "os"
1211 "regexp"
@@ -48,7 +47,10 @@ func NewInoHandler(stdin io.ReadCloser, stdout io.WriteCloser, logStreams *Strea
4847 stdStream := jsonrpc2 .NewBufferedStream (logStreams .AttachStdInOut (stdin , stdout ), jsonrpc2.VSCodeObjectCodec {})
4948 var stdHandler jsonrpc2.Handler = jsonrpc2 .HandlerWithError (handler .FromStdio )
5049 if asyncProcessing {
51- stdHandler = jsonrpc2 .AsyncHandler (stdHandler )
50+ stdHandler = AsyncHandler {
51+ handler : stdHandler ,
52+ synchronizer : & handler .synchronizer ,
53+ }
5254 }
5355 handler .StdioConn = jsonrpc2 .NewConn (context .Background (), stdStream , stdHandler )
5456 if enableLogging {
@@ -59,11 +61,12 @@ func NewInoHandler(stdin io.ReadCloser, stdout io.WriteCloser, logStreams *Strea
5961
6062// InoHandler is a JSON-RPC handler that delegates messages to clangd.
6163type 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
6770}
6871
6972// ClangdProc contains the process input / output streams for clangd.
@@ -108,15 +111,7 @@ func (handler *InoHandler) FromStdio(ctx context.Context, conn *jsonrpc2.Conn, r
108111 return
109112 }
110113
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
120115 var uri lsp.DocumentURI
121116 if params == nil {
122117 params = req .Params
@@ -128,89 +123,53 @@ func (handler *InoHandler) FromStdio(ctx context.Context, conn *jsonrpc2.Conn, r
128123 }
129124 if req .Notif {
130125 err = handler .ClangdConn .Notify (ctx , req .Method , params )
126+ if enableLogging {
127+ log .Println ("From stdio:" , req .Method )
128+ }
131129 } else {
132130 ctx , cancel := context .WithTimeout (ctx , 800 * time .Millisecond )
133131 defer cancel ()
134132 result , err = sendRequest (ctx , handler .ClangdConn , req .Method , params )
133+ if enableLogging {
134+ log .Println ("From stdio:" , req .Method , "id" , req .ID )
135+ }
135136 }
136137 if err != nil {
138+ // Exit the process and trigger a restart by the client in case of a severe error
137139 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 ()
143146 }
144147 return
145148 }
146- if enableLogging {
147- log .Println ("From stdio:" , req .Method )
148- }
149+
150+ // Transform and return the result
149151 if result != nil {
150152 result = handler .transformClangdResult (req .Method , uri , result )
151153 }
152154 return
153155}
154156
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." )
166159 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 )
211161}
212162
213163func (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+
214173 switch method {
215174 case "initialize" :
216175 handler .clangdProc .initParams = * params .(* lsp.InitializeParams )
@@ -221,7 +180,7 @@ func (handler *InoHandler) transformParamsToClangd(ctx context.Context, method s
221180 case "textDocument/didChange" :
222181 p := params .(* lsp.DidChangeTextDocumentParams )
223182 uri = p .TextDocument .URI
224- err = handler .ino2cppDidChangeTextDocumentParams (p )
183+ err = handler .ino2cppDidChangeTextDocumentParams (ctx , p )
225184 case "textDocument/didSave" :
226185 p := params .(* lsp.DidSaveTextDocumentParams )
227186 uri = p .TextDocument .URI
@@ -317,7 +276,7 @@ func (handler *InoHandler) createFileData(ctx context.Context, sourceURI lsp.Doc
317276 return data , targetBytes , nil
318277}
319278
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 ) {
321280 rang := change .Range
322281 if rang == nil || rang .Start .Line != rang .End .Line {
323282 // Update the source text and regenerate the cpp code
@@ -439,11 +398,11 @@ func (handler *InoHandler) ino2cppTextDocumentItem(ctx context.Context, doc *lsp
439398 return nil
440399}
441400
442- func (handler * InoHandler ) ino2cppDidChangeTextDocumentParams (params * lsp.DidChangeTextDocumentParams ) error {
401+ func (handler * InoHandler ) ino2cppDidChangeTextDocumentParams (ctx context. Context , params * lsp.DidChangeTextDocumentParams ) error {
443402 handler .ino2cppTextDocumentIdentifier (& params .TextDocument .TextDocumentIdentifier )
444403 if data , ok := handler .data [params .TextDocument .URI ]; ok {
445404 for index := range params .ContentChanges {
446- err := handler .updateFileData (data , & params .ContentChanges [index ])
405+ err := handler .updateFileData (ctx , data , & params .ContentChanges [index ])
447406 if err != nil {
448407 return err
449408 }
@@ -551,6 +510,9 @@ func (handler *InoHandler) ino2cppWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *ls
551510}
552511
553512func (handler * InoHandler ) transformClangdResult (method string , uri lsp.DocumentURI , result interface {}) interface {} {
513+ handler .synchronizer .DataMux .RLock ()
514+ defer handler .synchronizer .DataMux .RUnlock ()
515+
554516 switch method {
555517 case "textDocument/completion" :
556518 r := result .(* lsp.CompletionList )
@@ -798,20 +760,22 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2.
798760 var result interface {}
799761 if req .Notif {
800762 err = handler .StdioConn .Notify (ctx , req .Method , params )
763+ if enableLogging {
764+ log .Println ("From clangd:" , req .Method )
765+ }
801766 } else {
802767 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+ }
810771 }
811772 return result , err
812773}
813774
814775func (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+
815779 params , err = readParams (method , raw )
816780 if err != nil {
817781 return
0 commit comments