Improved LSP reactivity
This commit is contained in:
@@ -311,14 +311,19 @@ 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.AddFile(path, config)
|
||||||
tree.ResolveReferences()
|
tree.ResolveReferences()
|
||||||
runValidation(params.TextDocument.URI)
|
runValidation(params.TextDocument.URI)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func handleDidChange(params DidChangeTextDocumentParams) {
|
func handleDidChange(params DidChangeTextDocumentParams) {
|
||||||
if len(params.ContentChanges) == 0 {
|
if len(params.ContentChanges) == 0 {
|
||||||
@@ -329,14 +334,19 @@ 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.AddFile(path, config)
|
||||||
tree.ResolveReferences()
|
tree.ResolveReferences()
|
||||||
runValidation(params.TextDocument.URI)
|
runValidation(params.TextDocument.URI)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func handleFormatting(params DocumentFormattingParams) []TextEdit {
|
func handleFormatting(params DocumentFormattingParams) []TextEdit {
|
||||||
uri := params.TextDocument.URI
|
uri := params.TextDocument.URI
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
config.Definitions = append(config.Definitions, def)
|
||||||
|
} else {
|
||||||
|
// Synchronization: skip token if not consumed to make progress
|
||||||
|
if p.peek() == tok {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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()
|
|
||||||
if err != nil {
|
|
||||||
return sub, err
|
|
||||||
}
|
}
|
||||||
|
def, ok := p.parseDefinition()
|
||||||
|
if ok {
|
||||||
sub.Definitions = append(sub.Definitions, def)
|
sub.Definitions = append(sub.Definitions, def)
|
||||||
|
} else {
|
||||||
|
if p.peek() == t {
|
||||||
|
p.next()
|
||||||
}
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user