diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index bc8f7f4353..905ce6d2e1 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7,8 +7,8 @@ use client::{User, RECEIVE_TIMEOUT}; use collections::HashSet; use editor::{ test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion, - ConfirmRename, Editor, EditorSettings, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, - ToggleCodeActions, Undo, + ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions, + Undo, }; use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions}; use futures::StreamExt as _; @@ -18,15 +18,13 @@ use gpui::{ }; use indoc::indoc; use language::{ - language_settings::{AllLanguageSettings, Formatter}, + language_settings::{AllLanguageSettings, Formatter, InlayHintKind, InlayHintSettings}, tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, OffsetRangeExt, Point, Rope, }; use live_kit_client::MacOSDisplay; use lsp::LanguageServerId; -use project::{ - search::SearchQuery, DiagnosticSummary, HoverBlockKind, InlayHintKind, Project, ProjectPath, -}; +use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath}; use rand::prelude::*; use serde_json::json; use settings::SettingsStore; @@ -7823,24 +7821,24 @@ async fn test_mutual_editor_inlay_hint_cache_update( cx_a.update(|cx| { cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.inlay_hints = Some(editor::InlayHintsContent { - enabled: Some(true), - show_type_hints: Some(true), - show_parameter_hints: Some(false), - show_other_hints: Some(true), + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: false, + show_other_hints: true, }) }); }); }); cx_b.update(|cx| { cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.inlay_hints = Some(editor::InlayHintsContent { - enabled: Some(true), - show_type_hints: Some(true), - show_parameter_hints: Some(false), - show_other_hints: Some(true), + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: false, + show_other_hints: true, }) }); }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e96b3d41c3..f70440b922 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -31,7 +31,7 @@ use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; -pub use editor_settings::{EditorSettings, InlayHints, InlayHintsContent}; +pub use editor_settings::EditorSettings; pub use element::{ Cursor, EditorElement, HighlightedRange, HighlightedRangeLine, LineWithInvisibles, }; @@ -58,7 +58,7 @@ pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ - language_settings::{self, all_language_settings}, + language_settings::{self, all_language_settings, InlayHintSettings}, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, TransactionId, @@ -88,7 +88,7 @@ use std::{ cmp::{self, Ordering, Reverse}, mem, num::NonZeroU32, - ops::{Deref, DerefMut, Range}, + ops::{ControlFlow, Deref, DerefMut, Range}, path::Path, sync::Arc, time::{Duration, Instant}, @@ -1197,7 +1197,7 @@ enum GotoDefinitionKind { #[derive(Debug, Copy, Clone)] enum InlayRefreshReason { - SettingsChange(editor_settings::InlayHints), + SettingsChange(InlayHintSettings), NewLinesShown, ExcerptEdited, RefreshRequested, @@ -1320,6 +1320,12 @@ impl Editor { } } + let inlay_hint_settings = inlay_hint_settings( + selections.newest_anchor().head(), + &buffer.read(cx).snapshot(cx), + cx, + ); + let mut this = Self { handle: cx.weak_handle(), buffer: buffer.clone(), @@ -1370,7 +1376,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_hint_cache: InlayHintCache::new(settings::get::(cx).inlay_hints), + inlay_hint_cache: InlayHintCache::new(inlay_hint_settings), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2607,35 +2613,38 @@ impl Editor { } fn refresh_inlays(&mut self, reason: InlayRefreshReason, cx: &mut ViewContext) { - if self.project.is_none() - || self.mode != EditorMode::Full - || !settings::get::(cx).inlay_hints.enabled - { + if self.project.is_none() || self.mode != EditorMode::Full { return; } let invalidate_cache = match reason { InlayRefreshReason::SettingsChange(new_settings) => { - let new_splice = self.inlay_hint_cache.update_settings( + match self.inlay_hint_cache.update_settings( &self.buffer, new_settings, self.visible_inlay_hints(cx), cx, - ); - if let Some(InlaySplice { - to_remove, - to_insert, - }) = new_splice - { - self.splice_inlay_hints(to_remove, to_insert, cx); + ) { + ControlFlow::Break(Some(InlaySplice { + to_remove, + to_insert, + })) => { + self.splice_inlay_hints(to_remove, to_insert, cx); + return; + } + ControlFlow::Break(None) => return, + ControlFlow::Continue(()) => InvalidationStrategy::Forced, } - return; } InlayRefreshReason::NewLinesShown => InvalidationStrategy::None, InlayRefreshReason::ExcerptEdited => InvalidationStrategy::OnConflict, InlayRefreshReason::RefreshRequested => InvalidationStrategy::Forced, }; + if !self.inlay_hint_cache.enabled { + return; + } + let excerpts_to_query = self .excerpt_visible_offsets(cx) .into_iter() @@ -7298,7 +7307,11 @@ impl Editor { fn settings_changed(&mut self, cx: &mut ViewContext) { self.refresh_copilot_suggestions(true, cx); self.refresh_inlays( - InlayRefreshReason::SettingsChange(settings::get::(cx).inlay_hints), + InlayRefreshReason::SettingsChange(inlay_hint_settings( + self.selections.newest_anchor().head(), + &self.buffer.read(cx).snapshot(cx), + cx, + )), cx, ); } @@ -7596,6 +7609,19 @@ impl Editor { } } +fn inlay_hint_settings( + location: Anchor, + snapshot: &MultiBufferSnapshot, + cx: &mut ViewContext<'_, '_, Editor>, +) -> InlayHintSettings { + let file = snapshot.file_at(location); + let language = snapshot.language_at(location); + let settings = all_language_settings(file, cx); + settings + .language(language.map(|l| l.name()).as_deref()) + .inlay_hints +} + fn consume_contiguous_rows( contiguous_row_selections: &mut Vec>, selection: &Selection, diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 557c3194c0..387d4d2c34 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -9,7 +9,6 @@ pub struct EditorSettings { pub show_completions_on_input: bool, pub use_on_type_format: bool, pub scrollbar: Scrollbar, - pub inlay_hints: InlayHints, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -18,14 +17,6 @@ pub struct Scrollbar { pub git_diff: bool, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct InlayHints { - pub enabled: bool, - pub show_type_hints: bool, - pub show_parameter_hints: bool, - pub show_other_hints: bool, -} - #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ShowScrollbar { @@ -42,7 +33,6 @@ pub struct EditorSettingsContent { pub show_completions_on_input: Option, pub use_on_type_format: Option, pub scrollbar: Option, - pub inlay_hints: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -51,14 +41,6 @@ pub struct ScrollbarContent { pub git_diff: Option, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct InlayHintsContent { - pub enabled: Option, - pub show_type_hints: Option, - pub show_parameter_hints: Option, - pub show_other_hints: Option, -} - impl Setting for EditorSettings { const KEY: Option<&'static str> = None; diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index d499474a01..1a03886c91 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,24 +1,29 @@ -use std::{cmp, ops::Range, sync::Arc}; +use std::{ + cmp, + ops::{ControlFlow, Range}, + sync::Arc, +}; use crate::{ - display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, - MultiBufferSnapshot, + display_map::Inlay, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, MultiBufferSnapshot, }; use anyhow::Context; use clock::Global; use gpui::{ModelHandle, Task, ViewContext}; -use language::{Buffer, BufferSnapshot}; +use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot}; use log::error; use parking_lot::RwLock; -use project::{InlayHint, InlayHintKind}; +use project::InlayHint; use collections::{hash_map, HashMap, HashSet}; +use language::language_settings::InlayHintSettings; use util::post_inc; pub struct InlayHintCache { pub hints: HashMap>>, pub allowed_hint_kinds: HashSet>, pub version: usize, + pub enabled: bool, update_tasks: HashMap, } @@ -138,9 +143,10 @@ struct ExcerptHintsUpdate { } impl InlayHintCache { - pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { + pub fn new(inlay_hint_settings: InlayHintSettings) -> Self { Self { - allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), + allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(), + enabled: inlay_hint_settings.enabled, hints: HashMap::default(), update_tasks: HashMap::default(), version: 0, @@ -150,38 +156,53 @@ impl InlayHintCache { pub fn update_settings( &mut self, multi_buffer: &ModelHandle, - inlay_hint_settings: editor_settings::InlayHints, + new_hint_settings: InlayHintSettings, visible_hints: Vec, cx: &mut ViewContext, - ) -> Option { - let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); - if !inlay_hint_settings.enabled { - if self.hints.is_empty() { + ) -> ControlFlow> { + dbg!(new_hint_settings); + let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds(); + match (self.enabled, new_hint_settings.enabled) { + (false, false) => { self.allowed_hint_kinds = new_allowed_hint_kinds; - None - } else { - self.clear(); - self.allowed_hint_kinds = new_allowed_hint_kinds; - Some(InlaySplice { - to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(), - to_insert: Vec::new(), - }) + ControlFlow::Break(None) } - } else if new_allowed_hint_kinds == self.allowed_hint_kinds { - None - } else { - let new_splice = self.new_allowed_hint_kinds_splice( - multi_buffer, - &visible_hints, - &new_allowed_hint_kinds, - cx, - ); - if new_splice.is_some() { - self.version += 1; - self.update_tasks.clear(); - self.allowed_hint_kinds = new_allowed_hint_kinds; + (true, true) => { + if new_allowed_hint_kinds == self.allowed_hint_kinds { + ControlFlow::Break(None) + } else { + let new_splice = self.new_allowed_hint_kinds_splice( + multi_buffer, + &visible_hints, + &new_allowed_hint_kinds, + cx, + ); + if new_splice.is_some() { + self.version += 1; + self.update_tasks.clear(); + self.allowed_hint_kinds = new_allowed_hint_kinds; + } + ControlFlow::Break(new_splice) + } + } + (true, false) => { + self.enabled = new_hint_settings.enabled; + self.allowed_hint_kinds = new_allowed_hint_kinds; + if self.hints.is_empty() { + ControlFlow::Break(None) + } else { + self.clear(); + ControlFlow::Break(Some(InlaySplice { + to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(), + to_insert: Vec::new(), + })) + } + } + (false, true) => { + self.enabled = new_hint_settings.enabled; + self.allowed_hint_kinds = new_allowed_hint_kinds; + ControlFlow::Continue(()) } - new_splice } } @@ -191,6 +212,9 @@ impl InlayHintCache { invalidate: InvalidationStrategy, cx: &mut ViewContext, ) { + if !self.enabled { + return; + } let update_tasks = &mut self.update_tasks; let invalidate_cache = matches!( invalidate, @@ -754,22 +778,6 @@ fn calculate_hint_updates( } } -fn allowed_hint_types( - inlay_hint_settings: editor_settings::InlayHints, -) -> HashSet> { - let mut new_allowed_hint_types = HashSet::default(); - if inlay_hint_settings.show_type_hints { - new_allowed_hint_types.insert(Some(InlayHintKind::Type)); - } - if inlay_hint_settings.show_parameter_hints { - new_allowed_hint_types.insert(Some(InlayHintKind::Parameter)); - } - if inlay_hint_settings.show_other_hints { - new_allowed_hint_types.insert(None); - } - new_allowed_hint_types -} - struct HintFetchRanges { visible_range: Range, other_ranges: Vec>, @@ -788,18 +796,19 @@ fn contains_position( mod tests { use std::sync::atomic::{AtomicU32, Ordering}; - use crate::serde_json::json; + use crate::{serde_json::json, InlayHintSettings}; use futures::StreamExt; use gpui::{TestAppContext, ViewHandle}; use language::{ - language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig, + language_settings::{AllLanguageSettings, AllLanguageSettingsContent}, + FakeLspAdapter, Language, LanguageConfig, }; use lsp::FakeLanguageServer; use project::{FakeFs, Project}; use settings::SettingsStore; use workspace::Workspace; - use crate::{editor_tests::update_test_settings, EditorSettings}; + use crate::editor_tests::update_test_settings; use super::*; @@ -926,16 +935,13 @@ mod tests { ) -> (&'static str, ViewHandle, FakeLanguageServer) { cx.update(|cx| { cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.inlay_hints = Some(crate::InlayHintsContent { - enabled: Some(true), - show_type_hints: Some( - allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), - ), - show_parameter_hints: Some( - allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), - ), - show_other_hints: Some(allowed_hint_kinds.contains(&None)), + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds + .contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), }) }); }); diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 832bb59222..aeceac9493 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -1,6 +1,6 @@ use crate::{File, Language}; use anyhow::Result; -use collections::HashMap; +use collections::{HashMap, HashSet}; use globset::GlobMatcher; use gpui::AppContext; use schemars::{ @@ -52,6 +52,7 @@ pub struct LanguageSettings { pub show_copilot_suggestions: bool, pub show_whitespaces: ShowWhitespaceSetting, pub extend_comment_on_newline: bool, + pub inlay_hints: InlayHintSettings, } #[derive(Clone, Debug, Default)] @@ -98,6 +99,8 @@ pub struct LanguageSettingsContent { pub show_whitespaces: Option, #[serde(default)] pub extend_comment_on_newline: Option, + #[serde(default)] + pub inlay_hints: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -150,6 +153,41 @@ pub enum Formatter { }, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct InlayHintSettings { + #[serde(default)] + pub enabled: bool, + #[serde(default = "default_true")] + pub show_type_hints: bool, + #[serde(default = "default_true")] + pub show_parameter_hints: bool, + #[serde(default = "default_true")] + pub show_other_hints: bool, +} + +fn default_true() -> bool { + true +} + +impl InlayHintSettings { + pub fn enabled_inlay_hint_kinds(&self) -> HashSet> { + let mut kinds = HashSet::default(); + if !self.enabled { + return kinds; + } + if self.show_type_hints { + kinds.insert(Some(InlayHintKind::Type)); + } + if self.show_parameter_hints { + kinds.insert(Some(InlayHintKind::Parameter)); + } + if self.show_other_hints { + kinds.insert(None); + } + kinds + } +} + impl AllLanguageSettings { pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings { if let Some(name) = language_name { @@ -184,6 +222,29 @@ impl AllLanguageSettings { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum InlayHintKind { + Type, + Parameter, +} + +impl InlayHintKind { + pub fn from_name(name: &str) -> Option { + match name { + "type" => Some(InlayHintKind::Type), + "parameter" => Some(InlayHintKind::Parameter), + _ => None, + } + } + + pub fn name(&self) -> &'static str { + match self { + InlayHintKind::Type => "type", + InlayHintKind::Parameter => "parameter", + } + } +} + impl settings::Setting for AllLanguageSettings { const KEY: Option<&'static str> = None; @@ -347,6 +408,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent &mut settings.extend_comment_on_newline, src.extend_comment_on_newline, ); + merge(&mut settings.inlay_hints, src.inlay_hints); fn merge(target: &mut T, value: Option) { if let Some(value) = value { *target = value; diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 674ee63349..a3c6302e29 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1,5 +1,5 @@ use crate::{ - DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintKind, InlayHintLabel, + DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink, MarkupContent, Project, ProjectTransaction, }; @@ -9,7 +9,7 @@ use client::proto::{self, PeerId}; use fs::LineEnding; use gpui::{AppContext, AsyncAppContext, ModelHandle}; use language::{ - language_settings::language_settings, + language_settings::{language_settings, InlayHintKind}, point_from_lsp, point_to_lsp, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a24581a610..0896932e7b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -31,7 +31,7 @@ use gpui::{ }; use itertools::Itertools; use language::{ - language_settings::{language_settings, FormatOnSave, Formatter}, + language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind}, point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, @@ -339,29 +339,6 @@ pub struct InlayHint { pub tooltip: Option, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum InlayHintKind { - Type, - Parameter, -} - -impl InlayHintKind { - pub fn from_name(name: &str) -> Option { - match name { - "type" => Some(InlayHintKind::Type), - "parameter" => Some(InlayHintKind::Parameter), - _ => None, - } - } - - pub fn name(&self) -> &'static str { - match self { - InlayHintKind::Type => "type", - InlayHintKind::Parameter => "parameter", - } - } -} - impl InlayHint { pub fn text(&self) -> String { match &self.label {