250 lines
5.5 KiB
Go
250 lines
5.5 KiB
Go
package lsp
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/marte-dev/marte-dev-tools/internal/index"
|
|
"github.com/marte-dev/marte-dev-tools/internal/parser"
|
|
)
|
|
|
|
type JsonRpcMessage struct {
|
|
Jsonrpc string `json:"jsonrpc"`
|
|
Method string `json:"method,omitempty"`
|
|
Params json.RawMessage `json:"params,omitempty"`
|
|
ID interface{} `json:"id,omitempty"`
|
|
Result interface{} `json:"result,omitempty"`
|
|
Error *JsonRpcError `json:"error,omitempty"`
|
|
}
|
|
|
|
type JsonRpcError struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type DidOpenTextDocumentParams struct {
|
|
TextDocument TextDocumentItem `json:"textDocument"`
|
|
}
|
|
|
|
type DidChangeTextDocumentParams struct {
|
|
TextDocument VersionedTextDocumentIdentifier `json:"textDocument"`
|
|
ContentChanges []TextDocumentContentChangeEvent `json:"contentChanges"`
|
|
}
|
|
|
|
type TextDocumentItem struct {
|
|
URI string `json:"uri"`
|
|
LanguageID string `json:"languageId"`
|
|
Version int `json:"version"`
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
type VersionedTextDocumentIdentifier struct {
|
|
URI string `json:"uri"`
|
|
Version int `json:"version"`
|
|
}
|
|
|
|
type TextDocumentContentChangeEvent struct {
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
type HoverParams struct {
|
|
TextDocument TextDocumentIdentifier `json:"textDocument"`
|
|
Position Position `json:"position"`
|
|
}
|
|
|
|
type TextDocumentIdentifier struct {
|
|
URI string `json:"uri"`
|
|
}
|
|
|
|
type Position struct {
|
|
Line int `json:"line"`
|
|
Character int `json:"character"`
|
|
}
|
|
|
|
type Hover struct {
|
|
Contents interface{} `json:"contents"`
|
|
}
|
|
|
|
type MarkupContent struct {
|
|
Kind string `json:"kind"`
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
var tree = index.NewProjectTree()
|
|
|
|
func RunServer() {
|
|
reader := bufio.NewReader(os.Stdin)
|
|
for {
|
|
msg, err := readMessage(reader)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
fmt.Fprintf(os.Stderr, "Error reading message: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
handleMessage(msg)
|
|
}
|
|
}
|
|
|
|
func readMessage(reader *bufio.Reader) (*JsonRpcMessage, error) {
|
|
var contentLength int
|
|
for {
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if line == "\r\n" {
|
|
break
|
|
}
|
|
if _, err := fmt.Sscanf(line, "Content-Length: %d", &contentLength); err == nil {
|
|
continue
|
|
}
|
|
}
|
|
|
|
body := make([]byte, contentLength)
|
|
_, err := io.ReadFull(reader, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var msg JsonRpcMessage
|
|
err = json.Unmarshal(body, &msg)
|
|
return &msg, err
|
|
}
|
|
|
|
func handleMessage(msg *JsonRpcMessage) {
|
|
switch msg.Method {
|
|
case "initialize":
|
|
respond(msg.ID, map[string]interface{}{
|
|
"capabilities": map[string]interface{}{
|
|
"textDocumentSync": 1, // Full sync
|
|
"hoverProvider": true,
|
|
"definitionProvider": true,
|
|
"referencesProvider": true,
|
|
},
|
|
})
|
|
case "initialized":
|
|
// Do nothing
|
|
case "shutdown":
|
|
respond(msg.ID, nil)
|
|
case "exit":
|
|
os.Exit(0)
|
|
case "textDocument/didOpen":
|
|
var params DidOpenTextDocumentParams
|
|
if err := json.Unmarshal(msg.Params, ¶ms); err == nil {
|
|
handleDidOpen(params)
|
|
}
|
|
case "textDocument/didChange":
|
|
var params DidChangeTextDocumentParams
|
|
if err := json.Unmarshal(msg.Params, ¶ms); err == nil {
|
|
handleDidChange(params)
|
|
}
|
|
case "textDocument/hover":
|
|
var params HoverParams
|
|
if err := json.Unmarshal(msg.Params, ¶ms); err == nil {
|
|
res := handleHover(params)
|
|
respond(msg.ID, res)
|
|
} else {
|
|
respond(msg.ID, nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
func uriToPath(uri string) string {
|
|
return strings.TrimPrefix(uri, "file://")
|
|
}
|
|
|
|
func handleDidOpen(params DidOpenTextDocumentParams) {
|
|
path := uriToPath(params.TextDocument.URI)
|
|
p := parser.NewParser(params.TextDocument.Text)
|
|
config, err := p.Parse()
|
|
if err == nil {
|
|
tree.AddFile(path, config)
|
|
tree.ResolveReferences()
|
|
}
|
|
}
|
|
|
|
func handleDidChange(params DidChangeTextDocumentParams) {
|
|
if len(params.ContentChanges) == 0 {
|
|
return
|
|
}
|
|
// Full sync: text is in ContentChanges[0].Text
|
|
text := params.ContentChanges[0].Text
|
|
path := uriToPath(params.TextDocument.URI)
|
|
p := parser.NewParser(text)
|
|
config, err := p.Parse()
|
|
if err == nil {
|
|
tree.AddFile(path, config)
|
|
tree.ResolveReferences()
|
|
}
|
|
}
|
|
|
|
func handleHover(params HoverParams) *Hover {
|
|
path := uriToPath(params.TextDocument.URI)
|
|
// LSP 0-based to Parser 1-based
|
|
line := params.Position.Line + 1
|
|
col := params.Position.Character + 1
|
|
|
|
res := tree.Query(path, line, col)
|
|
if res == nil {
|
|
return nil
|
|
}
|
|
|
|
var content string
|
|
|
|
if res.Node != nil {
|
|
// Try to find Class field
|
|
class := "Unknown"
|
|
for _, frag := range res.Node.Fragments {
|
|
for _, def := range frag.Definitions {
|
|
if f, ok := def.(*parser.Field); ok && f.Name == "Class" {
|
|
if s, ok := f.Value.(*parser.StringValue); ok {
|
|
class = s.Value
|
|
} else if r, ok := f.Value.(*parser.ReferenceValue); ok {
|
|
class = r.Value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
content = fmt.Sprintf("**Object**: `%s`\n\n**Class**: `%s`", res.Node.RealName, class)
|
|
} else if res.Field != nil {
|
|
content = fmt.Sprintf("**Field**: `%s`", res.Field.Name)
|
|
} else if res.Reference != nil {
|
|
targetName := "Unresolved"
|
|
if res.Reference.Target != nil {
|
|
targetName = res.Reference.Target.RealName
|
|
}
|
|
content = fmt.Sprintf("**Reference**: `%s` -> `%s`", res.Reference.Name, targetName)
|
|
}
|
|
|
|
if content == "" {
|
|
return nil
|
|
}
|
|
|
|
return &Hover{
|
|
Contents: MarkupContent{
|
|
Kind: "markdown",
|
|
Value: content,
|
|
},
|
|
}
|
|
}
|
|
|
|
func respond(id interface{}, result interface{}) {
|
|
msg := JsonRpcMessage{
|
|
Jsonrpc: "2.0",
|
|
ID: id,
|
|
Result: result,
|
|
}
|
|
send(msg)
|
|
}
|
|
|
|
func send(msg interface{}) {
|
|
body, _ := json.Marshal(msg)
|
|
fmt.Printf("Content-Length: %d\r\n\r\n%s", len(body), body)
|
|
} |