Skip to content

Commit 3a51dda

Browse files
committed
Several improvements
- Added workspace/executeCommand and workspace/applyEdit - Implement alternative result types for textDocument/codeAction and textDocument/documentSymbol - Exit process after a request timeout - Handle messages asynchronously
1 parent f7b8ee8 commit 3a51dda

File tree

3 files changed

+220
-15
lines changed

3 files changed

+220
-15
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__debug_bin

Diff for: handler/handler.go

+125-13
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import (
77
"io"
88
"io/ioutil"
99
"log"
10+
"os"
1011
"regexp"
1112
"strings"
13+
"time"
1214

1315
"github.com/pkg/errors"
1416
lsp "github.com/sourcegraph/go-lsp"
@@ -42,7 +44,7 @@ func NewInoHandler(stdin io.ReadCloser, stdout io.WriteCloser, stdinLog, stdoutL
4244
}
4345
handler.StartClangd()
4446
stdStream := jsonrpc2.NewBufferedStream(StreamReadWrite{stdin, stdout, stdinLog, stdoutLog}, jsonrpc2.VSCodeObjectCodec{})
45-
stdHandler := jsonrpc2.HandlerWithError(handler.FromStdio)
47+
stdHandler := jsonrpc2.AsyncHandler(jsonrpc2.HandlerWithError(handler.FromStdio))
4648
handler.StdioConn = jsonrpc2.NewConn(context.Background(), stdStream, stdHandler)
4749
if enableLogging {
4850
log.Println("Initial board configuration:", board)
@@ -84,7 +86,7 @@ func (handler *InoHandler) StartClangd() {
8486
}
8587
srw := StreamReadWrite{clangdRead, clangdWrite, handler.clangdProc.inLog, handler.clangdProc.outLog}
8688
clangdStream := jsonrpc2.NewBufferedStream(srw, jsonrpc2.VSCodeObjectCodec{})
87-
clangdHandler := jsonrpc2.HandlerWithError(handler.FromClangd)
89+
clangdHandler := jsonrpc2.AsyncHandler(jsonrpc2.HandlerWithError(handler.FromClangd))
8890
handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler)
8991
}
9092

@@ -114,20 +116,26 @@ func (handler *InoHandler) FromStdio(ctx context.Context, conn *jsonrpc2.Conn, r
114116
if params == nil {
115117
params = req.Params
116118
} else {
117-
uri, err = handler.transformClangdParams(ctx, req.Method, params)
119+
uri, err = handler.transformParamsToClangd(ctx, req.Method, params)
118120
}
119121
if err != nil {
120122
return
121123
}
122-
if handler.ClangdConn == nil {
123-
panic("Illegal state: handler.ClangdConn is nil")
124-
}
125124
if req.Notif {
126125
err = handler.ClangdConn.Notify(ctx, req.Method, params)
127126
} else {
127+
ctx, cancel := context.WithTimeout(ctx, 800*time.Millisecond)
128+
defer cancel()
128129
result, err = sendRequest(ctx, handler.ClangdConn, req.Method, params)
129130
}
130131
if err != nil {
132+
if err.Error() == "context deadline exceeded" {
133+
// Exit the process and trigger a restart by the client
134+
log.Println("Timeout exceeded while waiting for a reply from clangd:", req.Method)
135+
log.Println("Please restart the language server.")
136+
handler.StopClangd()
137+
os.Exit(1)
138+
}
131139
return
132140
}
133141
if enableLogging {
@@ -197,7 +205,7 @@ func (handler *InoHandler) changeBoardConfig(ctx context.Context, config *BoardC
197205
return
198206
}
199207

200-
func (handler *InoHandler) transformClangdParams(ctx context.Context, method string, params interface{}) (uri lsp.DocumentURI, err error) {
208+
func (handler *InoHandler) transformParamsToClangd(ctx context.Context, method string, params interface{}) (uri lsp.DocumentURI, err error) {
201209
switch method {
202210
case "initialize":
203211
handler.clangdProc.initParams = *params.(*lsp.InitializeParams)
@@ -267,6 +275,9 @@ func (handler *InoHandler) transformClangdParams(ctx context.Context, method str
267275
case "workspace/didChangeWatchedFiles":
268276
p := params.(*lsp.DidChangeWatchedFilesParams)
269277
err = handler.ino2cppDidChangeWatchedFilesParams(p)
278+
case "workspace/executeCommand":
279+
p := params.(*lsp.ExecuteCommandParams)
280+
err = handler.ino2cppExecuteCommand(p)
270281
}
271282
return
272283
}
@@ -495,18 +506,61 @@ func (handler *InoHandler) ino2cppDidChangeWatchedFilesParams(params *lsp.DidCha
495506
return nil
496507
}
497508

509+
func (handler *InoHandler) ino2cppExecuteCommand(executeCommand *lsp.ExecuteCommandParams) error {
510+
if len(executeCommand.Arguments) == 1 {
511+
arg := handler.parseCommandArgument(executeCommand.Arguments[0])
512+
if workspaceEdit, ok := arg.(*lsp.WorkspaceEdit); ok {
513+
executeCommand.Arguments[0] = handler.ino2cppWorkspaceEdit(workspaceEdit)
514+
}
515+
}
516+
return nil
517+
}
518+
519+
func (handler *InoHandler) ino2cppWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *lsp.WorkspaceEdit {
520+
newEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)}
521+
for uri, edit := range origEdit.Changes {
522+
if data, ok := handler.data[lsp.DocumentURI(uri)]; ok {
523+
newValue := make([]lsp.TextEdit, len(edit))
524+
for index := range edit {
525+
r := edit[index].Range
526+
newValue[index] = lsp.TextEdit{
527+
NewText: edit[index].NewText,
528+
Range: lsp.Range{
529+
Start: lsp.Position{Line: data.targetLineMap[r.Start.Line], Character: r.Start.Character},
530+
End: lsp.Position{Line: data.targetLineMap[r.End.Line], Character: r.End.Character},
531+
},
532+
}
533+
}
534+
newEdit.Changes[string(data.targetURI)] = newValue
535+
} else {
536+
newEdit.Changes[uri] = edit
537+
}
538+
}
539+
return &newEdit
540+
}
541+
498542
func (handler *InoHandler) transformClangdResult(method string, uri lsp.DocumentURI, result interface{}) interface{} {
499543
switch method {
500544
case "textDocument/completion":
501545
r := result.(*lsp.CompletionList)
502546
handler.cpp2inoCompletionList(r, uri)
503547
case "textDocument/codeAction":
504-
r := result.(*[]CodeAction)
548+
r := result.(*[]*commandOrCodeAction)
505549
for index := range *r {
506-
handler.cpp2inoCodeAction(&(*r)[index], uri)
550+
command := (*r)[index].Command
551+
if command != nil {
552+
handler.cpp2inoCommand(command)
553+
}
554+
codeAction := (*r)[index].CodeAction
555+
if codeAction != nil {
556+
handler.cpp2inoCodeAction(codeAction, uri)
557+
}
507558
}
508559
case "textDocument/hover":
509560
r := result.(*Hover)
561+
if len(r.Contents.Value) == 0 {
562+
return nil
563+
}
510564
handler.cpp2inoHover(r, uri)
511565
case "textDocument/definition":
512566
fallthrough
@@ -534,8 +588,26 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document
534588
handler.cpp2inoTextEdit(&(*r)[index], uri)
535589
}
536590
case "textDocument/documentSymbol":
537-
r := result.(*[]DocumentSymbol)
538-
result = handler.cpp2inoDocumentSymbols(*r, uri)
591+
r := result.(*[]*documentSymbolOrSymbolInformation)
592+
slice := *r
593+
if len(slice) > 0 && slice[0].DocumentSymbol != nil {
594+
// Treat the input as []DocumentSymbol
595+
symbols := make([]DocumentSymbol, len(slice))
596+
for index := range slice {
597+
symbols[index] = *slice[index].DocumentSymbol
598+
}
599+
result = handler.cpp2inoDocumentSymbols(symbols, uri)
600+
} else if len(slice) > 0 && slice[0].SymbolInformation != nil {
601+
// Treat the input as []SymbolInformation
602+
symbols := make([]lsp.SymbolInformation, len(slice))
603+
for index := range slice {
604+
symbols[index] = *slice[index].SymbolInformation
605+
}
606+
for index := range symbols {
607+
handler.cpp2inoLocation(&symbols[index].Location)
608+
}
609+
result = symbols
610+
}
539611
case "textDocument/rename":
540612
r := result.(*lsp.WorkspaceEdit)
541613
result = handler.cpp2inoWorkspaceEdit(r)
@@ -571,6 +643,15 @@ func (handler *InoHandler) cpp2inoCodeAction(codeAction *CodeAction, uri lsp.Doc
571643
}
572644
}
573645

646+
func (handler *InoHandler) cpp2inoCommand(command *lsp.Command) {
647+
if len(command.Arguments) == 1 {
648+
arg := handler.parseCommandArgument(command.Arguments[0])
649+
if workspaceEdit, ok := arg.(*lsp.WorkspaceEdit); ok {
650+
command.Arguments[0] = handler.cpp2inoWorkspaceEdit(workspaceEdit)
651+
}
652+
}
653+
}
654+
574655
func (handler *InoHandler) cpp2inoWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *lsp.WorkspaceEdit {
575656
newEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)}
576657
for uri, edit := range origEdit.Changes {
@@ -658,7 +739,7 @@ func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []DocumentSymbol,
658739

659740
// FromClangd handles a message received from clangd.
660741
func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) {
661-
params, _, err := handler.transformStdioParams(req.Method, req.Params)
742+
params, _, err := handler.transformParamsToStdio(req.Method, req.Params)
662743
if err != nil {
663744
log.Println("From clangd: Method:", req.Method, "Error:", err)
664745
return nil, err
@@ -679,7 +760,7 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2.
679760
return result, err
680761
}
681762

682-
func (handler *InoHandler) transformStdioParams(method string, raw *json.RawMessage) (params interface{}, uri lsp.DocumentURI, err error) {
763+
func (handler *InoHandler) transformParamsToStdio(method string, raw *json.RawMessage) (params interface{}, uri lsp.DocumentURI, err error) {
683764
params, err = readParams(method, raw)
684765
if err != nil {
685766
return
@@ -692,6 +773,9 @@ func (handler *InoHandler) transformStdioParams(method string, raw *json.RawMess
692773
p := params.(*lsp.PublishDiagnosticsParams)
693774
uri = p.URI
694775
err = handler.cpp2inoPublishDiagnosticsParams(p)
776+
case "workspace/applyEdit":
777+
p := params.(*ApplyWorkspaceEditParams)
778+
p.Edit = *handler.cpp2inoWorkspaceEdit(&p.Edit)
695779
}
696780
return
697781
}
@@ -708,6 +792,34 @@ func (handler *InoHandler) cpp2inoPublishDiagnosticsParams(params *lsp.PublishDi
708792
return nil
709793
}
710794

795+
func (handler *InoHandler) parseCommandArgument(rawArg interface{}) interface{} {
796+
if m1, ok := rawArg.(map[string]interface{}); ok && len(m1) == 1 && m1["changes"] != nil {
797+
m2 := m1["changes"].(map[string]interface{})
798+
workspaceEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)}
799+
for uri, rawValue := range m2 {
800+
rawTextEdits := rawValue.([]interface{})
801+
textEdits := make([]lsp.TextEdit, len(rawTextEdits))
802+
for index := range rawTextEdits {
803+
m3 := rawTextEdits[index].(map[string]interface{})
804+
rawRange := m3["range"]
805+
m4 := rawRange.(map[string]interface{})
806+
rawStart := m4["start"]
807+
m5 := rawStart.(map[string]interface{})
808+
textEdits[index].Range.Start.Line = int(m5["line"].(float64))
809+
textEdits[index].Range.Start.Character = int(m5["character"].(float64))
810+
rawEnd := m4["end"]
811+
m6 := rawEnd.(map[string]interface{})
812+
textEdits[index].Range.End.Line = int(m6["line"].(float64))
813+
textEdits[index].Range.End.Character = int(m6["character"].(float64))
814+
textEdits[index].NewText = m3["newText"].(string)
815+
}
816+
workspaceEdit.Changes[uri] = textEdits
817+
}
818+
return &workspaceEdit
819+
}
820+
return nil
821+
}
822+
711823
func (handler *InoHandler) showMessage(ctx context.Context, msgType lsp.MessageType, message string) {
712824
params := lsp.ShowMessageParams{
713825
Type: msgType,

Diff for: handler/protocol.go

+94-2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ func readParams(method string, raw *json.RawMessage) (interface{}, error) {
8484
params := new(lsp.DidChangeWatchedFilesParams)
8585
err := json.Unmarshal(*raw, params)
8686
return params, err
87+
case "workspace/executeCommand":
88+
params := new(lsp.ExecuteCommandParams)
89+
err := json.Unmarshal(*raw, params)
90+
return params, err
91+
case "workspace/applyEdit":
92+
params := new(ApplyWorkspaceEditParams)
93+
err := json.Unmarshal(*raw, params)
94+
return params, err
8795
case "textDocument/publishDiagnostics":
8896
params := new(lsp.PublishDiagnosticsParams)
8997
err := json.Unmarshal(*raw, params)
@@ -107,7 +115,7 @@ func sendRequest(ctx context.Context, conn *jsonrpc2.Conn, method string, params
107115
err := conn.Call(ctx, method, params, result)
108116
return result, err
109117
case "textDocument/codeAction":
110-
result := new([]CodeAction)
118+
result := new([]*commandOrCodeAction)
111119
err := conn.Call(ctx, method, params, result)
112120
return result, err
113121
case "completionItem/resolve":
@@ -145,7 +153,7 @@ func sendRequest(ctx context.Context, conn *jsonrpc2.Conn, method string, params
145153
err := conn.Call(ctx, method, params, result)
146154
return result, err
147155
case "textDocument/documentSymbol":
148-
result := new([]DocumentSymbol)
156+
result := new([]*documentSymbolOrSymbolInformation)
149157
err := conn.Call(ctx, method, params, result)
150158
return result, err
151159
case "textDocument/rename":
@@ -160,6 +168,14 @@ func sendRequest(ctx context.Context, conn *jsonrpc2.Conn, method string, params
160168
result := new(lsp.MessageActionItem)
161169
err := conn.Call(ctx, method, params, result)
162170
return result, err
171+
case "workspace/executeCommand":
172+
result := new(string)
173+
err := conn.Call(ctx, method, params, result)
174+
return result, err
175+
case "workspace/applyEdit":
176+
result := new(ApplyWorkspaceEditResponse)
177+
err := conn.Call(ctx, method, params, result)
178+
return result, err
163179
}
164180
var result interface{}
165181
err := conn.Call(ctx, method, params, result)
@@ -175,6 +191,37 @@ type CodeAction struct {
175191
Command *lsp.Command `json:"command,omitempty"`
176192
}
177193

194+
type commandOrCodeAction struct {
195+
Command *lsp.Command
196+
CodeAction *CodeAction
197+
}
198+
199+
func (entry *commandOrCodeAction) UnmarshalJSON(raw []byte) error {
200+
command := new(lsp.Command)
201+
err := json.Unmarshal(raw, command)
202+
if err == nil && len(command.Command) > 0 {
203+
entry.Command = command
204+
return nil
205+
}
206+
codeAction := new(CodeAction)
207+
err = json.Unmarshal(raw, codeAction)
208+
if err != nil {
209+
return err
210+
}
211+
entry.CodeAction = codeAction
212+
return nil
213+
}
214+
215+
func (entry *commandOrCodeAction) MarshalJSON() ([]byte, error) {
216+
if entry.Command != nil {
217+
return json.Marshal(entry.Command)
218+
}
219+
if entry.CodeAction != nil {
220+
return json.Marshal(entry.CodeAction)
221+
}
222+
return nil, nil
223+
}
224+
178225
// Hover structure according to LSP
179226
type Hover struct {
180227
Contents MarkupContent `json:"contents"`
@@ -198,6 +245,51 @@ type DocumentSymbol struct {
198245
Children []DocumentSymbol `json:"children,omitempty"`
199246
}
200247

248+
type documentSymbolOrSymbolInformation struct {
249+
DocumentSymbol *DocumentSymbol
250+
SymbolInformation *lsp.SymbolInformation
251+
}
252+
253+
type documentSymbolOrSymbolInformationDiscriminator struct {
254+
Range *lsp.Range `json:"range,omitempty"`
255+
Location *lsp.Location `json:"location,omitempty"`
256+
}
257+
258+
func (entry *documentSymbolOrSymbolInformation) UnmarshalJSON(raw []byte) error {
259+
discriminator := new(documentSymbolOrSymbolInformationDiscriminator)
260+
err := json.Unmarshal(raw, discriminator)
261+
if err != nil {
262+
return err
263+
}
264+
if discriminator.Range != nil {
265+
entry.DocumentSymbol = new(DocumentSymbol)
266+
err = json.Unmarshal(raw, entry.DocumentSymbol)
267+
if err != nil {
268+
return err
269+
}
270+
}
271+
if discriminator.Location != nil {
272+
entry.SymbolInformation = new(lsp.SymbolInformation)
273+
err = json.Unmarshal(raw, entry.SymbolInformation)
274+
if err != nil {
275+
return err
276+
}
277+
}
278+
return nil
279+
}
280+
281+
// ApplyWorkspaceEditParams structure according to LSP
282+
type ApplyWorkspaceEditParams struct {
283+
Label string `json:"label,omitempty"`
284+
Edit lsp.WorkspaceEdit `json:"edit"`
285+
}
286+
287+
// ApplyWorkspaceEditResponse structure according to LSP
288+
type ApplyWorkspaceEditResponse struct {
289+
Applied bool `json:"applied"`
290+
FailureReason string `json:"failureReason,omitempty"`
291+
}
292+
201293
// BoardConfig describes the board and port selected by the user.
202294
type BoardConfig struct {
203295
SelectedBoard Board `json:"selectedBoard"`

0 commit comments

Comments
 (0)