diff options
Diffstat (limited to 'cli/lsp')
-rw-r--r-- | cli/lsp/capabilities.rs | 2 | ||||
-rw-r--r-- | cli/lsp/config.rs | 131 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 51 | ||||
-rw-r--r-- | cli/lsp/lsp_custom.rs | 3 | ||||
-rw-r--r-- | cli/lsp/mod.rs | 1 | ||||
-rw-r--r-- | cli/lsp/repl.rs | 1 | ||||
-rw-r--r-- | cli/lsp/tsc.rs | 113 |
7 files changed, 301 insertions, 1 deletions
diff --git a/cli/lsp/capabilities.rs b/cli/lsp/capabilities.rs index 0e9c93e63..03192f597 100644 --- a/cli/lsp/capabilities.rs +++ b/cli/lsp/capabilities.rs @@ -141,6 +141,6 @@ pub fn server_capabilities( "denoConfigTasks": true, "testingApi":true, })), - inlay_hint_provider: None, + inlay_hint_provider: Some(OneOf::Left(true)), } } diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 98ba5afb5..3c44ebe05 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -106,6 +106,101 @@ impl Default for CompletionSettings { } } +#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct InlayHintsSettings { + #[serde(default)] + pub parameter_names: InlayHintsParamNamesOptions, + #[serde(default)] + pub parameter_types: InlayHintsParamTypesOptions, + #[serde(default)] + pub variable_types: InlayHintsVarTypesOptions, + #[serde(default)] + pub property_declaration_types: InlayHintsPropDeclTypesOptions, + #[serde(default)] + pub function_like_return_types: InlayHintsFuncLikeReturnTypesOptions, + #[serde(default)] + pub enum_member_values: InlayHintsEnumMemberValuesOptions, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct InlayHintsParamNamesOptions { + #[serde(default)] + pub enabled: InlayHintsParamNamesEnabled, + #[serde(default = "is_true")] + pub suppress_when_argument_matches_name: bool, +} + +impl Default for InlayHintsParamNamesOptions { + fn default() -> Self { + Self { + enabled: InlayHintsParamNamesEnabled::None, + suppress_when_argument_matches_name: true, + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum InlayHintsParamNamesEnabled { + None, + Literals, + All, +} + +impl Default for InlayHintsParamNamesEnabled { + fn default() -> Self { + Self::None + } +} + +#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct InlayHintsParamTypesOptions { + #[serde(default)] + pub enabled: bool, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct InlayHintsVarTypesOptions { + #[serde(default)] + pub enabled: bool, + #[serde(default = "is_true")] + pub suppress_when_argument_matches_name: bool, +} + +impl Default for InlayHintsVarTypesOptions { + fn default() -> Self { + Self { + enabled: false, + suppress_when_argument_matches_name: true, + } + } +} + +#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct InlayHintsPropDeclTypesOptions { + #[serde(default)] + pub enabled: bool, +} + +#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct InlayHintsFuncLikeReturnTypesOptions { + #[serde(default)] + pub enabled: bool, +} + +#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct InlayHintsEnumMemberValuesOptions { + #[serde(default)] + pub enabled: bool, +} + #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ImportCompletionSettings { @@ -202,6 +297,9 @@ pub struct WorkspaceSettings { #[serde(default)] pub code_lens: CodeLensSettings, + #[serde(default)] + pub inlay_hints: InlayHintsSettings, + /// A flag that indicates if internal debug logging should be made available. #[serde(default)] pub internal_debug: bool, @@ -238,6 +336,19 @@ impl WorkspaceSettings { pub fn enabled_code_lens(&self) -> bool { self.code_lens.implementations || self.code_lens.references } + + /// Determine if any inlay hints are enabled. This allows short circuiting + /// when there are no inlay hints enabled. + pub fn enabled_inlay_hints(&self) -> bool { + !matches!( + self.inlay_hints.parameter_names.enabled, + InlayHintsParamNamesEnabled::None + ) || self.inlay_hints.parameter_types.enabled + || self.inlay_hints.variable_types.enabled + || self.inlay_hints.property_declaration_types.enabled + || self.inlay_hints.function_like_return_types.enabled + || self.inlay_hints.enum_member_values.enabled + } } #[derive(Debug, Clone, Default)] @@ -566,6 +677,26 @@ mod tests { references_all_functions: false, test: true, }, + inlay_hints: InlayHintsSettings { + parameter_names: InlayHintsParamNamesOptions { + enabled: InlayHintsParamNamesEnabled::None, + suppress_when_argument_matches_name: true + }, + parameter_types: InlayHintsParamTypesOptions { enabled: false }, + variable_types: InlayHintsVarTypesOptions { + enabled: false, + suppress_when_argument_matches_name: true + }, + property_declaration_types: InlayHintsPropDeclTypesOptions { + enabled: false + }, + function_like_return_types: InlayHintsFuncLikeReturnTypesOptions { + enabled: false + }, + enum_member_values: InlayHintsEnumMemberValuesOptions { + enabled: false + }, + }, internal_debug: false, lint: true, suggest: CompletionSettings { diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 64c7adeb6..c4617df9f 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -196,6 +196,13 @@ impl LanguageServer { } } + pub async fn inlay_hint( + &self, + params: InlayHintParams, + ) -> LspResult<Option<Vec<InlayHint>>> { + self.0.lock().await.inlay_hint(params).await + } + pub async fn virtual_text_document( &self, params: Option<Value>, @@ -2896,6 +2903,50 @@ impl Inner { ) } + async fn inlay_hint( + &self, + params: InlayHintParams, + ) -> LspResult<Option<Vec<InlayHint>>> { + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); + let workspace_settings = self.config.get_workspace_settings(); + if !self.is_diagnosable(&specifier) + || !self.config.specifier_enabled(&specifier) + || !workspace_settings.enabled_inlay_hints() + { + return Ok(None); + } + + let mark = self.performance.mark("inlay_hint", Some(¶ms)); + let asset_or_doc = self.get_asset_or_document(&specifier)?; + let line_index = asset_or_doc.line_index(); + let range = tsc::TextSpan::from_range(¶ms.range, line_index.clone()) + .map_err(|err| { + error!("Failed to convert range to text_span: {}", err); + LspError::internal_error() + })?; + let req = tsc::RequestMethod::ProvideInlayHints(( + specifier.clone(), + range, + (&workspace_settings).into(), + )); + let maybe_inlay_hints: Option<Vec<tsc::InlayHint>> = self + .ts_server + .request(self.snapshot(), req) + .await + .map_err(|err| { + error!("Unable to get inlay hints: {}", err); + LspError::internal_error() + })?; + let maybe_inlay_hints = maybe_inlay_hints.map(|hints| { + hints + .iter() + .map(|hint| hint.to_lsp(line_index.clone())) + .collect() + }); + self.performance.measure(mark); + Ok(maybe_inlay_hints) + } + async fn reload_import_registries(&mut self) -> LspResult<Option<Value>> { fs_util::remove_dir_all_if_exists(&self.module_registries_location) .await diff --git a/cli/lsp/lsp_custom.rs b/cli/lsp/lsp_custom.rs index 49c06e15c..b154234c7 100644 --- a/cli/lsp/lsp_custom.rs +++ b/cli/lsp/lsp_custom.rs @@ -11,6 +11,9 @@ pub const RELOAD_IMPORT_REGISTRIES_REQUEST: &str = "deno/reloadImportRegistries"; pub const VIRTUAL_TEXT_DOCUMENT: &str = "deno/virtualTextDocument"; +// While lsp_types supports inlay hints currently, tower_lsp does not. +pub const INLAY_HINT: &str = "textDocument/inlayHint"; + #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CacheParams { diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs index 2ee22510f..7161c5209 100644 --- a/cli/lsp/mod.rs +++ b/cli/lsp/mod.rs @@ -58,6 +58,7 @@ pub async fn start() -> Result<(), AnyError> { lsp_custom::VIRTUAL_TEXT_DOCUMENT, LanguageServer::virtual_text_document, ) + .custom_method(lsp_custom::INLAY_HINT, LanguageServer::inlay_hint) .finish(); Server::new(stdin, stdout, socket).serve(service).await; diff --git a/cli/lsp/repl.rs b/cli/lsp/repl.rs index b6329205a..60354f2d0 100644 --- a/cli/lsp/repl.rs +++ b/cli/lsp/repl.rs @@ -288,6 +288,7 @@ pub fn get_repl_workspace_settings() -> WorkspaceSettings { cache: None, import_map: None, code_lens: Default::default(), + inlay_hints: Default::default(), internal_debug: false, lint: false, tls_certificate: None, diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 51dd74240..6c2136990 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -618,6 +618,15 @@ pub struct TextSpan { } impl TextSpan { + pub fn from_range( + range: &lsp::Range, + line_index: Arc<LineIndex>, + ) -> Result<Self, AnyError> { + let start = line_index.offset_tsc(range.start)?; + let length = line_index.offset_tsc(range.end)? - start; + Ok(Self { start, length }) + } + pub fn to_range(&self, line_index: Arc<LineIndex>) -> lsp::Range { lsp::Range { start: line_index.position_tsc(self.start.into()), @@ -933,6 +942,48 @@ impl NavigateToItem { } #[derive(Debug, Clone, Deserialize)] +pub enum InlayHintKind { + Type, + Parameter, + Enum, +} + +impl InlayHintKind { + pub fn to_lsp(&self) -> Option<lsp::InlayHintKind> { + match self { + Self::Enum => None, + Self::Parameter => Some(lsp::InlayHintKind::PARAMETER), + Self::Type => Some(lsp::InlayHintKind::TYPE), + } + } +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InlayHint { + pub text: String, + pub position: u32, + pub kind: InlayHintKind, + pub whitespace_before: Option<bool>, + pub whitespace_after: Option<bool>, +} + +impl InlayHint { + pub fn to_lsp(&self, line_index: Arc<LineIndex>) -> lsp::InlayHint { + lsp::InlayHint { + position: line_index.position_tsc(self.position.into()), + label: lsp::InlayHintLabel::String(self.text.clone()), + kind: self.kind.to_lsp(), + padding_left: self.whitespace_before, + padding_right: self.whitespace_after, + text_edits: None, + tooltip: None, + data: None, + } + } +} + +#[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct NavigationTree { pub text: String, @@ -2830,6 +2881,18 @@ pub enum IncludeInlayParameterNameHints { All, } +impl From<&config::InlayHintsParamNamesEnabled> + for IncludeInlayParameterNameHints +{ + fn from(setting: &config::InlayHintsParamNamesEnabled) -> Self { + match setting { + config::InlayHintsParamNamesEnabled::All => Self::All, + config::InlayHintsParamNamesEnabled::Literals => Self::Literals, + config::InlayHintsParamNamesEnabled::None => Self::None, + } + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "kebab-case")] #[allow(dead_code)] @@ -2910,6 +2973,8 @@ pub struct UserPreferences { #[serde(skip_serializing_if = "Option::is_none")] pub include_inlay_variable_type_hints: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] + pub include_inlay_variable_type_hints_when_type_matches_name: Option<bool>, + #[serde(skip_serializing_if = "Option::is_none")] pub include_inlay_property_declaration_type_hints: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub include_inlay_function_like_return_type_hints: Option<bool>, @@ -2921,6 +2986,43 @@ pub struct UserPreferences { pub auto_import_file_exclude_patterns: Option<Vec<String>>, } +impl From<&config::WorkspaceSettings> for UserPreferences { + fn from(workspace_settings: &config::WorkspaceSettings) -> Self { + let inlay_hints = &workspace_settings.inlay_hints; + Self { + include_inlay_parameter_name_hints: Some( + (&inlay_hints.parameter_names.enabled).into(), + ), + include_inlay_parameter_name_hints_when_argument_matches_name: Some( + inlay_hints + .parameter_names + .suppress_when_argument_matches_name, + ), + include_inlay_function_parameter_type_hints: Some( + inlay_hints.parameter_types.enabled, + ), + include_inlay_variable_type_hints: Some( + inlay_hints.variable_types.enabled, + ), + include_inlay_variable_type_hints_when_type_matches_name: Some( + inlay_hints + .variable_types + .suppress_when_argument_matches_name, + ), + include_inlay_property_declaration_type_hints: Some( + inlay_hints.property_declaration_types.enabled, + ), + include_inlay_function_like_return_type_hints: Some( + inlay_hints.function_like_return_types.enabled, + ), + include_inlay_enum_member_value_hints: Some( + inlay_hints.enum_member_values.enabled, + ), + ..Default::default() + } + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct SignatureHelpItemsOptions { @@ -3053,6 +3155,8 @@ pub enum RequestMethod { ProvideCallHierarchyIncomingCalls((ModuleSpecifier, u32)), /// Resolve outgoing call hierarchy items for a specific position. ProvideCallHierarchyOutgoingCalls((ModuleSpecifier, u32)), + /// Resolve inlay hints for a specific text span + ProvideInlayHints((ModuleSpecifier, TextSpan, UserPreferences)), // Special request, used only internally by the LSP Restart, @@ -3269,6 +3373,15 @@ impl RequestMethod { "position": position }) } + RequestMethod::ProvideInlayHints((specifier, span, preferences)) => { + json!({ + "id": id, + "method": "provideInlayHints", + "specifier": state.denormalize_specifier(specifier), + "span": span, + "preferences": preferences, + }) + } RequestMethod::Restart => json!({ "id": id, "method": "restart", |