feat(lsp): pull diagnostics, include links and folding ranges
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -5,6 +5,10 @@ package lsp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
@@ -13,6 +17,7 @@ import (
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/format"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/lexer"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/lint"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/token"
|
||||
)
|
||||
@@ -784,3 +789,94 @@ func textRange(t *ast.Text) Range {
|
||||
}
|
||||
return Range{Start: start, End: end}
|
||||
}
|
||||
|
||||
// diagnosticsFor computes the LSP diagnostics of one document; the push
|
||||
// (publishDiagnostics) and pull (textDocument/diagnostic) paths share it.
|
||||
func (s *Server) diagnosticsFor(uri string) []Diagnostic {
|
||||
text := s.docs[uri]
|
||||
f, _ := parser.Parse(uriPath(uri), text)
|
||||
cfg := lint.Config{Arch: arch.FromFilename(uriPath(uri))}
|
||||
diags := lint.File(f, cfg)
|
||||
|
||||
out := make([]Diagnostic, 0, len(diags))
|
||||
for _, d := range diags {
|
||||
out = append(out, Diagnostic{
|
||||
Range: toRange(d.Pos.Line, d.Pos.Column, d.End),
|
||||
Severity: lintSeverity(d.Severity),
|
||||
Code: d.Code,
|
||||
Source: "gasm",
|
||||
Message: d.Message,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// includeRe matches a #include directive's quoted path.
|
||||
var includeRe = regexp.MustCompile(`^\s*#include\s+"([^"]+)"`)
|
||||
|
||||
// documentLinks links each #include path to the header file it resolves to:
|
||||
// first relative to the document's directory, then in $GOROOT/pkg/include
|
||||
// (where textflag.h and friends live). Unresolvable paths get no link.
|
||||
func (s *Server) documentLinks(uri string) []DocumentLink {
|
||||
text := s.docs[uri]
|
||||
if text == "" {
|
||||
return nil
|
||||
}
|
||||
docDir := filepath.Dir(uriPath(uri))
|
||||
goroot := runtime.GOROOT()
|
||||
|
||||
var out []DocumentLink
|
||||
for i, line := range strings.Split(text, "\n") {
|
||||
m := includeRe.FindStringSubmatch(line)
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
target := resolveInclude(docDir, goroot, m[1])
|
||||
if target == "" {
|
||||
continue
|
||||
}
|
||||
start := strings.Index(line, "\"")
|
||||
out = append(out, DocumentLink{
|
||||
Range: Range{
|
||||
Start: Position{Line: i, Character: start},
|
||||
End: Position{Line: i, Character: start + len(m[1]) + 2},
|
||||
},
|
||||
Target: "file://" + target,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// resolveInclude returns the existing file a #include path refers to, or "".
|
||||
func resolveInclude(docDir, goroot, path string) string {
|
||||
for _, dir := range []string{docDir, filepath.Join(goroot, "pkg", "include")} {
|
||||
cand := filepath.Join(dir, path)
|
||||
if _, err := os.Stat(cand); err == nil {
|
||||
return cand
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// foldingRanges marks each TEXT function's body as a collapsible region.
|
||||
func (s *Server) foldingRanges(uri string) []FoldingRange {
|
||||
text := s.docs[uri]
|
||||
f, _ := parser.Parse(uriPath(uri), text)
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
var out []FoldingRange
|
||||
for _, d := range f.Decls {
|
||||
t, ok := d.(*ast.Text)
|
||||
if !ok || len(t.Body) == 0 {
|
||||
continue
|
||||
}
|
||||
start := t.Keyword.Pos.Line - 1
|
||||
end := t.Body[len(t.Body)-1].Pos().Line - 1
|
||||
if end <= start {
|
||||
continue
|
||||
}
|
||||
out = append(out, FoldingRange{StartLine: start, EndLine: end, Kind: "region"})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user