diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0f95ef07ac..c36eccb682 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1535,9 +1535,10 @@ impl Editor { self.completion_state.is_some() } - pub fn render_completions(&self) -> Option { + pub fn render_completions(&self, cx: &AppContext) -> Option { self.completion_state.as_ref().map(|state| { let build_settings = self.build_settings.clone(); + let settings = build_settings(cx); let completions = state.completions.clone(); UniformList::new( state.list.clone(), @@ -1547,11 +1548,23 @@ impl Editor { for completion in &completions[range] { items.push( Label::new(completion.label().to_string(), settings.style.text.clone()) + .contained() + .with_style(settings.style.autocomplete.item) .boxed(), ); } }, ) + .with_width_from_item( + state + .completions + .iter() + .enumerate() + .max_by_key(|(_, completion)| completion.label().chars().count()) + .map(|(ix, _)| ix), + ) + .contained() + .with_style(settings.style.autocomplete.container) .boxed() }) } @@ -4056,6 +4069,7 @@ impl EditorSettings { invalid_information_diagnostic: default_diagnostic_style.clone(), hint_diagnostic: default_diagnostic_style.clone(), invalid_hint_diagnostic: default_diagnostic_style.clone(), + autocomplete: Default::default(), } }, } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2e01a2758e..512c6325f9 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -886,7 +886,7 @@ impl Element for EditorElement { .to_display_point(&snapshot); if (start_row..end_row).contains(&newest_selection_head.row()) { - let list = view.render_completions().unwrap(); + let list = view.render_completions(cx).unwrap(); completions = Some((newest_selection_head, list)); } } diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 945340e4c0..9248a8d146 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -51,6 +51,7 @@ where append_items: F, padding_top: f32, padding_bottom: f32, + get_width_from_item: Option, } impl UniformList @@ -64,9 +65,15 @@ where append_items, padding_top: 0., padding_bottom: 0., + get_width_from_item: None, } } + pub fn with_width_from_item(mut self, item_ix: Option) -> Self { + self.get_width_from_item = item_ix; + self + } + pub fn with_padding_top(mut self, padding: f32) -> Self { self.padding_top = padding; self @@ -155,46 +162,70 @@ where "UniformList does not support being rendered with an unconstrained height" ); } - let mut size = constraint.max; - let mut item_constraint = - SizeConstraint::new(vec2f(size.x(), 0.0), vec2f(size.x(), f32::INFINITY)); - let mut item_height = 0.; - let mut scroll_max = 0.; - let mut items = Vec::new(); - (self.append_items)(0..1, &mut items, cx); - if let Some(first_item) = items.first_mut() { - let mut item_size = first_item.layout(item_constraint, cx); - item_size.set_x(size.x()); - item_constraint.min = item_size; - item_constraint.max = item_size; - item_height = item_size.y(); - let scroll_height = self.item_count as f32 * item_height; - if scroll_height < size.y() { - size.set_y(size.y().min(scroll_height).max(constraint.min.y())); - } - - let scroll_height = - item_height * self.item_count as f32 + self.padding_top + self.padding_bottom; - scroll_max = (scroll_height - size.y()).max(0.); - self.autoscroll(scroll_max, size.y(), item_height); - - items.clear(); - let start = cmp::min( - ((self.scroll_top() - self.padding_top) / item_height) as usize, - self.item_count, + if self.item_count == 0 { + return ( + constraint.min, + LayoutState { + item_height: 0., + scroll_max: 0., + items, + }, ); - let end = cmp::min( - self.item_count, - start + (size.y() / item_height).ceil() as usize + 1, - ); - (self.append_items)(start..end, &mut items, cx); - for item in &mut items { - item.layout(item_constraint, cx); - } + } + + let mut size = constraint.max; + let mut item_size; + if let Some(sample_item_ix) = self.get_width_from_item { + (self.append_items)(sample_item_ix..sample_item_ix + 1, &mut items, cx); + let sample_item = items.get_mut(0).unwrap(); + item_size = sample_item.layout(constraint, cx); + size.set_x(item_size.x()); } else { - size = constraint.min; + (self.append_items)(0..1, &mut items, cx); + let first_item = items.first_mut().unwrap(); + item_size = first_item.layout( + SizeConstraint::new( + vec2f(constraint.max.x(), 0.0), + vec2f(constraint.max.x(), f32::INFINITY), + ), + cx, + ); + item_size.set_x(size.x()); + } + + let item_constraint = SizeConstraint { + min: item_size, + max: vec2f(constraint.max.x(), item_size.y()), + }; + let item_height = item_size.y(); + + let scroll_height = self.item_count as f32 * item_height; + if scroll_height < size.y() { + size.set_y(size.y().min(scroll_height).max(constraint.min.y())); + } + + let scroll_height = + item_height * self.item_count as f32 + self.padding_top + self.padding_bottom; + let scroll_max = (scroll_height - size.y()).max(0.); + self.autoscroll(scroll_max, size.y(), item_height); + + let start = cmp::min( + ((self.scroll_top() - self.padding_top) / item_height) as usize, + self.item_count, + ); + let end = cmp::min( + self.item_count, + start + (size.y() / item_height).ceil() as usize + 1, + ); + items.clear(); + (self.append_items)(start..end, &mut items, cx); + for item in &mut items { + let item_size = item.layout(item_constraint, cx); + if item_size.x() > size.x() { + size.set_x(item_size.x()); + } } ( diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index fec42853dc..1c669c57f4 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -292,6 +292,7 @@ pub struct EditorStyle { pub invalid_information_diagnostic: DiagnosticStyle, pub hint_diagnostic: DiagnosticStyle, pub invalid_hint_diagnostic: DiagnosticStyle, + pub autocomplete: AutocompleteStyle, } #[derive(Clone, Deserialize, Default)] @@ -321,6 +322,13 @@ pub struct DiagnosticStyle { pub text_scale_factor: f32, } +#[derive(Clone, Deserialize, Default)] +pub struct AutocompleteStyle { + #[serde(flatten)] + pub container: ContainerStyle, + pub item: ContainerStyle, +} + #[derive(Clone, Copy, Default, Deserialize)] pub struct SelectionStyle { pub cursor: Color, @@ -408,6 +416,7 @@ impl InputEditorStyle { invalid_information_diagnostic: default_diagnostic_style.clone(), hint_diagnostic: default_diagnostic_style.clone(), invalid_hint_diagnostic: default_diagnostic_style.clone(), + autocomplete: Default::default(), } } } diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 93019db6e4..4281812d0e 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -188,7 +188,7 @@ corner_radius = 6 [project_panel] extends = "$panel" -padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 +padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 [project_panel.entry] text = "$text.1" @@ -314,6 +314,11 @@ extends = "$editor.hint_diagnostic" message.text.color = "$text.3.color" message.highlight_text.color = "$text.3.color" +[editor.autocomplete] +background = "$surface.2" +border = { width = 1, color = "$border.1" } +item.padding = 2 + [project_diagnostics] background = "$surface.1" empty_message = { extends = "$text.0", size = 18 }