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) }