fix(lsp): recover from handler panics and decode client uris
This commit is contained in:
+31
-6
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -18,10 +19,11 @@ import (
|
||||
|
||||
// Server is a GAsm language server bound to a byte stream.
|
||||
type Server struct {
|
||||
in *bufio.Reader
|
||||
out io.Writer
|
||||
mu sync.Mutex // guards writes to out
|
||||
docs map[string]string
|
||||
in *bufio.Reader
|
||||
out io.Writer
|
||||
mu sync.Mutex // guards writes to out
|
||||
docs map[string]string
|
||||
version string // reported in the initialize result ("" omits it)
|
||||
}
|
||||
|
||||
// New returns a server reading from in and writing to out.
|
||||
@@ -33,6 +35,9 @@ func New(in io.Reader, out io.Writer) *Server {
|
||||
}
|
||||
}
|
||||
|
||||
// SetVersion records the server version reported in the initialize result.
|
||||
func (s *Server) SetVersion(v string) { s.version = v }
|
||||
|
||||
// Run serves requests until the input is exhausted or an exit is requested.
|
||||
func (s *Server) Run() error {
|
||||
for {
|
||||
@@ -109,9 +114,24 @@ func (s *Server) notify(method string, params any) {
|
||||
}
|
||||
|
||||
// dispatch routes one message. It returns true when the server should stop.
|
||||
// A panic in any handler is recovered and answered as an internal error:
|
||||
// handlers parse live editor buffers, so malformed input must never take
|
||||
// the whole server down.
|
||||
func (s *Server) dispatch(msg *rpcMessage) (exit bool) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if msg != nil && msg.ID != nil {
|
||||
s.respondError(msg.ID, errInternalError, fmt.Sprintf("internal error: %v", r))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
switch msg.Method {
|
||||
case "initialize":
|
||||
info := map[string]string{"name": "gasm"}
|
||||
if s.version != "" {
|
||||
info["version"] = s.version
|
||||
}
|
||||
s.respond(msg.ID, initializeResult{
|
||||
Capabilities: ServerCapabilities{
|
||||
TextDocumentSync: 1, // full sync
|
||||
@@ -138,7 +158,7 @@ func (s *Server) dispatch(msg *rpcMessage) (exit bool) {
|
||||
DocumentLinkProvider: map[string]any{},
|
||||
FoldingRangeProvider: true,
|
||||
},
|
||||
ServerInfo: map[string]string{"name": "gasm", "version": "0.31.1"},
|
||||
ServerInfo: info,
|
||||
})
|
||||
|
||||
case "initialized", "textDocument/didSave":
|
||||
@@ -292,9 +312,14 @@ func lintSeverity(s lint.Severity) int {
|
||||
}
|
||||
}
|
||||
|
||||
// uriPath strips a file:// scheme and returns the path component.
|
||||
// uriPath strips a file:// scheme and percent-decodes the path component.
|
||||
// LSP clients percent-encode URIs, so a raw slice would break every on-disk
|
||||
// lookup for paths containing spaces or non-ASCII characters.
|
||||
func uriPath(uri string) string {
|
||||
if rest, ok := strings.CutPrefix(uri, "file://"); ok {
|
||||
if decoded, err := url.PathUnescape(rest); err == nil {
|
||||
return decoded
|
||||
}
|
||||
return rest
|
||||
}
|
||||
return uri
|
||||
|
||||
Reference in New Issue
Block a user