use std::fmt::{self, Display}; use serde::{Deserialize, Serialize}; use crate::*; /// Describes the severity level of a [`Diagnostic`]. #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)] pub enum Severity { Hint, Info, Warning, Error, } impl Ty for Severity { fn from_value(val: Value) -> anyhow::Result { let val: String = val.typed()?; match &*val { "hint" => Ok(Severity::Hint), "info" => Ok(Severity::Info), "warning" => Ok(Severity::Warning), "error" => Ok(Severity::Error), _ => bail!("expected one of 'hint', 'info', 'warning' or 'error' (got {val:?})"), } } fn to_value(&self) -> Value { match self { Severity::Hint => "hint".into(), Severity::Info => "info".into(), Severity::Warning => "warning".into(), Severity::Error => "error".into(), } } } // TODO: move to stdx /// Helper macro that automatically generates an array /// that contains all variants of an enum macro_rules! variant_list { ( $(#[$outer:meta])* $vis: vis enum $name: ident { $($(#[$inner: meta])* $variant: ident $(= $_: literal)?),*$(,)? } ) => { $(#[$outer])* $vis enum $name { $($(#[$inner])* $variant),* } impl $name { $vis const ALL: &[$name] = &[$(Self::$variant),*]; } } } variant_list! { #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum LanguageServerFeature { Format, GotoDeclaration, GotoDefinition, GotoTypeDefinition, GotoReference, GotoImplementation, // Goto, use bitflags, combining previous Goto members? SignatureHelp, Hover, DocumentHighlight, Completion, CodeAction, WorkspaceCommand, DocumentSymbols, WorkspaceSymbols, // Symbols, use bitflags, see above? Diagnostics, RenameSymbol, InlayHints, } } impl LanguageServerFeature { fn to_str(self) -> &'static str { use LanguageServerFeature::*; match self { Format => "format", GotoDeclaration => "goto-declaration", GotoDefinition => "goto-definition", GotoTypeDefinition => "goto-type-definition", GotoReference => "goto-reference", GotoImplementation => "goto-implementation", SignatureHelp => "signature-help", Hover => "hover", DocumentHighlight => "document-highlight", Completion => "completion", CodeAction => "code-action", WorkspaceCommand => "workspace-command", DocumentSymbols => "document-symbols", WorkspaceSymbols => "workspace-symbols", Diagnostics => "diagnostics", RenameSymbol => "rename-symbol", InlayHints => "inlay-hints", } } fn description(self) -> &'static str { use LanguageServerFeature::*; match self { Format => "Use this language server for autoformatting.", GotoDeclaration => "Use this language server for the goto_declaration command.", GotoDefinition => "Use this language server for the goto_definition command.", GotoTypeDefinition => "Use this language server for the goto_type_definition command.", GotoReference => "Use this language server for the goto_reference command.", GotoImplementation => "Use this language server for the goto_implementation command.", SignatureHelp => "Use this language server to display signature help.", Hover => "Use this language server to display hover information.", DocumentHighlight => { "Use this language server for the select_references_to_symbol_under_cursor command." } Completion => "Request completion items from this language server.", CodeAction => "Use this language server for the code_action command.", WorkspaceCommand => "Use this language server for :lsp-workspace-command.", DocumentSymbols => "Use this language server for the symbol_picker command.", WorkspaceSymbols => "Use this language server for the workspace_symbol_picker command.", Diagnostics => "Display diagnostics emitted by this language server.", RenameSymbol => "Use this language server for the rename_symbol command.", InlayHints => "Display inlay hints form this language server.", } } } impl Display for LanguageServerFeature { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let feature = self.to_str(); write!(f, "{feature}",) } } impl Debug for LanguageServerFeature { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self}") } } impl Ty for LanguageServerFeature { fn from_value(val: Value) -> anyhow::Result { let val: String = val.typed()?; use LanguageServerFeature::*; match &*val { "format" => Ok(Format), "goto-declaration" => Ok(GotoDeclaration), "goto-definition" => Ok(GotoDefinition), "goto-type-definition" => Ok(GotoTypeDefinition), "goto-reference" => Ok(GotoReference), "goto-implementation" => Ok(GotoImplementation), "signature-help" => Ok(SignatureHelp), "hover" => Ok(Hover), "document-highlight" => Ok(DocumentHighlight), "completion" => Ok(Completion), "code-action" => Ok(CodeAction), "workspace-command" => Ok(WorkspaceCommand), "document-symbols" => Ok(DocumentSymbols), "workspace-symbols" => Ok(WorkspaceSymbols), "diagnostics" => Ok(Diagnostics), "rename-symbol" => Ok(RenameSymbol), "inlay-hints" => Ok(InlayHints), _ => bail!("invalid language server feature {val}"), } } fn to_value(&self) -> Value { Value::String(self.to_str().into()) } } pub fn init_language_server_config(registry: &mut OptionRegistry, languag_server: &str) { registry.register( &format!("language-servers.{languag_server}.active"), "Wether this language servers is used for a buffer", false, ); for &feature in LanguageServerFeature::ALL { registry.register( &format!("language-servers.{languag_server}.{feature}"), feature.description(), true, ); } } options! { struct LspConfig { /// Enables LSP integration. Setting to false will completely disable language servers. #[name = "lsp.enable"] #[read = copy] enable: bool = true, /// Enables LSP integration. Setting to false will completely disable language servers. #[name = "lsp.display-messages"] #[read = copy] display_messages: bool = false, /// Enable automatic popup of signature help (parameter hints) #[name = "lsp.auto-signature-help"] #[read = copy] auto_signature_help: bool = true, /// Enable automatic popup of signature help (parameter hints) #[name = "lsp.display-inlay-hints"] #[read = copy] display_inlay_hints: bool = false, /// Display docs under signature help popup #[name = "lsp.display-signature-help-docs"] #[read = copy] display_signature_help_docs: bool = true, /// Enables snippet completions. Requires a server restart /// (`:lsp-restart`) to take effect after `:config-reload`/`:set`. #[name = "lsp.snippets"] #[read = copy] snippets: bool = true, /// Include declaration in the goto references popup. #[name = "lsp.goto-reference-include-declaration"] #[read = copy] goto_reference_include_declaration: bool = true, // TODO(breaing): prefix all options below with `lsp.` /// The language-id for language servers, checkout the /// table at [TextDocumentItem](https://microsoft.github.io/ /// language-server-protocol/specifications/lsp/3.17/specification/ /// #textDocumentItem) for the right id #[name = "languague-id"] language_server_id: Option = None, // TODO(breaking): rename to root-markers to differentiate from workspace-roots // TODO: also makes this setteble on the language server /// A set of marker files to look for when trying to find the workspace /// root. For example `Cargo.lock`, `yarn.lock` roots: List = List::default(), // TODO: also makes this setteble on the language server /// Directories relative to the workspace root that are treated as LSP /// roots. The search for root markers (starting at the path of the /// file) will stop at these paths. #[name = "workspace-lsp-roots"] workspace_roots: List = List::default(), /// An array of LSP diagnostic sources assumed unchanged when the /// language server resends the same set of diagnostics. Helix can track /// the position for these diagnostics internally instead. Useful for /// diagnostics that are recomputed on save. persistent_diagnostic_sources: List = List::default(), /// Minimal severity of diagnostic for it to be displayed. (Allowed /// values: `error`, `warning`, `info`, `hint`) diagnostic_severity: Severity = Severity::Hint, } struct CompletionConfig { /// Automatic auto-completion, automatically pop up without user trigger. #[read = copy] auto_completion: bool = true, /// Whether to apply completion item instantly when selected #[read = copy] preview_completion_insert: bool = true, /// Whether to apply completion item instantly when selected #[read = copy] completion_replace: bool = false, /// Whether to apply completion item instantly when selected #[read = copy] completion_trigger_len: u8 = 2, } }