fix(lsp): recover from handler panics and decode client uris

This commit is contained in:
2026-09-14 18:22:00 +02:00
parent e307bf830f
commit 909f874797
4 changed files with 190 additions and 23 deletions
+34 -13
View File
@@ -22,12 +22,13 @@ import (
"sourcedock.dev/petrbalvin/gasm-devkit/token"
)
// textflagMacros are the flag names defined by textflag.h; they are highlighted
// as macros and offered as completions after a TEXT/GLOBL directive.
// textflagMacros are the flag names defined by runtime/textflag.h; they are
// highlighted as macros and offered as completions after a TEXT/GLOBL
// directive.
var textflagMacros = map[string]bool{
"NOPROFILE": true, "DUPOK": true, "NOSPLIT": true, "RODATA": true,
"NOPTR": true, "WRAPPER": true, "NEEDCTXT": true, "TOPFRAME": true,
"LEAF": true, "ABI0": true, "REFLECTDATA": true,
"NOPROF": true, "DUPOK": true, "NOSPLIT": true, "RODATA": true,
"NOPTR": true, "WRAPPER": true, "NEEDCTXT": true, "TLSBSS": true,
"NOFRAME": true, "REFLECTMETHOD": true, "TOPFRAME": true, "ABIWRAPPER": true,
}
// completion builds the completion list for a document.
@@ -81,13 +82,13 @@ func (s *Server) hover(p hoverParams) *Hover {
var md string
if in, ok := a.Lookup(word); ok {
md = "**" + in.Name + "** — " + in.Summary
md = "**" + in.Name + "**: " + in.Summary
} else if r, ok := a.Register(word); ok {
md = "**" + r.Name + "** — " + r.Class.String() + " register. " + r.Desc
md = "**" + r.Name + "**: " + r.Class.String() + " register. " + r.Desc
} else if desc, ok := arch.PseudoRegDesc(word); ok {
md = "**" + strings.ToUpper(word) + "** — pseudo-register. " + desc
md = "**" + strings.ToUpper(word) + "**: pseudo-register. " + desc
} else if textflagMacros[strings.ToUpper(word)] {
md = "**" + strings.ToUpper(word) + "** — textflag.h flag"
md = "**" + strings.ToUpper(word) + "**: textflag.h flag"
} else {
return nil
}
@@ -271,7 +272,9 @@ func (s *Server) codeActions(p codeActionParams) []CodeAction {
for _, diag := range p.Context.Diagnostics {
switch diag.Code {
case "missing-ret":
// Offer to add RET at the end of the function.
// Offer to add RET at the end of the flagged function only: the
// diagnostic's range covers the TEXT keyword, so a line match
// picks the function the diagnostic belongs to.
f, _ := parser.Parse(uriPath(p.TextDocument.URI), text)
if f == nil {
continue
@@ -281,6 +284,9 @@ func (s *Server) codeActions(p codeActionParams) []CodeAction {
if !ok {
continue
}
if t.Keyword.Pos.Line-1 != int(diag.Range.Start.Line) {
continue
}
if len(t.Body) == 0 {
continue
}
@@ -298,7 +304,7 @@ func (s *Server) codeActions(p codeActionParams) []CodeAction {
endLine := len(lines) - 1
endChar := len([]rune(lines[endLine]))
actions = append(actions, CodeAction{
Title: "Add RET",
Title: "Add RET to " + t.Name.Name,
Kind: "quickfix",
Edit: &WorkspaceEdit{
Changes: map[string][]TextEdit{p.TextDocument.URI: {
@@ -792,13 +798,28 @@ func textRange(t *ast.Text) Range {
// diagnosticsFor computes the LSP diagnostics of one document; the push
// (publishDiagnostics) and pull (textDocument/diagnostic) paths share it.
// Parse errors surface as error-severity diagnostics so a malformed line is
// visible in the editor instead of only breaking derived features.
func (s *Server) diagnosticsFor(uri string) []Diagnostic {
text := s.docs[uri]
f, _ := parser.Parse(uriPath(uri), text)
f, errs := parser.Parse(uriPath(uri), text)
cfg := lint.Config{Arch: arch.FromFilename(uriPath(uri))}
diags := lint.File(f, cfg)
out := make([]Diagnostic, 0, len(diags))
out := make([]Diagnostic, 0, len(diags)+len(errs))
for _, e := range errs {
pos := token.Position{Line: 1, Column: 1}
if pe, ok := e.(parser.Error); ok && pe.Pos.IsValid() {
pos = pe.Pos
}
out = append(out, Diagnostic{
Range: toRange(pos.Line, pos.Column, token.Position{}),
Severity: sevError,
Code: "syntax",
Source: "gasm",
Message: e.Error(),
})
}
for _, d := range diags {
out = append(out, Diagnostic{
Range: toRange(d.Pos.Line, d.Pos.Column, d.End),