package lsp import ( "encoding/json" "strings" ) // LSP protocol types — minimal subset needed for semantic enrichment. // Based on LSP 3.17. // InitializeParams is sent as the first request to the server. type InitializeParams struct { ProcessID int `json:"processId"` RootURI string `json:"rootUri"` // WorkspaceFolders carries the primary root plus any additional // roots for cross-package resolution. Omitted when empty so // servers that only understand rootUri keep working. WorkspaceFolders []WorkspaceFolder `json:"workspaceFolders,omitempty"` Capabilities ClientCapabilities `json:"capabilities"` // InitializationOptions carries server-specific initialization // parameters. For jdtls this includes Maven/Gradle import settings; // for other servers it is nil/omitted. InitializationOptions json.RawMessage `json:"initializationOptions,omitempty"` } // WorkspaceFolder is one root in a multi-folder LSP workspace. type WorkspaceFolder struct { URI string `json:"uri"` Name string `json:"name"` } // ClientCapabilities declares what the client supports. type ClientCapabilities struct { Workspace *WorkspaceClientCapabilities `json:"workspace,omitempty"` TextDocument TextDocumentClientCapabilities `json:"textDocument,omitempty"` } // WorkspaceClientCapabilities declares workspace-level capabilities. type WorkspaceClientCapabilities struct { ApplyEdit bool `json:"applyEdit,omitempty"` WorkspaceEdit *WorkspaceEditClientCapabilities `json:"workspaceEdit,omitempty"` ExecuteCommand *ExecuteCommandCapability `json:"executeCommand,omitempty"` WorkspaceFolders bool `json:"workspaceFolders,omitempty"` Configuration bool `json:"configuration,omitempty"` } // WorkspaceEditClientCapabilities declares applyEdit support. type WorkspaceEditClientCapabilities struct { DocumentChanges bool `json:"documentChanges,omitempty"` ResourceOperations []string `json:"resourceOperations,omitempty"` } // ExecuteCommandCapability declares workspace/executeCommand support. type ExecuteCommandCapability struct { DynamicRegistration bool `json:"dynamicRegistration,omitempty"` } // TextDocumentClientCapabilities declares text document capabilities. type TextDocumentClientCapabilities struct { Implementation *ImplementationCapability `json:"implementation,omitempty"` References *ReferencesCapability `json:"references,omitempty"` Definition *DefinitionCapability `json:"definition,omitempty"` Hover *HoverCapability `json:"hover,omitempty"` CallHierarchy *CallHierarchyCapability `json:"callHierarchy,omitempty"` TypeHierarchy *TypeHierarchyCapability `json:"typeHierarchy,omitempty"` CodeAction *CodeActionCapability `json:"codeAction,omitempty"` PublishDiagnostics *PublishDiagnosticsCapability `json:"publishDiagnostics,omitempty"` Synchronization *SynchronizationCapability `json:"synchronization,omitempty"` } // SynchronizationCapability advertises didOpen/didChange support. type SynchronizationCapability struct { DynamicRegistration bool `json:"dynamicRegistration,omitempty"` WillSave bool `json:"willSave,omitempty"` DidSave bool `json:"didSave,omitempty"` } // CodeActionCapability advertises codeAction request support. type CodeActionCapability struct { DynamicRegistration bool `json:"dynamicRegistration,omitempty"` CodeActionLiteralSupport *CodeActionLiteralSupport `json:"codeActionLiteralSupport,omitempty"` IsPreferredSupport bool `json:"isPreferredSupport,omitempty"` DataSupport bool `json:"dataSupport,omitempty"` ResolveSupport *CodeActionResolveSupport `json:"resolveSupport,omitempty"` } // CodeActionLiteralSupport declares supported code action kinds. type CodeActionLiteralSupport struct { CodeActionKind CodeActionKindCapability `json:"codeActionKind"` } // CodeActionKindCapability lists known code action kinds. type CodeActionKindCapability struct { ValueSet []string `json:"valueSet"` } // CodeActionResolveSupport declares which CodeAction fields the // server can be asked to resolve lazily via codeAction/resolve. type CodeActionResolveSupport struct { Properties []string `json:"properties"` } // PublishDiagnosticsCapability advertises diagnostic-related features. type PublishDiagnosticsCapability struct { RelatedInformation bool `json:"relatedInformation,omitempty"` VersionSupport bool `json:"versionSupport,omitempty"` CodeDescriptionSupport bool `json:"codeDescriptionSupport,omitempty"` DataSupport bool `json:"dataSupport,omitempty"` } // CallHierarchyCapability declares callHierarchy/* request support. type CallHierarchyCapability struct { DynamicRegistration bool `json:"dynamicRegistration,omitempty"` } // TypeHierarchyCapability declares typeHierarchy/* request support. type TypeHierarchyCapability struct { DynamicRegistration bool `json:"dynamicRegistration,omitempty"` } // ImplementationCapability declares implementation request support. type ImplementationCapability struct { DynamicRegistration bool `json:"dynamicRegistration,omitempty"` } // ReferencesCapability declares references request support. type ReferencesCapability struct { DynamicRegistration bool `json:"dynamicRegistration,omitempty"` } // DefinitionCapability declares definition request support. type DefinitionCapability struct { DynamicRegistration bool `json:"dynamicRegistration,omitempty"` } // HoverCapability declares hover request support. type HoverCapability struct { DynamicRegistration bool `json:"dynamicRegistration,omitempty"` ContentFormat []string `json:"contentFormat,omitempty"` } // InitializeResult is the server's response to initialize. type InitializeResult struct { Capabilities ServerCapabilities `json:"capabilities"` } // ServerCapabilities declares what the server supports. type ServerCapabilities struct { ImplementationProvider any `json:"implementationProvider,omitempty"` ReferencesProvider any `json:"referencesProvider,omitempty"` DefinitionProvider any `json:"definitionProvider,omitempty"` HoverProvider any `json:"hoverProvider,omitempty"` CallHierarchyProvider any `json:"callHierarchyProvider,omitempty"` TypeHierarchyProvider any `json:"typeHierarchyProvider,omitempty"` CodeActionProvider any `json:"codeActionProvider,omitempty"` ExecuteCommandProvider *ExecuteCommandOptions `json:"executeCommandProvider,omitempty"` TextDocumentSync any `json:"textDocumentSync,omitempty"` } // ExecuteCommandOptions tells which commands the server can execute. type ExecuteCommandOptions struct { Commands []string `json:"commands,omitempty"` } // Registration is one capability the server is registering dynamically // via client/registerCapability. The id is opaque and used as the key // for a later client/unregisterCapability. Method names the LSP feature // (e.g. "textDocument/foldingRange"). RegisterOptions carries the // server's configuration for the capability (a document selector, // trigger characters, etc.) — opaque to us; we just preserve it so // callers that want to consult it can. type Registration struct { ID string `json:"id"` Method string `json:"method"` RegisterOptions json.RawMessage `json:"registerOptions,omitempty"` } // RegistrationParams is the payload of client/registerCapability. type RegistrationParams struct { Registrations []Registration `json:"registrations"` } // Unregistration identifies a previously-registered capability to drop. // The LSP spec uses the misspelled wire field "unregisterations" — keep // that spelling on the params struct, but the singular type is sane. type Unregistration struct { ID string `json:"id"` Method string `json:"method"` } // UnregistrationParams is the payload of client/unregisterCapability. // The JSON field name "unregisterations" is the LSP spec's misspelling // — keep it; servers send that exact name on the wire. type UnregistrationParams struct { Unregisterations []Unregistration `json:"unregisterations"` } // TextDocumentIdentifier identifies a text document. type TextDocumentIdentifier struct { URI string `json:"uri"` } // TextDocumentPositionParams identifies a position in a text document. type TextDocumentPositionParams struct { TextDocument TextDocumentIdentifier `json:"textDocument"` Position Position `json:"position"` } // Position in a text document (0-indexed). type Position struct { Line int `json:"line"` Character int `json:"character"` } // Location represents a location in a document. type Location struct { URI string `json:"uri"` Range Range `json:"range"` } // Range in a text document. type Range struct { Start Position `json:"start"` End Position `json:"end"` } // DidOpenTextDocumentParams is sent when a document is opened. type DidOpenTextDocumentParams struct { TextDocument TextDocumentItem `json:"textDocument"` } // TextDocumentItem is a text document with content. type TextDocumentItem struct { URI string `json:"uri"` LanguageID string `json:"languageId"` Version int `json:"version"` Text string `json:"text"` } // ReferenceParams extends TextDocumentPositionParams with reference context. type ReferenceParams struct { TextDocumentPositionParams Context ReferenceContext `json:"context"` } // ReferenceContext controls what references are returned. type ReferenceContext struct { IncludeDeclaration bool `json:"includeDeclaration"` } // ImplementationParams is the params for textDocument/implementation. type ImplementationParams struct { TextDocumentPositionParams } // HoverParams is the params for textDocument/hover. type HoverParams struct { TextDocumentPositionParams } // HoverResult is the response for textDocument/hover. type HoverResult struct { Contents HoverContents `json:"contents"` Range *Range `json:"range,omitempty"` } // HoverContents handles the three LSP hover content formats: // - MarkupContent (object with kind + value) // - MarkedString (plain string or {language, value} object) // - MarkedString[] (array of the above) // // jdtls returns an array, which caused "cannot unmarshal array into Go struct" // when Contents was typed as MarkupContent directly. type HoverContents struct { Value string // the concatenated hover text } func (hc *HoverContents) UnmarshalJSON(data []byte) error { // Try MarkupContent first (object with kind + value). var mc MarkupContent if err := json.Unmarshal(data, &mc); err == nil && mc.Value != "" { hc.Value = mc.Value return nil } // Try MarkedString[] (array). var arr []json.RawMessage if err := json.Unmarshal(data, &arr); err == nil { var parts []string for _, raw := range arr { // Each element can be a string or a MarkedString object. var s string if json.Unmarshal(raw, &s) == nil { parts = append(parts, s) continue } var ms MarkedString if json.Unmarshal(raw, &ms) == nil { parts = append(parts, ms.Value) } } hc.Value = strings.Join(parts, "\n") return nil } // Try plain string (MarkedString without array). var s string if err := json.Unmarshal(data, &s); err == nil { hc.Value = s return nil } // Fallback: single MarkedString object. var ms MarkedString if err := json.Unmarshal(data, &ms); err == nil { hc.Value = ms.Value return nil } return nil // empty/unrecognised — leave Value empty } // MarkedString represents a {language, value} object in hover content. type MarkedString struct { Language string `json:"language"` Value string `json:"value"` } // MarkupContent represents hover content. type MarkupContent struct { Kind string `json:"kind"` Value string `json:"value"` } // CallHierarchyPrepareParams is the params for callHierarchy/prepare. type CallHierarchyPrepareParams struct { TextDocumentPositionParams } // CallHierarchyItem represents an item in the call hierarchy. type CallHierarchyItem struct { Name string `json:"name"` Kind int `json:"kind"` URI string `json:"uri"` Range Range `json:"range"` SelectionRange Range `json:"selectionRange"` } // CallHierarchyIncomingCallsParams is the params for callHierarchy/incomingCalls. type CallHierarchyIncomingCallsParams struct { Item CallHierarchyItem `json:"item"` } // CallHierarchyIncomingCall represents an incoming call. type CallHierarchyIncomingCall struct { From CallHierarchyItem `json:"from"` FromRanges []Range `json:"fromRanges"` } // CallHierarchyOutgoingCallsParams is the params for callHierarchy/outgoingCalls. type CallHierarchyOutgoingCallsParams struct { Item CallHierarchyItem `json:"item"` } // CallHierarchyOutgoingCall represents an outgoing call. type CallHierarchyOutgoingCall struct { To CallHierarchyItem `json:"to"` FromRanges []Range `json:"fromRanges"` } // TypeHierarchyPrepareParams is the params for textDocument/prepareTypeHierarchy. type TypeHierarchyPrepareParams struct { TextDocumentPositionParams } // TypeHierarchyItem represents an item in the type hierarchy. type TypeHierarchyItem struct { Name string `json:"name"` Kind int `json:"kind"` URI string `json:"uri"` Range Range `json:"range"` SelectionRange Range `json:"selectionRange"` } // TypeHierarchySupertypesParams is the params for typeHierarchy/supertypes. type TypeHierarchySupertypesParams struct { Item TypeHierarchyItem `json:"item"` } // TypeHierarchySubtypesParams is the params for typeHierarchy/subtypes. type TypeHierarchySubtypesParams struct { Item TypeHierarchyItem `json:"item"` } // --------------------------------------------------------------------------- // textDocument/codeAction support (LSP fix-all). // --------------------------------------------------------------------------- // Standard LSP code-action kinds (a subset). const ( CodeActionKindEmpty = "" CodeActionKindQuickFix = "quickfix" CodeActionKindRefactor = "refactor" CodeActionKindRefactorExtract = "refactor.extract" CodeActionKindRefactorInline = "refactor.inline" CodeActionKindRefactorRewrite = "refactor.rewrite" CodeActionKindSource = "source" CodeActionKindSourceOrganizeImports = "source.organizeImports" CodeActionKindSourceFixAll = "source.fixAll" ) // DiagnosticSeverity values match the LSP spec. const ( DiagSeverityError = 1 DiagSeverityWarning = 2 DiagSeverityInformation = 3 DiagSeverityHint = 4 ) // Diagnostic is a problem report from the server. type Diagnostic struct { Range Range `json:"range"` Severity int `json:"severity,omitempty"` Code any `json:"code,omitempty"` CodeDescription *DiagnosticCodeDescription `json:"codeDescription,omitempty"` Source string `json:"source,omitempty"` Message string `json:"message"` Tags []int `json:"tags,omitempty"` RelatedInformation []DiagnosticRelatedInformation `json:"relatedInformation,omitempty"` Data any `json:"data,omitempty"` } // DiagnosticCodeDescription points to documentation for the code. type DiagnosticCodeDescription struct { Href string `json:"href"` } // DiagnosticRelatedInformation locates a related document position. type DiagnosticRelatedInformation struct { Location Location `json:"location"` Message string `json:"message"` } // PublishDiagnosticsParams is the payload of textDocument/publishDiagnostics. type PublishDiagnosticsParams struct { URI string `json:"uri"` Version *int `json:"version,omitempty"` Diagnostics []Diagnostic `json:"diagnostics"` } // CodeActionParams is the params for textDocument/codeAction. type CodeActionParams struct { TextDocument TextDocumentIdentifier `json:"textDocument"` Range Range `json:"range"` Context CodeActionContext `json:"context"` } // CodeActionContext narrows the kinds of actions returned and lists // diagnostics those actions should address. type CodeActionContext struct { Diagnostics []Diagnostic `json:"diagnostics"` Only []string `json:"only,omitempty"` TriggerKind int `json:"triggerKind,omitempty"` } // Command is a server-defined command (e.g. "go.organizeImports"). type Command struct { Title string `json:"title"` Command string `json:"command"` Arguments []any `json:"arguments,omitempty"` } // CodeAction is the rich form returned from textDocument/codeAction. // Either Edit or Command (or both) may be set; servers vary. type CodeAction struct { Title string `json:"title"` Kind string `json:"kind,omitempty"` Diagnostics []Diagnostic `json:"diagnostics,omitempty"` IsPreferred bool `json:"isPreferred,omitempty"` Disabled *DisabledReason `json:"disabled,omitempty"` Edit *WorkspaceEdit `json:"edit,omitempty"` Command *Command `json:"command,omitempty"` Data any `json:"data,omitempty"` } // DisabledReason explains why a server marked a code action disabled. type DisabledReason struct { Reason string `json:"reason"` } // CodeActionOrCommand is the union returned by textDocument/codeAction. // LSP servers may emit either a Command (legacy) or a CodeAction // literal — we accept both and normalise downstream. type CodeActionOrCommand struct { // Title is always present and human readable. Title string `json:"title,omitempty"` // Command is set on legacy (Command) form. Command string `json:"command,omitempty"` // Arguments accompanies legacy Command form. Arguments []any `json:"arguments,omitempty"` // Kind / Edit / Diagnostics / IsPreferred / Disabled are part of // the CodeAction literal form. Kind string `json:"kind,omitempty"` Edit *WorkspaceEdit `json:"edit,omitempty"` Diagnostics []Diagnostic `json:"diagnostics,omitempty"` IsPreferred bool `json:"isPreferred,omitempty"` Disabled *DisabledReason `json:"disabled,omitempty"` Data any `json:"data,omitempty"` } // IsCommand reports whether the action is a legacy Command (no edit). func (a *CodeActionOrCommand) IsCommand() bool { return a != nil && a.Command != "" && a.Edit == nil } // AsCodeAction projects the union into the literal CodeAction form. func (a *CodeActionOrCommand) AsCodeAction() *CodeAction { if a == nil { return nil } if a.Edit == nil && a.Command == "" { return &CodeAction{Title: a.Title, Kind: a.Kind, IsPreferred: a.IsPreferred, Diagnostics: a.Diagnostics, Disabled: a.Disabled, Data: a.Data} } out := &CodeAction{ Title: a.Title, Kind: a.Kind, Edit: a.Edit, Diagnostics: a.Diagnostics, IsPreferred: a.IsPreferred, Disabled: a.Disabled, Data: a.Data, } if a.Command != "" { out.Command = &Command{Title: a.Title, Command: a.Command, Arguments: a.Arguments} } return out } // WorkspaceEdit groups edits across multiple documents. type WorkspaceEdit struct { Changes map[string][]TextEdit `json:"changes,omitempty"` DocumentChanges []TextDocumentEdit `json:"documentChanges,omitempty"` } // TextDocumentEdit is the per-document grouping in a WorkspaceEdit. type TextDocumentEdit struct { TextDocument VersionedTextDocumentIdentifier `json:"textDocument"` Edits []TextEdit `json:"edits"` } // VersionedTextDocumentIdentifier carries a version for optimistic edits. type VersionedTextDocumentIdentifier struct { URI string `json:"uri"` Version int `json:"version"` } // TextEdit is one textual change in a document. type TextEdit struct { Range Range `json:"range"` NewText string `json:"newText"` } // ApplyWorkspaceEditParams is the params of workspace/applyEdit. type ApplyWorkspaceEditParams struct { Label string `json:"label,omitempty"` Edit WorkspaceEdit `json:"edit"` } // ApplyWorkspaceEditResponse is the server's reply (we send these as a // reverse request — the server sends them to the client when it wants // to apply edits via a command). type ApplyWorkspaceEditResponse struct { Applied bool `json:"applied"` FailureReason string `json:"failureReason,omitempty"` } // ExecuteCommandParams is the params of workspace/executeCommand. type ExecuteCommandParams struct { Command string `json:"command"` Arguments []any `json:"arguments,omitempty"` } // DidChangeTextDocumentParams is sent when a document version moves. type DidChangeTextDocumentParams struct { TextDocument VersionedTextDocumentIdentifier `json:"textDocument"` ContentChanges []TextDocumentContentChangeEvent `json:"contentChanges"` } // TextDocumentContentChangeEvent is a single edit to a document. // When Range is nil the change replaces the whole text. type TextDocumentContentChangeEvent struct { Range *Range `json:"range,omitempty"` Text string `json:"text"` } // DidCloseTextDocumentParams is sent on textDocument/didClose. type DidCloseTextDocumentParams struct { TextDocument TextDocumentIdentifier `json:"textDocument"` }