Improved LSP reactivity

This commit is contained in:
Martino Ferrari
2026-01-23 13:14:34 +01:00
parent 0ee44c0a27
commit 77fe3e9cac
2 changed files with 99 additions and 58 deletions

View File

@@ -311,13 +311,18 @@ func handleDidOpen(params DidOpenTextDocumentParams) {
documents[params.TextDocument.URI] = params.TextDocument.Text documents[params.TextDocument.URI] = params.TextDocument.Text
p := parser.NewParser(params.TextDocument.Text) p := parser.NewParser(params.TextDocument.Text)
config, err := p.Parse() config, err := p.Parse()
if err != nil { if err != nil {
publishParserError(params.TextDocument.URI, err) publishParserError(params.TextDocument.URI, err)
return } else {
publishParserError(params.TextDocument.URI, nil)
}
if config != nil {
tree.AddFile(path, config)
tree.ResolveReferences()
runValidation(params.TextDocument.URI)
} }
tree.AddFile(path, config)
tree.ResolveReferences()
runValidation(params.TextDocument.URI)
} }
func handleDidChange(params DidChangeTextDocumentParams) { func handleDidChange(params DidChangeTextDocumentParams) {
@@ -329,13 +334,18 @@ func handleDidChange(params DidChangeTextDocumentParams) {
path := uriToPath(params.TextDocument.URI) path := uriToPath(params.TextDocument.URI)
p := parser.NewParser(text) p := parser.NewParser(text)
config, err := p.Parse() config, err := p.Parse()
if err != nil { if err != nil {
publishParserError(params.TextDocument.URI, err) publishParserError(params.TextDocument.URI, err)
return } else {
publishParserError(params.TextDocument.URI, nil)
}
if config != nil {
tree.AddFile(path, config)
tree.ResolveReferences()
runValidation(params.TextDocument.URI)
} }
tree.AddFile(path, config)
tree.ResolveReferences()
runValidation(params.TextDocument.URI)
} }
func handleFormatting(params DocumentFormattingParams) []TextEdit { func handleFormatting(params DocumentFormattingParams) []TextEdit {
@@ -426,6 +436,19 @@ func runValidation(uri string) {
} }
func publishParserError(uri string, err error) { func publishParserError(uri string, err error) {
if err == nil {
notification := JsonRpcMessage{
Jsonrpc: "2.0",
Method: "textDocument/publishDiagnostics",
Params: mustMarshal(PublishDiagnosticsParams{
URI: uri,
Diagnostics: []LSPDiagnostic{},
}),
}
send(notification)
return
}
var line, col int var line, col int
var msg string var msg string
// Try parsing "line:col: message" // Try parsing "line:col: message"

View File

@@ -11,6 +11,7 @@ type Parser struct {
buf []Token buf []Token
comments []Comment comments []Comment
pragmas []Pragma pragmas []Pragma
errors []error
} }
func NewParser(input string) *Parser { func NewParser(input string) *Parser {
@@ -19,6 +20,10 @@ func NewParser(input string) *Parser {
} }
} }
func (p *Parser) addError(pos Position, msg string) {
p.errors = append(p.errors, fmt.Errorf("%d:%d: %s", pos.Line, pos.Column, msg))
}
func (p *Parser) next() Token { func (p *Parser) next() Token {
if len(p.buf) > 0 { if len(p.buf) > 0 {
t := p.buf[0] t := p.buf[0]
@@ -71,72 +76,82 @@ func (p *Parser) Parse() (*Configuration, error) {
continue continue
} }
def, err := p.parseDefinition() def, ok := p.parseDefinition()
if err != nil { if ok {
return nil, err config.Definitions = append(config.Definitions, def)
} else {
// Synchronization: skip token if not consumed to make progress
if p.peek() == tok {
p.next()
}
} }
config.Definitions = append(config.Definitions, def)
} }
config.Comments = p.comments config.Comments = p.comments
config.Pragmas = p.pragmas config.Pragmas = p.pragmas
return config, nil
var err error
if len(p.errors) > 0 {
err = p.errors[0]
}
return config, err
} }
func (p *Parser) parseDefinition() (Definition, error) { func (p *Parser) parseDefinition() (Definition, bool) {
tok := p.next() tok := p.next()
switch tok.Type { switch tok.Type {
case TokenIdentifier: case TokenIdentifier:
// Could be Field = Value OR Node = { ... }
name := tok.Value name := tok.Value
if p.next().Type != TokenEqual { if p.peek().Type != TokenEqual {
return nil, fmt.Errorf("%d:%d: expected =", tok.Position.Line, tok.Position.Column) p.addError(tok.Position, "expected =")
return nil, false
} }
p.next() // Consume =
// Disambiguate based on RHS
nextTok := p.peek() nextTok := p.peek()
if nextTok.Type == TokenLBrace { if nextTok.Type == TokenLBrace {
// Check if it looks like a Subnode (contains definitions) or Array (contains values)
if p.isSubnodeLookahead() { if p.isSubnodeLookahead() {
sub, err := p.parseSubnode() sub, ok := p.parseSubnode()
if err != nil { if !ok {
return nil, err return nil, false
} }
return &ObjectNode{ return &ObjectNode{
Position: tok.Position, Position: tok.Position,
Name: name, Name: name,
Subnode: sub, Subnode: sub,
}, nil }, true
} }
} }
// Default to Field val, ok := p.parseValue()
val, err := p.parseValue() if !ok {
if err != nil { return nil, false
return nil, err
} }
return &Field{ return &Field{
Position: tok.Position, Position: tok.Position,
Name: name, Name: name,
Value: val, Value: val,
}, nil }, true
case TokenObjectIdentifier: case TokenObjectIdentifier:
// node = subnode
name := tok.Value name := tok.Value
if p.next().Type != TokenEqual { if p.peek().Type != TokenEqual {
return nil, fmt.Errorf("%d:%d: expected =", tok.Position.Line, tok.Position.Column) p.addError(tok.Position, "expected =")
return nil, false
} }
sub, err := p.parseSubnode() p.next() // Consume =
if err != nil {
return nil, err sub, ok := p.parseSubnode()
if !ok {
return nil, false
} }
return &ObjectNode{ return &ObjectNode{
Position: tok.Position, Position: tok.Position,
Name: name, Name: name,
Subnode: sub, Subnode: sub,
}, nil }, true
default: default:
return nil, fmt.Errorf("%d:%d: unexpected token %v", tok.Position.Line, tok.Position.Column, tok.Value) p.addError(tok.Position, fmt.Sprintf("unexpected token %v", tok.Value))
return nil, false
} }
} }
@@ -176,10 +191,11 @@ func (p *Parser) isSubnodeLookahead() bool {
return false return false
} }
func (p *Parser) parseSubnode() (Subnode, error) { func (p *Parser) parseSubnode() (Subnode, bool) {
tok := p.next() tok := p.next()
if tok.Type != TokenLBrace { if tok.Type != TokenLBrace {
return Subnode{}, fmt.Errorf("%d:%d: expected {", tok.Position.Line, tok.Position.Column) p.addError(tok.Position, "expected {")
return Subnode{}, false
} }
sub := Subnode{Position: tok.Position} sub := Subnode{Position: tok.Position}
for { for {
@@ -190,18 +206,22 @@ func (p *Parser) parseSubnode() (Subnode, error) {
break break
} }
if t.Type == TokenEOF { if t.Type == TokenEOF {
return sub, fmt.Errorf("%d:%d: unexpected EOF, expected }", t.Position.Line, t.Position.Column) p.addError(t.Position, "unexpected EOF, expected }")
return sub, false
} }
def, err := p.parseDefinition() def, ok := p.parseDefinition()
if err != nil { if ok {
return sub, err sub.Definitions = append(sub.Definitions, def)
} else {
if p.peek() == t {
p.next()
}
} }
sub.Definitions = append(sub.Definitions, def)
} }
return sub, nil return sub, true
} }
func (p *Parser) parseValue() (Value, error) { func (p *Parser) parseValue() (Value, bool) {
tok := p.next() tok := p.next()
switch tok.Type { switch tok.Type {
case TokenString: case TokenString:
@@ -209,24 +229,21 @@ func (p *Parser) parseValue() (Value, error) {
Position: tok.Position, Position: tok.Position,
Value: strings.Trim(tok.Value, "\""), Value: strings.Trim(tok.Value, "\""),
Quoted: true, Quoted: true,
}, nil }, true
case TokenNumber: case TokenNumber:
// Simplistic handling
if strings.Contains(tok.Value, ".") || strings.Contains(tok.Value, "e") { if strings.Contains(tok.Value, ".") || strings.Contains(tok.Value, "e") {
f, _ := strconv.ParseFloat(tok.Value, 64) f, _ := strconv.ParseFloat(tok.Value, 64)
return &FloatValue{Position: tok.Position, Value: f, Raw: tok.Value}, nil return &FloatValue{Position: tok.Position, Value: f, Raw: tok.Value}, true
} }
i, _ := strconv.ParseInt(tok.Value, 0, 64) i, _ := strconv.ParseInt(tok.Value, 0, 64)
return &IntValue{Position: tok.Position, Value: i, Raw: tok.Value}, nil return &IntValue{Position: tok.Position, Value: i, Raw: tok.Value}, true
case TokenBool: case TokenBool:
return &BoolValue{Position: tok.Position, Value: tok.Value == "true"}, return &BoolValue{Position: tok.Position, Value: tok.Value == "true"},
nil true
case TokenIdentifier: case TokenIdentifier:
// reference? return &ReferenceValue{Position: tok.Position, Value: tok.Value}, true
return &ReferenceValue{Position: tok.Position, Value: tok.Value}, nil
case TokenLBrace: case TokenLBrace:
// array
arr := &ArrayValue{Position: tok.Position} arr := &ArrayValue{Position: tok.Position}
for { for {
t := p.peek() t := p.peek()
@@ -239,14 +256,15 @@ func (p *Parser) parseValue() (Value, error) {
p.next() p.next()
continue continue
} }
val, err := p.parseValue() val, ok := p.parseValue()
if err != nil { if !ok {
return nil, err return nil, false
} }
arr.Elements = append(arr.Elements, val) arr.Elements = append(arr.Elements, val)
} }
return arr, nil return arr, true
default: default:
return nil, fmt.Errorf("%d:%d: unexpected value token %v", tok.Position.Line, tok.Position.Column, tok.Value) p.addError(tok.Position, fmt.Sprintf("unexpected value token %v", tok.Value))
return nil, false
} }
} }