Revert PR #8327 - Fix autocomplete completions being cut in half (#10084)

This reverts https://github.com/zed-industries/zed/pull/8327

That PR introduced a regression where completions' syntax highlighting
would be corrupted in a non-deterministic way, such that it varied from
frame to frame:

In the screenshot below, many of the field names (e.g. `cursor`,
`depth`) are incorrectly colored as types instead of fields. The
`finished_states` field has highlighting that changes at the wrong
offset. All of these values changed from frame to frame, creating a
strange flickering effect:

<img width="599" alt="Screenshot 2024-04-01 at 5 56 36 PM"
src="https://github.com/zed-industries/zed/assets/326587/b6a48f02-f146-4f76-92e6-32fb417d86c0">

Release Notes:

- N/A
This commit is contained in:
Max Brunsfeld 2024-04-02 09:24:55 -07:00 committed by GitHub
parent 8df888e5b1
commit 46544d7354
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 78 additions and 374 deletions

View file

@ -64,12 +64,11 @@ use gpui::{
AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds,
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView,
FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model,
MouseButton, ParentElement, Pixels, Render, SharedString, Styled, StyledText, Subscription, MouseButton, ParentElement, Pixels, Render, SharedString, StrikethroughStyle, Styled,
Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View,
VisualContext, WeakView, WhiteSpace, WindowContext, ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
}; };
use highlight_matching_bracket::refresh_matching_bracket_highlights; use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
use hover_popover::{hide_hover, HoverState}; use hover_popover::{hide_hover, HoverState};
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
pub use inline_completion_provider::*; pub use inline_completion_provider::*;
@ -82,6 +81,8 @@ use language::{
CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
}; };
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
use lsp::{DiagnosticSeverity, LanguageServerId}; use lsp::{DiagnosticSeverity, LanguageServerId};
use mouse_context_menu::MouseContextMenu; use mouse_context_menu::MouseContextMenu;
use movement::TextLayoutDetails; use movement::TextLayoutDetails;
@ -860,41 +861,25 @@ impl CompletionsMenu {
let settings = EditorSettings::get_global(cx); let settings = EditorSettings::get_global(cx);
let show_completion_documentation = settings.show_completion_documentation; let show_completion_documentation = settings.show_completion_documentation;
let font_size = style.text.font_size.to_pixels(cx.rem_size()); let widest_completion_ix = self
let padding_width = cx.rem_size().0 / 16. * 36.;
let max_completion_len = px(510.);
let widest_completion_pixels = self
.matches .matches
.iter() .iter()
.map(|mat| { .enumerate()
.max_by_key(|(_, mat)| {
let completions = self.completions.read(); let completions = self.completions.read();
let completion = &completions[mat.candidate_id]; let completion = &completions[mat.candidate_id];
let documentation = &completion.documentation; let documentation = &completion.documentation;
let mut len = completion.label.text.chars().count() as f32; let mut len = completion.label.text.chars().count();
if let Ok(text_width) = cx.text_system().layout_line( if let Some(Documentation::SingleLine(text)) = documentation {
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 {
if show_completion_documentation { if show_completion_documentation {
if let Ok(documentation_width) = cx.text_system().layout_line( len += text.chars().count();
documentation_text.as_str(),
font_size,
&[style.text.to_run(documentation_text.as_str().len())],
) {
len = documentation_width.width.0 + padding_width;
}
} }
} }
(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 completions = self.completions.clone();
let matches = self.matches.clone(); let matches = self.matches.clone();
@ -948,7 +933,7 @@ impl CompletionsMenu {
.map(|(ix, mat)| { .map(|(ix, mat)| {
let item_ix = start_ix + ix; let item_ix = start_ix + ix;
let candidate_id = mat.candidate_id; 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 { let documentation = if show_completion_documentation {
&completion.documentation &completion.documentation
@ -956,36 +941,63 @@ impl CompletionsMenu {
&None &None
}; };
let (_completion_width, completion_label, documentation_label) = let highlights = gpui::combine_highlights(
Self::truncate_completion( mat.ranges().map(|range| (range, FontWeight::BOLD.into())),
&style, styled_runs_for_code_label(&completion.label, &style.syntax).map(
cx, |(range, mut highlight)| {
mat, // Ignore font weight for syntax highlighting, as we'll use it
&mut completion.clone(), // for fuzzy matches.
documentation, highlight.font_weight = None;
max_completion_len,
); if completion.lsp_completion.deprecated.unwrap_or(false) {
div() highlight.strikethrough = Some(StrikethroughStyle {
.min_w(px(widest_completion_pixels + padding_width)) thickness: 1.0.into(),
.max_w(max_completion_len + px(padding_width)) ..Default::default()
.child( });
ListItem::new(mat.candidate_id) highlight.color = Some(cx.theme().colors().text_muted);
.inset(true) }
.selected(item_ix == selected_item)
.on_click(cx.listener(move |editor, _event, cx| { (range, highlight)
cx.stop_propagation(); },
if let Some(task) = editor.confirm_completion( ),
&ConfirmCompletion { );
item_ix: Some(item_ix), let completion_label = StyledText::new(completion.label.text.clone())
}, .with_highlights(&style.text, highlights);
cx, let documentation_label =
) { if let Some(Documentation::SingleLine(text)) = documentation {
task.detach_and_log_err(cx) if text.trim().is_empty() {
} None
})) } else {
.child(h_flex().overflow_hidden().child(completion_label)) Some(
.end_slot(documentation_label), 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::<Div>(documentation_label),
)
}) })
.collect() .collect()
}, },
@ -993,7 +1005,7 @@ impl CompletionsMenu {
.occlude() .occlude()
.max_h(max_height) .max_h(max_height)
.track_scroll(self.scroll_handle.clone()) .track_scroll(self.scroll_handle.clone())
.min_w(px(widest_completion_pixels + padding_width)); .with_width_from_item(widest_completion_ix);
Popover::new() Popover::new()
.child(list) .child(list)
@ -1003,294 +1015,6 @@ impl CompletionsMenu {
.into_any_element() .into_any_element()
} }
fn truncate_completion(
style: &EditorStyle,
cx: &mut ViewContext<Editor>,
mat: &StringMatch,
completion: &mut Completion,
documentation: &Option<Documentation>,
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::<String>()
+ "";
}
} 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::<String>()
+ "";
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::<String>()
+ "";
}
}
}
}
}
} 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::<String>()
+ "";
}
} 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::<String>()
+ ""
+ 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::<String>()
+ "";
}
}
}
}
}
} 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) { pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
let mut matches = if let Some(query) = query { let mut matches = if let Some(query) = query {
fuzzy::match_strings( fuzzy::match_strings(
@ -3578,7 +3302,7 @@ impl Editor {
.text_anchor_for_position(position, cx)?; .text_anchor_for_position(position, cx)?;
// OnTypeFormatting returns a list of edits, no need to pass them between Zed instances, // 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; let push_to_lsp_host_history = true;
// If this is not the host, append its history with new edits. // If this is not the host, append its history with new edits.
let push_to_client_history = project.read(cx).is_remote(); let push_to_client_history = project.read(cx).is_remote();
@ -9663,9 +9387,7 @@ impl Editor {
} }
} }
let Some(project) = &self.project else { let Some(project) = &self.project else { return };
return;
};
let telemetry = project.read(cx).client().telemetry().clone(); let telemetry = project.read(cx).client().telemetry().clone();
telemetry.log_edit_event("editor"); telemetry.log_edit_event("editor");
} }

View file

@ -1,8 +1,8 @@
use crate::{ use crate::{
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId, ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId,
HighlightStyle, Hitbox, IntoElement, LayoutId, LineLayout, MouseDownEvent, MouseMoveEvent, HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
WrappedLine, TOOLTIP_DELAY, TOOLTIP_DELAY,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use parking_lot::{Mutex, MutexGuard}; 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<SharedString>) -> Self {
self.text = text.into();
self
}
/// Set the styling attributes for the given text, as well as /// Set the styling attributes for the given text, as well as
/// as any ranges of text that have had their style customized. /// as any ranges of text that have had their style customized.
pub fn with_highlights( pub fn with_highlights(
@ -151,18 +145,6 @@ impl StyledText {
self.runs = Some(runs); self.runs = Some(runs);
self self
} }
/// Lays out this line of [`StyledText`] at the specified font size.
pub fn layout_line(
&self,
font_size: Pixels,
cx: &WindowContext,
) -> anyhow::Result<Arc<LineLayout>> {
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 { impl Element for StyledText {