Skip to content

Commit 6f17cb1

Browse files
authored
feat(vscode): Add an option to trace LSP messages (#17)
* feat(vscode): Add an option to trace LSP messages * chore: Some minor LSP handling improvementa * fix(vscode): Fix tracing not working because of incorrect extension ID * fix: Fix LSP errors being interpreted as results
1 parent aa7a4c9 commit 6f17cb1

File tree

4 files changed

+97
-30
lines changed

4 files changed

+97
-30
lines changed

internal/lsp/server.go

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,7 @@ func (s *Server) Serve() error {
8282
}
8383

8484
func (s *Server) processMessage(payload []byte) bool {
85-
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage
86-
var request struct {
87-
ID json.RawMessage `json:"id"`
88-
Method string `json:"method"`
89-
Params json.RawMessage `json:"params"`
90-
}
91-
85+
var request RequestMessage
9286
if err := json.Unmarshal(payload, &request); err != nil {
9387
slog.Error("Bad request", "error", err)
9488
return true
@@ -117,27 +111,35 @@ func (s *Server) processMessage(payload []byte) bool {
117111
logger.Debug("Received request", "params", string(request.Params))
118112
}
119113

120-
response, err := s.handleRequest(request.Method, request.Params)
121-
if err != nil {
114+
result, err := s.handleRequest(request.Method, request.Params)
115+
if err == nil {
116+
err = s.writeMessage(&ResponseMessage{
117+
JSONRPC: "2.0",
118+
ID: request.ID,
119+
Result: result,
120+
})
121+
} else {
122122
logger.Error("Error handling request", "error", err)
123123
var asResponseError *ResponseError
124-
if errors.As(err, &asResponseError) {
125-
response = asResponseError
126-
} else {
127-
response = &ResponseError{
124+
if !errors.As(err, &asResponseError) {
125+
asResponseError = &ResponseError{
128126
Code: CodeInternalError,
129127
Message: err.Error(),
130128
}
131129
}
130+
err = s.writeMessage(&ErrorResponseMessage{
131+
JSONRPC: "2.0",
132+
ID: request.ID,
133+
Error: asResponseError,
134+
})
132135
}
133136

134-
if err := s.write(request.ID, response); err != nil {
135-
logger.Error("Write error", "error", err)
136-
return true
137+
if err != nil {
138+
logger.Error("Failed to write response", "error", err)
137139
}
138140

139141
if debugEnabled {
140-
logger.Debug("Sent response", "response", fmt.Sprintf("%#v", response))
142+
logger.Debug("Sent response", "response", fmt.Sprintf("%#v", result))
141143
}
142144

143145
return true
@@ -176,7 +178,7 @@ func (s *Server) handleNotification(method string, paramsRaw json.RawMessage) er
176178
switch method {
177179
case "initialized":
178180

179-
case "cancelRequest":
181+
case "$/cancelRequest":
180182
// TODO(asnyder): Handle cancelRequest and make everything
181183
// async.
182184

@@ -264,23 +266,24 @@ func parseParams(paramsRaw json.RawMessage, result any) error {
264266
return nil
265267
}
266268

267-
func (s *Server) write(requestID json.RawMessage, result any) error {
268-
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#responseMessage
269-
message := struct {
270-
JSONRPC string `json:"jsonrpc"`
271-
ID json.RawMessage `json:"id"`
272-
Result any `json:"result"`
273-
}{JSONRPC: "2.0", ID: requestID, Result: result}
274-
269+
func (s *Server) writeMessage(message Message) error {
275270
data, err := json.Marshal(message)
276271
if err != nil {
277-
return fmt.Errorf("invalid response: %w", err)
272+
return fmt.Errorf("marshal response: %w", err)
278273
}
279274

275+
if err := s.writeRaw(data); err != nil {
276+
return fmt.Errorf("write response: %w", err)
277+
}
278+
279+
return nil
280+
}
281+
282+
func (s *Server) writeRaw(data []byte) error {
280283
if s.Stdout == nil {
281284
s.Stdout = os.Stdout
282285
}
283286

284-
_, err = s.Stdout.Write(append([]byte("Content-Length: "+strconv.Itoa(len(data))+"\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n"), data...))
287+
_, err := s.Stdout.Write(append([]byte("Content-Length: "+strconv.Itoa(len(data))+"\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n"), data...))
285288
return err
286289
}

internal/lsp/types.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222

2323
package lsp
2424

25-
import "fmt"
25+
import (
26+
"encoding/json"
27+
"fmt"
28+
)
2629

2730
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#clientCapabilities
2831
type ClientCapabilities struct{}
@@ -62,6 +65,49 @@ type ServerInfo struct {
6265
Version string `json:"version"`
6366
}
6467

68+
// Message satisfies the LSP message interface.
69+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#abstractMessage
70+
type Message interface {
71+
message()
72+
}
73+
74+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage
75+
type RequestMessage struct {
76+
JSONRPC string `json:"jsonrpc"`
77+
ID json.RawMessage `json:"id"`
78+
Method string `json:"method"`
79+
Params json.RawMessage `json:"params"`
80+
}
81+
82+
func (r *RequestMessage) message() {}
83+
84+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#notificationMessage
85+
type NotificationMessage struct {
86+
JSONRPC string `json:"jsonrpc"`
87+
Method string `json:"method"`
88+
Params json.RawMessage `json:"params,omitempty"`
89+
}
90+
91+
func (n *NotificationMessage) message() {}
92+
93+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#responseMessage
94+
type ResponseMessage struct {
95+
JSONRPC string `json:"jsonrpc"`
96+
ID json.RawMessage `json:"id"`
97+
Result any `json:"result"`
98+
}
99+
100+
func (r *ResponseMessage) message() {}
101+
102+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#responseMessage
103+
type ErrorResponseMessage struct {
104+
JSONRPC string `json:"jsonrpc"`
105+
ID json.RawMessage `json:"id"`
106+
Error *ResponseError `json:"error"`
107+
}
108+
109+
func (e *ErrorResponseMessage) message() {}
110+
65111
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#responseError
66112
type ResponseError struct {
67113
Code ErrorCode `json:"code"`

vscode-extension/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,23 @@
7070
"lsp"
7171
],
7272
"contributes": {
73+
"configuration": {
74+
"type": "object",
75+
"title": "GDShader Language Server Configuration",
76+
"properties": {
77+
"gdshader.trace.server": {
78+
"type": "string",
79+
"scope": "window",
80+
"default": "off",
81+
"enum": [
82+
"off",
83+
"messages",
84+
"verbose"
85+
],
86+
"description": "Enables tracing of the underlying LSP requests and responses. This results in highly verbose logs (especially the document sync messages) and is not recommended for use outside development and troubleshooting contexts."
87+
}
88+
}
89+
},
7390
"languages": [
7491
{
7592
"id": "gdshader",

vscode-extension/src/extension.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,11 @@ async function activate(context) {
7878
fileEvents: vscode.workspace.createFileSystemWatcher("**/.clientrc"),
7979
},
8080
outputChannel: logger(),
81+
traceOutputChannel: logger(),
8182
};
8283

8384
const client = new LanguageClient(
84-
"gdshaderLanguageServer",
85+
"gdshader",
8586
"GDShader Language Server",
8687
serverOptions,
8788
clientOptions,

0 commit comments

Comments
 (0)