diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 736ba74f8b..64c9bbc2d8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -64,12 +64,11 @@ use gpui::{ AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, - MouseButton, ParentElement, Pixels, Render, SharedString, Styled, StyledText, Subscription, - Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, - VisualContext, WeakView, WhiteSpace, WindowContext, + MouseButton, ParentElement, Pixels, Render, SharedString, StrikethroughStyle, Styled, + StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View, + ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; -use hover_links::{HoverLink, HoveredLinkState, InlayHighlight}; use hover_popover::{hide_hover, HoverState}; use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; pub use inline_completion_provider::*; @@ -82,6 +81,8 @@ use language::{ CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId, }; + +use hover_links::{HoverLink, HoveredLinkState, InlayHighlight}; use lsp::{DiagnosticSeverity, LanguageServerId}; use mouse_context_menu::MouseContextMenu; use movement::TextLayoutDetails; @@ -860,41 +861,25 @@ impl CompletionsMenu { let settings = EditorSettings::get_global(cx); let show_completion_documentation = settings.show_completion_documentation; - let font_size = style.text.font_size.to_pixels(cx.rem_size()); - let padding_width = cx.rem_size().0 / 16. * 36.; - - let max_completion_len = px(510.); - let widest_completion_pixels = self + let widest_completion_ix = self .matches .iter() - .map(|mat| { + .enumerate() + .max_by_key(|(_, mat)| { let completions = self.completions.read(); let completion = &completions[mat.candidate_id]; let documentation = &completion.documentation; - let mut len = completion.label.text.chars().count() as f32; - if let Ok(text_width) = cx.text_system().layout_line( - completion.label.text.as_str(), - font_size, - &[style.text.to_run(completion.label.text.as_str().len())], - ) { - len = text_width.width.0; - } - - if let Some(Documentation::SingleLine(documentation_text)) = documentation { + let mut len = completion.label.text.chars().count(); + if let Some(Documentation::SingleLine(text)) = documentation { if show_completion_documentation { - if let Ok(documentation_width) = cx.text_system().layout_line( - documentation_text.as_str(), - font_size, - &[style.text.to_run(documentation_text.as_str().len())], - ) { - len = documentation_width.width.0 + padding_width; - } + len += text.chars().count(); } } - (len + padding_width).min(max_completion_len.0 as f32) + + len }) - .fold(190_f32, |a, b| a.max(b)); + .map(|(ix, _)| ix); let completions = self.completions.clone(); let matches = self.matches.clone(); @@ -948,7 +933,7 @@ impl CompletionsMenu { .map(|(ix, mat)| { let item_ix = start_ix + ix; let candidate_id = mat.candidate_id; - let completion = completions_guard[candidate_id].clone(); + let completion = &completions_guard[candidate_id]; let documentation = if show_completion_documentation { &completion.documentation @@ -956,36 +941,63 @@ impl CompletionsMenu { &None }; - let (_completion_width, completion_label, documentation_label) = - Self::truncate_completion( - &style, - cx, - mat, - &mut completion.clone(), - documentation, - max_completion_len, - ); - div() - .min_w(px(widest_completion_pixels + padding_width)) - .max_w(max_completion_len + px(padding_width)) - .child( - ListItem::new(mat.candidate_id) - .inset(true) - .selected(item_ix == selected_item) - .on_click(cx.listener(move |editor, _event, cx| { - cx.stop_propagation(); - if let Some(task) = editor.confirm_completion( - &ConfirmCompletion { - item_ix: Some(item_ix), - }, - cx, - ) { - task.detach_and_log_err(cx) - } - })) - .child(h_flex().overflow_hidden().child(completion_label)) - .end_slot(documentation_label), - ) + let highlights = gpui::combine_highlights( + mat.ranges().map(|range| (range, FontWeight::BOLD.into())), + styled_runs_for_code_label(&completion.label, &style.syntax).map( + |(range, mut highlight)| { + // Ignore font weight for syntax highlighting, as we'll use it + // for fuzzy matches. + highlight.font_weight = None; + + if completion.lsp_completion.deprecated.unwrap_or(false) { + highlight.strikethrough = Some(StrikethroughStyle { + thickness: 1.0.into(), + ..Default::default() + }); + highlight.color = Some(cx.theme().colors().text_muted); + } + + (range, highlight) + }, + ), + ); + let completion_label = StyledText::new(completion.label.text.clone()) + .with_highlights(&style.text, highlights); + let documentation_label = + if let Some(Documentation::SingleLine(text)) = documentation { + if text.trim().is_empty() { + None + } else { + Some( + h_flex().ml_4().child( + Label::new(text.clone()) + .size(LabelSize::Small) + .color(Color::Muted), + ), + ) + } + } else { + None + }; + + div().min_w(px(220.)).max_w(px(540.)).child( + ListItem::new(mat.candidate_id) + .inset(true) + .selected(item_ix == selected_item) + .on_click(cx.listener(move |editor, _event, cx| { + cx.stop_propagation(); + if let Some(task) = editor.confirm_completion( + &ConfirmCompletion { + item_ix: Some(item_ix), + }, + cx, + ) { + task.detach_and_log_err(cx) + } + })) + .child(h_flex().overflow_hidden().child(completion_label)) + .end_slot::
(documentation_label), + ) }) .collect() }, @@ -993,7 +1005,7 @@ impl CompletionsMenu { .occlude() .max_h(max_height) .track_scroll(self.scroll_handle.clone()) - .min_w(px(widest_completion_pixels + padding_width)); + .with_width_from_item(widest_completion_ix); Popover::new() .child(list) @@ -1003,294 +1015,6 @@ impl CompletionsMenu { .into_any_element() } - fn truncate_completion( - style: &EditorStyle, - cx: &mut ViewContext, - mat: &StringMatch, - completion: &mut Completion, - documentation: &Option, - max_completion_len: Pixels, - ) -> (Pixels, StyledText, StyledText) { - let highlights = gpui::combine_highlights( - mat.ranges().map(|range| (range, FontWeight::BOLD.into())), - styled_runs_for_code_label(&completion.label, &style.syntax).map( - |(range, mut highlight)| { - // Ignore font weight for syntax highlighting, as we'll use it - // for fuzzy matches. - highlight.font_weight = None; - (range, highlight) - }, - ), - ); - - let mut inline_documentation_exists = false; - - let mut documentation_text = if let Some(Documentation::SingleLine(text)) = documentation { - inline_documentation_exists = true; - text - } else { - "" - } - .to_owned(); - let documentation_style = style.clone().text; - - let documentation_highlight_style = HighlightStyle { - color: Some(Color::Muted.color(cx)), - ..Default::default() - }; - - let documentation_highlights = vec![( - Range { - start: 0, - end: documentation_text.len(), - }, - documentation_highlight_style, - )]; - let documentation_label = StyledText::new(documentation_text.clone()) - .with_highlights(&documentation_style, documentation_highlights); - - let mut completion_label = - StyledText::new(completion.label.text.clone()).with_highlights(&style.text, highlights); - - let font_size = style.text.font_size.to_pixels(cx.rem_size()); - - let mut variable_name_end = completion.label.filter_range.end; - let mut completion_label_text = completion.label.text.clone(); - let mut variable_name_length_truncated: i32 = 0; - - let mut actual_width: Pixels = px(0.); - if let Ok(ellipsis_width) = - cx.text_system() - .layout_line("…", font_size, &[style.text.to_run("…".len())]) - { - if let Ok(completion_layout_line) = completion_label.layout_line(font_size, cx) { - if let Ok(documentation_layout_line) = - documentation_label.layout_line(font_size, cx) - { - if inline_documentation_exists { - if completion_layout_line.width + documentation_layout_line.width - > max_completion_len - { - actual_width = max_completion_len; - let width_of_variable_name = completion_layout_line - .x_for_index(completion.label.filter_range.end); - let width_of_documentation = - documentation_layout_line.x_for_index(documentation_text.len()); - - let max_width_of_variable_name = - if width_of_documentation < max_completion_len * 0.2 { - max_completion_len - width_of_documentation - } else { - max_completion_len * 0.8 - }; - - if width_of_variable_name < max_width_of_variable_name { - // Only truncate the second part. - if let Some(documentation_truncation_index) = - documentation_layout_line.index_for_x( - (max_completion_len * 0.65).min( - max_completion_len - - ellipsis_width.width - - width_of_variable_name, - ), - ) - { - variable_name_end = documentation_truncation_index + 2; - documentation_text = documentation_text - .chars() - .take(documentation_truncation_index) - .collect::() - + "…"; - } - } else { - // Truncate the first part (and optionally the second part). - if let Some(variable_name_truncation_index) = completion_layout_line - .index_for_x(max_width_of_variable_name - ellipsis_width.width) - { - variable_name_end = variable_name_truncation_index + 2; - variable_name_length_truncated = - completion.label.filter_range.end as i32 - - variable_name_end as i32 - - 1; - completion_label_text = completion - .label - .text - .chars() - .take(variable_name_truncation_index) - .collect::() - + "…"; - completion_label = - completion_label.with_text(completion_label_text.clone()); - if let Ok(new_completion_layout_line) = - completion_label.layout_line(font_size, cx) - { - let combined_width = new_completion_layout_line - .x_for_index(completion_label_text.len()) - + width_of_documentation; - if combined_width > max_completion_len { - if let Some(documentation_truncation_index) = - documentation_layout_line.index_for_x( - (max_completion_len * 0.65).min( - max_completion_len - - ellipsis_width.width - - max_width_of_variable_name, - ), - ) - { - documentation_text = documentation_text - .chars() - .take(documentation_truncation_index) - .collect::() - + "…"; - } - } - } - } - } - } else { - actual_width = - completion_layout_line.width + documentation_layout_line.width; - } - } else { - if completion_layout_line.width > max_completion_len { - actual_width = max_completion_len; - let width_of_variable_name = completion_layout_line - .x_for_index(completion.label.filter_range.end); - let width_of_type_annotation = - completion_layout_line.width - width_of_variable_name; - - let max_width_of_variable_name = - if width_of_type_annotation < max_completion_len * 0.2 { - max_completion_len - width_of_type_annotation - } else { - max_completion_len * 0.8 - }; - - if width_of_variable_name < max_width_of_variable_name { - // Only truncate the second part. - - if let Some(type_annotation_truncation_index) = - completion_layout_line - .index_for_x(max_completion_len - ellipsis_width.width) - { - completion_label_text = completion - .label - .text - .chars() - .take(type_annotation_truncation_index) - .collect::() - + "…"; - } - } else { - // Truncate the first part (and optionally the second part). - if let Some(variable_name_truncation_index) = completion_layout_line - .index_for_x(max_width_of_variable_name - ellipsis_width.width) - { - variable_name_end = variable_name_truncation_index + 2; - - variable_name_length_truncated = - completion.label.filter_range.end as i32 - - variable_name_end as i32 - - 1; - - let second_part_text = &completion.label.text.as_str() - [completion.label.filter_range.end..]; - - completion_label_text = completion - .label - .text - .chars() - .take(variable_name_truncation_index) - .collect::() - + "…" - + second_part_text; - completion_label = - completion_label.with_text(completion_label_text.clone()); - - if let Ok(layout_line) = - completion_label.layout_line(font_size, cx) - { - let combined_width = - layout_line.x_for_index(completion_label_text.len()); - if combined_width > max_completion_len { - if let Some(type_annotation_truncation_index) = - layout_line.index_for_x( - max_completion_len - ellipsis_width.width, - ) - { - completion_label_text = completion_label_text - .chars() - .take(type_annotation_truncation_index - 2) - .collect::() - + "…"; - } - } - } - } - } - } else { - actual_width = completion_layout_line.width; - } - } - } - } - }; - - // Recompute syntax highlighting. - completion.label.text = completion_label_text.clone(); - if inline_documentation_exists { - completion.label.filter_range.end = completion_label_text.len(); - for run in completion.label.runs.iter_mut() { - if run.0.start == 0 { - run.0.start = 0; - run.0.end = completion_label_text.len(); - } - } - } else { - completion.label.filter_range.end = variable_name_end; - for run in completion.label.runs.iter_mut() { - if run.0.start == 0 { - run.0.start = 0; - run.0.end = variable_name_end; - } else { - run.0.start = (run.0.start as i32 - variable_name_length_truncated) as usize; - run.0.end = (run.0.end as i32 - variable_name_length_truncated) as usize; - } - } - } - let highlights = gpui::combine_highlights( - mat.ranges().map(|range| (range, FontWeight::NORMAL.into())), - styled_runs_for_code_label(&completion.label, &style.syntax).map( - |(range, mut highlight)| { - // Ignore font weight for syntax highlighting, as we'll use it - // for fuzzy matches. - highlight.font_weight = None; - (range, highlight) - }, - ), - ); - - let completion_label = - StyledText::new(completion_label_text).with_highlights(&style.text, highlights); - - let documentation_style = style.clone().text; - let documentation_highlight_style = HighlightStyle { - color: Some(Color::Muted.color(cx)), - ..Default::default() - }; - let documentation_highlights = vec![( - Range { - start: 0, - end: documentation_text.len(), - }, - documentation_highlight_style, - )]; - - let documentation_label = StyledText::new(documentation_text) - .with_highlights(&documentation_style, documentation_highlights); - (actual_width, completion_label, documentation_label) - } - pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) { let mut matches = if let Some(query) = query { fuzzy::match_strings( @@ -3578,7 +3302,7 @@ impl Editor { .text_anchor_for_position(position, cx)?; // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances, - // hence we do LSP request & edit on host side only — add formats to host's history. + // hence we do LSP request & edit on host side only — add formats to host's history. let push_to_lsp_host_history = true; // If this is not the host, append its history with new edits. let push_to_client_history = project.read(cx).is_remote(); @@ -9663,9 +9387,7 @@ impl Editor { } } - let Some(project) = &self.project else { - return; - }; + let Some(project) = &self.project else { return }; let telemetry = project.read(cx).client().telemetry().clone(); telemetry.log_edit_event("editor"); } diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 7278560dc2..735e8dc858 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -1,8 +1,8 @@ use crate::{ ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId, - HighlightStyle, Hitbox, IntoElement, LayoutId, LineLayout, MouseDownEvent, MouseMoveEvent, - MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, - WrappedLine, TOOLTIP_DELAY, + HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, + TOOLTIP_DELAY, }; use anyhow::anyhow; use parking_lot::{Mutex, MutexGuard}; @@ -118,12 +118,6 @@ impl StyledText { } } - /// Sets the text for this [`StyledText`]. - pub fn with_text(mut self, text: impl Into) -> Self { - self.text = text.into(); - self - } - /// Set the styling attributes for the given text, as well as /// as any ranges of text that have had their style customized. pub fn with_highlights( @@ -151,18 +145,6 @@ impl StyledText { self.runs = Some(runs); self } - - /// Lays out this line of [`StyledText`] at the specified font size. - pub fn layout_line( - &self, - font_size: Pixels, - cx: &WindowContext, - ) -> anyhow::Result> { - let Some(runs) = self.runs.as_ref() else { - return Err(anyhow!("must pass runs")); - }; - cx.text_system().layout_line(&self.text, font_size, runs) - } } impl Element for StyledText {