Finish autocomplete (#3404)
 TODO: - [x] Respect max height imposed by editor element - [x] Ensure mouse down event handler in `EditorElement` doesn't prevent links from being clicked Release Notes: - N/A
This commit is contained in:
+287
-384
@@ -40,11 +40,11 @@ use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use git::diff_hunk_to_display;
|
||||
use gpui::{
|
||||
actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement,
|
||||
AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
|
||||
AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context, ElementId,
|
||||
EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle,
|
||||
Hsla, InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render, RenderOnce,
|
||||
SharedString, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
|
||||
ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
|
||||
Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton, ParentElement, Pixels,
|
||||
Render, RenderOnce, SharedString, Styled, StyledText, Subscription, Task, TextRun, TextStyle,
|
||||
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HoverState};
|
||||
@@ -54,9 +54,10 @@ use itertools::Itertools;
|
||||
pub use language::{char_kind, CharKind};
|
||||
use language::{
|
||||
language_settings::{self, all_language_settings, InlayHintSettings},
|
||||
point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion,
|
||||
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, LanguageRegistry,
|
||||
LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
|
||||
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel,
|
||||
Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language,
|
||||
LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal,
|
||||
TransactionId,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
|
||||
@@ -97,7 +98,7 @@ use text::{OffsetUtf16, Rope};
|
||||
use theme::{
|
||||
ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
|
||||
};
|
||||
use ui::{h_stack, v_stack, HighlightedLabel, IconButton, StyledExt, Tooltip};
|
||||
use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, StyledExt, Tooltip};
|
||||
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
item::{ItemEvent, ItemHandle},
|
||||
@@ -115,70 +116,70 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis
|
||||
|
||||
pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
|
||||
// pub fn render_parsed_markdown<Tag: 'static>(
|
||||
// parsed: &language::ParsedMarkdown,
|
||||
// editor_style: &EditorStyle,
|
||||
// workspace: Option<WeakView<Workspace>>,
|
||||
// cx: &mut ViewContext<Editor>,
|
||||
// ) -> Text {
|
||||
// enum RenderedMarkdown {}
|
||||
pub fn render_parsed_markdown(
|
||||
element_id: impl Into<ElementId>,
|
||||
parsed: &language::ParsedMarkdown,
|
||||
editor_style: &EditorStyle,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> InteractiveText {
|
||||
let code_span_background_color = cx
|
||||
.theme()
|
||||
.colors()
|
||||
.editor_document_highlight_read_background;
|
||||
|
||||
// let parsed = parsed.clone();
|
||||
// let view_id = cx.view_id();
|
||||
// let code_span_background_color = editor_style.document_highlight_read_background;
|
||||
let highlights = gpui::combine_highlights(
|
||||
parsed.highlights.iter().filter_map(|(range, highlight)| {
|
||||
let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
|
||||
Some((range.clone(), highlight))
|
||||
}),
|
||||
parsed
|
||||
.regions
|
||||
.iter()
|
||||
.zip(&parsed.region_ranges)
|
||||
.filter_map(|(region, range)| {
|
||||
if region.code {
|
||||
Some((
|
||||
range.clone(),
|
||||
HighlightStyle {
|
||||
background_color: Some(code_span_background_color),
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
);
|
||||
let runs = text_runs_for_highlights(&parsed.text, &editor_style.text, highlights);
|
||||
|
||||
// let mut region_id = 0;
|
||||
// todo!("add the ability to change cursor style for link ranges")
|
||||
let mut links = Vec::new();
|
||||
let mut link_ranges = Vec::new();
|
||||
for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
|
||||
if let Some(link) = region.link.clone() {
|
||||
links.push(link);
|
||||
link_ranges.push(range.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// todo!()
|
||||
// // Text::new(parsed.text, editor_style.text.clone())
|
||||
// // .with_highlights(
|
||||
// // parsed
|
||||
// // .highlights
|
||||
// // .iter()
|
||||
// // .filter_map(|(range, highlight)| {
|
||||
// // let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
|
||||
// // Some((range.clone(), highlight))
|
||||
// // })
|
||||
// // .collect::<Vec<_>>(),
|
||||
// // )
|
||||
// // .with_custom_runs(parsed.region_ranges, move |ix, bounds, cx| {
|
||||
// // region_id += 1;
|
||||
// // let region = parsed.regions[ix].clone();
|
||||
|
||||
// // if let Some(link) = region.link {
|
||||
// // cx.scene().push_cursor_region(CursorRegion {
|
||||
// // bounds,
|
||||
// // style: CursorStyle::PointingHand,
|
||||
// // });
|
||||
// // cx.scene().push_mouse_region(
|
||||
// // MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds)
|
||||
// // .on_down::<Editor, _>(MouseButton::Left, move |_, _, cx| match &link {
|
||||
// // markdown::Link::Web { url } => cx.platform().open_url(url),
|
||||
// // markdown::Link::Path { path } => {
|
||||
// // if let Some(workspace) = &workspace {
|
||||
// // _ = workspace.update(cx, |workspace, cx| {
|
||||
// // workspace.open_abs_path(path.clone(), false, cx).detach();
|
||||
// // });
|
||||
// // }
|
||||
// // }
|
||||
// // }),
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// // if region.code {
|
||||
// // cx.draw_quad(Quad {
|
||||
// // bounds,
|
||||
// // background: Some(code_span_background_color),
|
||||
// // corner_radii: (2.0).into(),
|
||||
// // order: todo!(),
|
||||
// // content_mask: todo!(),
|
||||
// // border_color: todo!(),
|
||||
// // border_widths: todo!(),
|
||||
// // });
|
||||
// // }
|
||||
// // })
|
||||
// // .with_soft_wrap(true)
|
||||
// }
|
||||
InteractiveText::new(
|
||||
element_id,
|
||||
StyledText::new(parsed.text.clone()).with_runs(runs),
|
||||
)
|
||||
.on_click(link_ranges, move |clicked_range_ix, cx| {
|
||||
match &links[clicked_range_ix] {
|
||||
markdown::Link::Web { url } => cx.open_url(url),
|
||||
markdown::Link::Path { path } => {
|
||||
if let Some(workspace) = &workspace {
|
||||
_ = workspace.update(cx, |workspace, cx| {
|
||||
workspace.open_abs_path(path.clone(), false, cx).detach();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
pub struct SelectNext {
|
||||
@@ -905,12 +906,16 @@ impl ContextMenu {
|
||||
&self,
|
||||
cursor_position: DisplayPoint,
|
||||
style: &EditorStyle,
|
||||
max_height: Pixels,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> (DisplayPoint, AnyElement) {
|
||||
match self {
|
||||
ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)),
|
||||
ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
|
||||
ContextMenu::Completions(menu) => (
|
||||
cursor_position,
|
||||
menu.render(style, max_height, workspace, cx),
|
||||
),
|
||||
ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, max_height, cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1222,6 +1227,7 @@ impl CompletionsMenu {
|
||||
fn render(
|
||||
&self,
|
||||
style: &EditorStyle,
|
||||
max_height: Pixels,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> AnyElement {
|
||||
@@ -1251,7 +1257,28 @@ impl CompletionsMenu {
|
||||
let completions = self.completions.clone();
|
||||
let matches = self.matches.clone();
|
||||
let selected_item = self.selected_item;
|
||||
let style = style.clone();
|
||||
|
||||
let multiline_docs = {
|
||||
let mat = &self.matches[selected_item];
|
||||
let multiline_docs = match &self.completions.read()[mat.candidate_id].documentation {
|
||||
Some(Documentation::MultiLinePlainText(text)) => {
|
||||
Some(div().child(SharedString::from(text.clone())))
|
||||
}
|
||||
Some(Documentation::MultiLineMarkdown(parsed)) => Some(div().child(
|
||||
render_parsed_markdown("completions_markdown", parsed, &style, workspace, cx),
|
||||
)),
|
||||
_ => None,
|
||||
};
|
||||
multiline_docs.map(|div| {
|
||||
div.id("multiline_docs")
|
||||
.max_h(max_height)
|
||||
.overflow_y_scroll()
|
||||
// Prevent a mouse down on documentation from being propagated to the editor,
|
||||
// because that would move the cursor.
|
||||
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
|
||||
})
|
||||
};
|
||||
let list = uniform_list(
|
||||
cx.view().clone(),
|
||||
"completions",
|
||||
@@ -1274,151 +1301,69 @@ impl CompletionsMenu {
|
||||
&None
|
||||
};
|
||||
|
||||
// todo!("highlights")
|
||||
// let highlights = combine_syntax_and_fuzzy_match_highlights(
|
||||
// &completion.label.text,
|
||||
// style.text.color.into(),
|
||||
// styled_runs_for_code_label(&completion.label, &style.syntax),
|
||||
// &mat.positions,
|
||||
// )
|
||||
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 completion_label = StyledText::new(completion.label.text.clone())
|
||||
.with_runs(text_runs_for_highlights(
|
||||
&completion.label.text,
|
||||
&style.text,
|
||||
highlights,
|
||||
));
|
||||
let documentation_label =
|
||||
if let Some(Documentation::SingleLine(text)) = documentation {
|
||||
Some(SharedString::from(text.clone()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// todo!("documentation")
|
||||
// MouseEventHandler::new::<CompletionTag, _>(mat.candidate_id, cx, |state, _| {
|
||||
// let completion_label = HighlightedLabel::new(
|
||||
// completion.label.text.clone(),
|
||||
// combine_syntax_and_fuzzy_match_highlights(
|
||||
// &completion.label.text,
|
||||
// style.text.color.into(),
|
||||
// styled_runs_for_code_label(&completion.label, &style.syntax),
|
||||
// &mat.positions,
|
||||
// ),
|
||||
// );
|
||||
// Text::new(completion.label.text.clone(), style.text.clone())
|
||||
// .with_soft_wrap(false)
|
||||
// .with_highlights();
|
||||
|
||||
// if let Some(Documentation::SingleLine(text)) = documentation {
|
||||
// h_stack()
|
||||
// .child(completion_label)
|
||||
// .with_children((|| {
|
||||
// let text_style = TextStyle {
|
||||
// color: style.autocomplete.inline_docs_color,
|
||||
// font_size: style.text.font_size
|
||||
// * style.autocomplete.inline_docs_size_percent,
|
||||
// ..style.text.clone()
|
||||
// };
|
||||
|
||||
// let label = Text::new(text.clone(), text_style)
|
||||
// .aligned()
|
||||
// .constrained()
|
||||
// .dynamically(move |constraint, _, _| gpui::SizeConstraint {
|
||||
// min: constraint.min,
|
||||
// max: vec2f(constraint.max.x(), constraint.min.y()),
|
||||
// });
|
||||
|
||||
// if Some(item_ix) == widest_completion_ix {
|
||||
// Some(
|
||||
// label
|
||||
// .contained()
|
||||
// .with_style(style.autocomplete.inline_docs_container)
|
||||
// .into_any(),
|
||||
// )
|
||||
// } else {
|
||||
// Some(label.flex_float().into_any())
|
||||
// }
|
||||
// })())
|
||||
// .into_any()
|
||||
// } else {
|
||||
// completion_label.into_any()
|
||||
// }
|
||||
// .contained()
|
||||
// .with_style(item_style)
|
||||
// .constrained()
|
||||
// .dynamically(move |constraint, _, _| {
|
||||
// if Some(item_ix) == widest_completion_ix {
|
||||
// constraint
|
||||
// } else {
|
||||
// gpui::SizeConstraint {
|
||||
// min: constraint.min,
|
||||
// max: constraint.min,
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// .with_cursor_style(CursorStyle::PointingHand)
|
||||
// .on_down(MouseButton::Left, move |_, this, cx| {
|
||||
// this.confirm_completion(
|
||||
// &ConfirmCompletion {
|
||||
// item_ix: Some(item_ix),
|
||||
// },
|
||||
// cx,
|
||||
// )
|
||||
// .map(|task| task.detach());
|
||||
// })
|
||||
// .constrained()
|
||||
//
|
||||
div()
|
||||
.id(mat.candidate_id)
|
||||
.min_w(px(300.))
|
||||
.max_w(px(700.))
|
||||
.whitespace_nowrap()
|
||||
.overflow_hidden()
|
||||
.bg(gpui::green())
|
||||
.hover(|style| style.bg(gpui::blue()))
|
||||
.when(item_ix == selected_item, |div| div.bg(gpui::red()))
|
||||
.child(SharedString::from(completion.label.text.clone()))
|
||||
.min_w(px(300.))
|
||||
.max_w(px(700.))
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |editor, event, cx| {
|
||||
cx.stop_propagation();
|
||||
editor
|
||||
.confirm_completion(
|
||||
&ConfirmCompletion {
|
||||
item_ix: Some(item_ix),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
}),
|
||||
)
|
||||
.child(completion_label)
|
||||
.children(documentation_label)
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
.max_h(max_height)
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
.with_width_from_item(widest_completion_ix);
|
||||
|
||||
list.into_any_element()
|
||||
// todo!("multiline documentation")
|
||||
// enum MultiLineDocumentation {}
|
||||
|
||||
// Flex::row()
|
||||
// .with_child(list.flex(1., false))
|
||||
// .with_children({
|
||||
// let mat = &self.matches[selected_item];
|
||||
// let completions = self.completions.read();
|
||||
// let completion = &completions[mat.candidate_id];
|
||||
// let documentation = &completion.documentation;
|
||||
|
||||
// match documentation {
|
||||
// Some(Documentation::MultiLinePlainText(text)) => Some(
|
||||
// Flex::column()
|
||||
// .scrollable::<MultiLineDocumentation>(0, None, cx)
|
||||
// .with_child(
|
||||
// Text::new(text.clone(), style.text.clone()).with_soft_wrap(true),
|
||||
// )
|
||||
// .contained()
|
||||
// .with_style(style.autocomplete.alongside_docs_container)
|
||||
// .constrained()
|
||||
// .with_max_width(style.autocomplete.alongside_docs_max_width)
|
||||
// .flex(1., false),
|
||||
// ),
|
||||
|
||||
// Some(Documentation::MultiLineMarkdown(parsed)) => Some(
|
||||
// Flex::column()
|
||||
// .scrollable::<MultiLineDocumentation>(0, None, cx)
|
||||
// .with_child(render_parsed_markdown::<MultiLineDocumentation>(
|
||||
// parsed, &style, workspace, cx,
|
||||
// ))
|
||||
// .contained()
|
||||
// .with_style(style.autocomplete.alongside_docs_container)
|
||||
// .constrained()
|
||||
// .with_max_width(style.autocomplete.alongside_docs_max_width)
|
||||
// .flex(1., false),
|
||||
// ),
|
||||
|
||||
// _ => None,
|
||||
// }
|
||||
// })
|
||||
// .contained()
|
||||
// .with_style(style.autocomplete.container)
|
||||
// .into_any()
|
||||
Popover::new()
|
||||
.child(list)
|
||||
.when_some(multiline_docs, |popover, multiline_docs| {
|
||||
popover.aside(multiline_docs)
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
|
||||
@@ -1535,6 +1480,7 @@ impl CodeActionsMenu {
|
||||
&self,
|
||||
mut cursor_position: DisplayPoint,
|
||||
style: &EditorStyle,
|
||||
max_height: Pixels,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> (DisplayPoint, AnyElement) {
|
||||
let actions = self.actions.clone();
|
||||
@@ -1589,6 +1535,7 @@ impl CodeActionsMenu {
|
||||
.elevation_1(cx)
|
||||
.px_2()
|
||||
.py_1()
|
||||
.max_h(max_height)
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
.with_width_from_item(
|
||||
self.actions
|
||||
@@ -3695,135 +3642,135 @@ impl Editor {
|
||||
self.completion_tasks.push((id, task));
|
||||
}
|
||||
|
||||
// pub fn confirm_completion(
|
||||
// &mut self,
|
||||
// action: &ConfirmCompletion,
|
||||
// cx: &mut ViewContext<Self>,
|
||||
// ) -> Option<Task<Result<()>>> {
|
||||
// use language::ToOffset as _;
|
||||
pub fn confirm_completion(
|
||||
&mut self,
|
||||
action: &ConfirmCompletion,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
use language::ToOffset as _;
|
||||
|
||||
// let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
|
||||
// menu
|
||||
// } else {
|
||||
// return None;
|
||||
// };
|
||||
let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
|
||||
menu
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// let mat = completions_menu
|
||||
// .matches
|
||||
// .get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
|
||||
// let buffer_handle = completions_menu.buffer;
|
||||
// let completions = completions_menu.completions.read();
|
||||
// let completion = completions.get(mat.candidate_id)?;
|
||||
let mat = completions_menu
|
||||
.matches
|
||||
.get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
|
||||
let buffer_handle = completions_menu.buffer;
|
||||
let completions = completions_menu.completions.read();
|
||||
let completion = completions.get(mat.candidate_id)?;
|
||||
|
||||
// let snippet;
|
||||
// let text;
|
||||
// if completion.is_snippet() {
|
||||
// snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
|
||||
// text = snippet.as_ref().unwrap().text.clone();
|
||||
// } else {
|
||||
// snippet = None;
|
||||
// text = completion.new_text.clone();
|
||||
// };
|
||||
// let selections = self.selections.all::<usize>(cx);
|
||||
// let buffer = buffer_handle.read(cx);
|
||||
// let old_range = completion.old_range.to_offset(buffer);
|
||||
// let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
|
||||
let snippet;
|
||||
let text;
|
||||
if completion.is_snippet() {
|
||||
snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
|
||||
text = snippet.as_ref().unwrap().text.clone();
|
||||
} else {
|
||||
snippet = None;
|
||||
text = completion.new_text.clone();
|
||||
};
|
||||
let selections = self.selections.all::<usize>(cx);
|
||||
let buffer = buffer_handle.read(cx);
|
||||
let old_range = completion.old_range.to_offset(buffer);
|
||||
let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
|
||||
|
||||
// let newest_selection = self.selections.newest_anchor();
|
||||
// if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
|
||||
// return None;
|
||||
// }
|
||||
let newest_selection = self.selections.newest_anchor();
|
||||
if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// let lookbehind = newest_selection
|
||||
// .start
|
||||
// .text_anchor
|
||||
// .to_offset(buffer)
|
||||
// .saturating_sub(old_range.start);
|
||||
// let lookahead = old_range
|
||||
// .end
|
||||
// .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
|
||||
// let mut common_prefix_len = old_text
|
||||
// .bytes()
|
||||
// .zip(text.bytes())
|
||||
// .take_while(|(a, b)| a == b)
|
||||
// .count();
|
||||
let lookbehind = newest_selection
|
||||
.start
|
||||
.text_anchor
|
||||
.to_offset(buffer)
|
||||
.saturating_sub(old_range.start);
|
||||
let lookahead = old_range
|
||||
.end
|
||||
.saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
|
||||
let mut common_prefix_len = old_text
|
||||
.bytes()
|
||||
.zip(text.bytes())
|
||||
.take_while(|(a, b)| a == b)
|
||||
.count();
|
||||
|
||||
// let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
// let mut range_to_replace: Option<Range<isize>> = None;
|
||||
// let mut ranges = Vec::new();
|
||||
// for selection in &selections {
|
||||
// if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
|
||||
// let start = selection.start.saturating_sub(lookbehind);
|
||||
// let end = selection.end + lookahead;
|
||||
// if selection.id == newest_selection.id {
|
||||
// range_to_replace = Some(
|
||||
// ((start + common_prefix_len) as isize - selection.start as isize)
|
||||
// ..(end as isize - selection.start as isize),
|
||||
// );
|
||||
// }
|
||||
// ranges.push(start + common_prefix_len..end);
|
||||
// } else {
|
||||
// common_prefix_len = 0;
|
||||
// ranges.clear();
|
||||
// ranges.extend(selections.iter().map(|s| {
|
||||
// if s.id == newest_selection.id {
|
||||
// range_to_replace = Some(
|
||||
// old_range.start.to_offset_utf16(&snapshot).0 as isize
|
||||
// - selection.start as isize
|
||||
// ..old_range.end.to_offset_utf16(&snapshot).0 as isize
|
||||
// - selection.start as isize,
|
||||
// );
|
||||
// old_range.clone()
|
||||
// } else {
|
||||
// s.start..s.end
|
||||
// }
|
||||
// }));
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// let text = &text[common_prefix_len..];
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut range_to_replace: Option<Range<isize>> = None;
|
||||
let mut ranges = Vec::new();
|
||||
for selection in &selections {
|
||||
if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
|
||||
let start = selection.start.saturating_sub(lookbehind);
|
||||
let end = selection.end + lookahead;
|
||||
if selection.id == newest_selection.id {
|
||||
range_to_replace = Some(
|
||||
((start + common_prefix_len) as isize - selection.start as isize)
|
||||
..(end as isize - selection.start as isize),
|
||||
);
|
||||
}
|
||||
ranges.push(start + common_prefix_len..end);
|
||||
} else {
|
||||
common_prefix_len = 0;
|
||||
ranges.clear();
|
||||
ranges.extend(selections.iter().map(|s| {
|
||||
if s.id == newest_selection.id {
|
||||
range_to_replace = Some(
|
||||
old_range.start.to_offset_utf16(&snapshot).0 as isize
|
||||
- selection.start as isize
|
||||
..old_range.end.to_offset_utf16(&snapshot).0 as isize
|
||||
- selection.start as isize,
|
||||
);
|
||||
old_range.clone()
|
||||
} else {
|
||||
s.start..s.end
|
||||
}
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
let text = &text[common_prefix_len..];
|
||||
|
||||
// cx.emit(Event::InputHandled {
|
||||
// utf16_range_to_replace: range_to_replace,
|
||||
// text: text.into(),
|
||||
// });
|
||||
cx.emit(EditorEvent::InputHandled {
|
||||
utf16_range_to_replace: range_to_replace,
|
||||
text: text.into(),
|
||||
});
|
||||
|
||||
// self.transact(cx, |this, cx| {
|
||||
// if let Some(mut snippet) = snippet {
|
||||
// snippet.text = text.to_string();
|
||||
// for tabstop in snippet.tabstops.iter_mut().flatten() {
|
||||
// tabstop.start -= common_prefix_len as isize;
|
||||
// tabstop.end -= common_prefix_len as isize;
|
||||
// }
|
||||
self.transact(cx, |this, cx| {
|
||||
if let Some(mut snippet) = snippet {
|
||||
snippet.text = text.to_string();
|
||||
for tabstop in snippet.tabstops.iter_mut().flatten() {
|
||||
tabstop.start -= common_prefix_len as isize;
|
||||
tabstop.end -= common_prefix_len as isize;
|
||||
}
|
||||
|
||||
// this.insert_snippet(&ranges, snippet, cx).log_err();
|
||||
// } else {
|
||||
// this.buffer.update(cx, |buffer, cx| {
|
||||
// buffer.edit(
|
||||
// ranges.iter().map(|range| (range.clone(), text)),
|
||||
// this.autoindent_mode.clone(),
|
||||
// cx,
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
this.insert_snippet(&ranges, snippet, cx).log_err();
|
||||
} else {
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
ranges.iter().map(|range| (range.clone(), text)),
|
||||
this.autoindent_mode.clone(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// this.refresh_copilot_suggestions(true, cx);
|
||||
// });
|
||||
this.refresh_copilot_suggestions(true, cx);
|
||||
});
|
||||
|
||||
// let project = self.project.clone()?;
|
||||
// let apply_edits = project.update(cx, |project, cx| {
|
||||
// project.apply_additional_edits_for_completion(
|
||||
// buffer_handle,
|
||||
// completion.clone(),
|
||||
// true,
|
||||
// cx,
|
||||
// )
|
||||
// });
|
||||
// Some(cx.foreground().spawn(async move {
|
||||
// apply_edits.await?;
|
||||
// Ok(())
|
||||
// }))
|
||||
// }
|
||||
let project = self.project.clone()?;
|
||||
let apply_edits = project.update(cx, |project, cx| {
|
||||
project.apply_additional_edits_for_completion(
|
||||
buffer_handle,
|
||||
completion.clone(),
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
Some(cx.foreground_executor().spawn(async move {
|
||||
apply_edits.await?;
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
|
||||
let mut context_menu = self.context_menu.write();
|
||||
@@ -4446,12 +4393,14 @@ impl Editor {
|
||||
&self,
|
||||
cursor_position: DisplayPoint,
|
||||
style: &EditorStyle,
|
||||
max_height: Pixels,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<(DisplayPoint, AnyElement)> {
|
||||
self.context_menu.read().as_ref().map(|menu| {
|
||||
menu.render(
|
||||
cursor_position,
|
||||
style,
|
||||
max_height,
|
||||
self.workspace.as_ref().map(|(w, _)| w.clone()),
|
||||
cx,
|
||||
)
|
||||
@@ -10078,75 +10027,29 @@ pub fn diagnostic_style(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn combine_syntax_and_fuzzy_match_highlights(
|
||||
pub fn text_runs_for_highlights(
|
||||
text: &str,
|
||||
default_style: HighlightStyle,
|
||||
syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,
|
||||
match_indices: &[usize],
|
||||
) -> Vec<(Range<usize>, HighlightStyle)> {
|
||||
let mut result = Vec::new();
|
||||
let mut match_indices = match_indices.iter().copied().peekable();
|
||||
|
||||
for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())])
|
||||
{
|
||||
syntax_highlight.font_weight = None;
|
||||
|
||||
// Add highlights for any fuzzy match characters before the next
|
||||
// syntax highlight range.
|
||||
while let Some(&match_index) = match_indices.peek() {
|
||||
if match_index >= range.start {
|
||||
break;
|
||||
}
|
||||
match_indices.next();
|
||||
let end_index = char_ix_after(match_index, text);
|
||||
let mut match_style = default_style;
|
||||
match_style.font_weight = Some(FontWeight::BOLD);
|
||||
result.push((match_index..end_index, match_style));
|
||||
}
|
||||
|
||||
if range.start == usize::MAX {
|
||||
break;
|
||||
}
|
||||
|
||||
// Add highlights for any fuzzy match characters within the
|
||||
// syntax highlight range.
|
||||
let mut offset = range.start;
|
||||
while let Some(&match_index) = match_indices.peek() {
|
||||
if match_index >= range.end {
|
||||
break;
|
||||
}
|
||||
|
||||
match_indices.next();
|
||||
if match_index > offset {
|
||||
result.push((offset..match_index, syntax_highlight));
|
||||
}
|
||||
|
||||
let mut end_index = char_ix_after(match_index, text);
|
||||
while let Some(&next_match_index) = match_indices.peek() {
|
||||
if next_match_index == end_index && next_match_index < range.end {
|
||||
end_index = char_ix_after(next_match_index, text);
|
||||
match_indices.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut match_style = syntax_highlight;
|
||||
match_style.font_weight = Some(FontWeight::BOLD);
|
||||
result.push((match_index..end_index, match_style));
|
||||
offset = end_index;
|
||||
}
|
||||
|
||||
if offset < range.end {
|
||||
result.push((offset..range.end, syntax_highlight));
|
||||
default_style: &TextStyle,
|
||||
highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
|
||||
) -> Vec<TextRun> {
|
||||
let mut runs = Vec::new();
|
||||
let mut ix = 0;
|
||||
for (range, highlight) in highlights {
|
||||
if ix < range.start {
|
||||
runs.push(default_style.clone().to_run(range.start - ix));
|
||||
}
|
||||
runs.push(
|
||||
default_style
|
||||
.clone()
|
||||
.highlight(highlight)
|
||||
.to_run(range.len()),
|
||||
);
|
||||
ix = range.end;
|
||||
}
|
||||
|
||||
fn char_ix_after(ix: usize, text: &str) -> usize {
|
||||
ix + text[ix..].chars().next().unwrap().len_utf8()
|
||||
if ix < text.len() {
|
||||
runs.push(default_style.to_run(text.len() - ix));
|
||||
}
|
||||
|
||||
result
|
||||
runs
|
||||
}
|
||||
|
||||
pub fn styled_runs_for_code_label<'a>(
|
||||
|
||||
@@ -6740,75 +6740,6 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_combine_syntax_and_fuzzy_match_highlights() {
|
||||
let string = "abcdefghijklmnop";
|
||||
let syntax_ranges = [
|
||||
(
|
||||
0..3,
|
||||
HighlightStyle {
|
||||
color: Some(Hsla::red()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
4..8,
|
||||
HighlightStyle {
|
||||
color: Some(Hsla::green()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
];
|
||||
let match_indices = [4, 6, 7, 8];
|
||||
assert_eq!(
|
||||
combine_syntax_and_fuzzy_match_highlights(
|
||||
string,
|
||||
Default::default(),
|
||||
syntax_ranges.into_iter(),
|
||||
&match_indices,
|
||||
),
|
||||
&[
|
||||
(
|
||||
0..3,
|
||||
HighlightStyle {
|
||||
color: Some(Hsla::red()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
4..5,
|
||||
HighlightStyle {
|
||||
color: Some(Hsla::green()),
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
5..6,
|
||||
HighlightStyle {
|
||||
color: Some(Hsla::green()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
6..8,
|
||||
HighlightStyle {
|
||||
color: Some(Hsla::green()),
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
8..9,
|
||||
HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn go_to_prev_overlapping_diagnostic(
|
||||
executor: BackgroundExecutor,
|
||||
|
||||
@@ -268,7 +268,11 @@ impl EditorElement {
|
||||
});
|
||||
register_action(view, cx, Editor::restart_language_server);
|
||||
register_action(view, cx, Editor::show_character_palette);
|
||||
// on_action(cx, Editor::confirm_completion); todo!()
|
||||
register_action(view, cx, |editor, action, cx| {
|
||||
editor
|
||||
.confirm_completion(action, cx)
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
});
|
||||
register_action(view, cx, |editor, action, cx| {
|
||||
editor
|
||||
.confirm_code_action(action, cx)
|
||||
@@ -1022,14 +1026,8 @@ impl EditorElement {
|
||||
|
||||
if let Some((position, mut context_menu)) = layout.context_menu.take() {
|
||||
cx.with_z_index(1, |cx| {
|
||||
let line_height = self.style.text.line_height_in_pixels(cx.rem_size());
|
||||
let available_space = size(
|
||||
AvailableSpace::MinContent,
|
||||
AvailableSpace::Definite(
|
||||
(12. * line_height)
|
||||
.min((text_bounds.size.height - line_height) / 2.),
|
||||
),
|
||||
);
|
||||
let available_space =
|
||||
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
||||
let context_menu_size = context_menu.measure(available_space, cx);
|
||||
|
||||
let cursor_row_layout = &layout.position_map.line_layouts
|
||||
@@ -1974,8 +1972,9 @@ impl EditorElement {
|
||||
if let Some(newest_selection_head) = newest_selection_head {
|
||||
if (start_row..end_row).contains(&newest_selection_head.row()) {
|
||||
if editor.context_menu_visible() {
|
||||
let max_height = (12. * line_height).min((bounds.size.height - line_height) / 2.);
|
||||
context_menu =
|
||||
editor.render_context_menu(newest_selection_head, &self.style, cx);
|
||||
editor.render_context_menu(newest_selection_head, &self.style, max_height, cx);
|
||||
}
|
||||
|
||||
let active = matches!(
|
||||
|
||||
@@ -6,6 +6,8 @@ use gpui::BackgroundExecutor;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{self, Ordering},
|
||||
iter,
|
||||
ops::Range,
|
||||
sync::atomic::AtomicBool,
|
||||
};
|
||||
|
||||
@@ -54,6 +56,32 @@ pub struct StringMatch {
|
||||
pub string: String,
|
||||
}
|
||||
|
||||
impl StringMatch {
|
||||
pub fn ranges<'a>(&'a self) -> impl 'a + Iterator<Item = Range<usize>> {
|
||||
let mut positions = self.positions.iter().peekable();
|
||||
iter::from_fn(move || {
|
||||
while let Some(start) = positions.next().copied() {
|
||||
let mut end = start + self.char_len_at_index(start);
|
||||
while let Some(next_start) = positions.peek() {
|
||||
if end == **next_start {
|
||||
end += self.char_len_at_index(end);
|
||||
positions.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Some(start..end);
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn char_len_at_index(&self, ix: usize) -> usize {
|
||||
self.string[ix..].chars().next().unwrap().len_utf8()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for StringMatch {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.cmp(other).is_eq()
|
||||
|
||||
@@ -244,13 +244,17 @@ impl TextState {
|
||||
|
||||
let line_height = element_state.line_height;
|
||||
let mut line_origin = bounds.origin;
|
||||
let mut line_start_ix = 0;
|
||||
for line in &element_state.lines {
|
||||
let line_bottom = line_origin.y + line.size(line_height).height;
|
||||
if position.y > line_bottom {
|
||||
line_origin.y = line_bottom;
|
||||
line_start_ix += line.len() + 1;
|
||||
} else {
|
||||
let position_within_line = position - line_origin;
|
||||
return line.index_for_position(position_within_line, line_height);
|
||||
let index_within_line =
|
||||
line.index_for_position(position_within_line, line_height)?;
|
||||
return Some(line_start_ix + index_within_line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+160
-1
@@ -1,9 +1,12 @@
|
||||
use std::{iter, mem, ops::Range};
|
||||
|
||||
use crate::{
|
||||
black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
|
||||
Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
|
||||
FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
|
||||
SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext,
|
||||
};
|
||||
use collections::HashSet;
|
||||
use refineable::{Cascade, Refineable};
|
||||
use smallvec::SmallVec;
|
||||
pub use taffy::style::{
|
||||
@@ -168,7 +171,8 @@ impl Default for TextStyle {
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub fn highlight(mut self, style: HighlightStyle) -> Self {
|
||||
pub fn highlight(mut self, style: impl Into<HighlightStyle>) -> Self {
|
||||
let style = style.into();
|
||||
if let Some(weight) = style.font_weight {
|
||||
self.font_weight = weight;
|
||||
}
|
||||
@@ -502,6 +506,24 @@ impl From<Hsla> for HighlightStyle {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FontWeight> for HighlightStyle {
|
||||
fn from(font_weight: FontWeight) -> Self {
|
||||
Self {
|
||||
font_weight: Some(font_weight),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FontStyle> for HighlightStyle {
|
||||
fn from(font_style: FontStyle) -> Self {
|
||||
Self {
|
||||
font_style: Some(font_style),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rgba> for HighlightStyle {
|
||||
fn from(color: Rgba) -> Self {
|
||||
Self {
|
||||
@@ -510,3 +532,140 @@ impl From<Rgba> for HighlightStyle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn combine_highlights(
|
||||
a: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
|
||||
b: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
|
||||
) -> impl Iterator<Item = (Range<usize>, HighlightStyle)> {
|
||||
let mut endpoints = Vec::new();
|
||||
let mut highlights = Vec::new();
|
||||
for (range, highlight) in a.into_iter().chain(b) {
|
||||
if !range.is_empty() {
|
||||
let highlight_id = highlights.len();
|
||||
endpoints.push((range.start, highlight_id, true));
|
||||
endpoints.push((range.end, highlight_id, false));
|
||||
highlights.push(highlight);
|
||||
}
|
||||
}
|
||||
endpoints.sort_unstable_by_key(|(position, _, _)| *position);
|
||||
let mut endpoints = endpoints.into_iter().peekable();
|
||||
|
||||
let mut active_styles = HashSet::default();
|
||||
let mut ix = 0;
|
||||
iter::from_fn(move || {
|
||||
while let Some((endpoint_ix, highlight_id, is_start)) = endpoints.peek() {
|
||||
let prev_index = mem::replace(&mut ix, *endpoint_ix);
|
||||
if ix > prev_index && !active_styles.is_empty() {
|
||||
let mut current_style = HighlightStyle::default();
|
||||
for highlight_id in &active_styles {
|
||||
current_style.highlight(highlights[*highlight_id]);
|
||||
}
|
||||
return Some((prev_index..ix, current_style));
|
||||
}
|
||||
|
||||
if *is_start {
|
||||
active_styles.insert(*highlight_id);
|
||||
} else {
|
||||
active_styles.remove(highlight_id);
|
||||
}
|
||||
endpoints.next();
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{blue, green, red, yellow};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_combine_highlights() {
|
||||
assert_eq!(
|
||||
combine_highlights(
|
||||
[
|
||||
(0..5, green().into()),
|
||||
(4..10, FontWeight::BOLD.into()),
|
||||
(15..20, yellow().into()),
|
||||
],
|
||||
[
|
||||
(2..6, FontStyle::Italic.into()),
|
||||
(1..3, blue().into()),
|
||||
(21..23, red().into()),
|
||||
]
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
[
|
||||
(
|
||||
0..1,
|
||||
HighlightStyle {
|
||||
color: Some(green()),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
(
|
||||
1..2,
|
||||
HighlightStyle {
|
||||
color: Some(blue()),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
(
|
||||
2..3,
|
||||
HighlightStyle {
|
||||
color: Some(blue()),
|
||||
font_style: Some(FontStyle::Italic),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
(
|
||||
3..4,
|
||||
HighlightStyle {
|
||||
color: Some(green()),
|
||||
font_style: Some(FontStyle::Italic),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
(
|
||||
4..5,
|
||||
HighlightStyle {
|
||||
color: Some(green()),
|
||||
font_weight: Some(FontWeight::BOLD),
|
||||
font_style: Some(FontStyle::Italic),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
(
|
||||
5..6,
|
||||
HighlightStyle {
|
||||
font_weight: Some(FontWeight::BOLD),
|
||||
font_style: Some(FontStyle::Italic),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
(
|
||||
6..10,
|
||||
HighlightStyle {
|
||||
font_weight: Some(FontWeight::BOLD),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
(
|
||||
15..20,
|
||||
HighlightStyle {
|
||||
color: Some(yellow()),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
(
|
||||
21..23,
|
||||
HighlightStyle {
|
||||
color: Some(red()),
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +290,15 @@ impl TextSystem {
|
||||
text: SharedString::from(line_text),
|
||||
});
|
||||
|
||||
line_start = line_end + 1; // Skip `\n` character.
|
||||
// Skip `\n` character.
|
||||
line_start = line_end + 1;
|
||||
if let Some(run) = runs.peek_mut() {
|
||||
run.len = run.len.saturating_sub(1);
|
||||
if run.len == 0 {
|
||||
runs.next();
|
||||
}
|
||||
}
|
||||
|
||||
font_runs.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ impl ShapedLine {
|
||||
&self.layout,
|
||||
line_height,
|
||||
&self.decoration_runs,
|
||||
None,
|
||||
&[],
|
||||
cx,
|
||||
)?;
|
||||
@@ -74,7 +73,6 @@ impl WrappedLine {
|
||||
&self.layout.unwrapped_layout,
|
||||
line_height,
|
||||
&self.decoration_runs,
|
||||
self.wrap_width,
|
||||
&self.wrap_boundaries,
|
||||
cx,
|
||||
)?;
|
||||
@@ -88,7 +86,6 @@ fn paint_line(
|
||||
layout: &LineLayout,
|
||||
line_height: Pixels,
|
||||
decoration_runs: &[DecorationRun],
|
||||
wrap_width: Option<Pixels>,
|
||||
wrap_boundaries: &[WrapBoundary],
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> Result<()> {
|
||||
@@ -113,24 +110,28 @@ fn paint_line(
|
||||
|
||||
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
|
||||
wraps.next();
|
||||
if let Some((background_origin, background_color)) = current_background.take() {
|
||||
if let Some((background_origin, background_color)) = current_background.as_mut() {
|
||||
cx.paint_quad(
|
||||
Bounds {
|
||||
origin: background_origin,
|
||||
origin: *background_origin,
|
||||
size: size(glyph_origin.x - background_origin.x, line_height),
|
||||
},
|
||||
Corners::default(),
|
||||
background_color,
|
||||
*background_color,
|
||||
Edges::default(),
|
||||
transparent_black(),
|
||||
);
|
||||
background_origin.x = origin.x;
|
||||
background_origin.y += line_height;
|
||||
}
|
||||
if let Some((underline_origin, underline_style)) = current_underline.take() {
|
||||
if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
|
||||
cx.paint_underline(
|
||||
underline_origin,
|
||||
*underline_origin,
|
||||
glyph_origin.x - underline_origin.x,
|
||||
&underline_style,
|
||||
underline_style,
|
||||
);
|
||||
underline_origin.x = origin.x;
|
||||
underline_origin.y += line_height;
|
||||
}
|
||||
|
||||
glyph_origin.x = origin.x;
|
||||
@@ -149,7 +150,7 @@ fn paint_line(
|
||||
}
|
||||
if let Some(run_background) = style_run.background_color {
|
||||
current_background
|
||||
.get_or_insert((point(glyph_origin.x, origin.y), run_background));
|
||||
.get_or_insert((point(glyph_origin.x, glyph_origin.y), run_background));
|
||||
}
|
||||
|
||||
if let Some((_, underline_style)) = &mut current_underline {
|
||||
@@ -161,7 +162,7 @@ fn paint_line(
|
||||
current_underline.get_or_insert((
|
||||
point(
|
||||
glyph_origin.x,
|
||||
origin.y + baseline_offset.y + (layout.descent * 0.618),
|
||||
glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
|
||||
),
|
||||
UnderlineStyle {
|
||||
color: Some(run_underline.color.unwrap_or(style_run.color)),
|
||||
@@ -228,12 +229,18 @@ fn paint_line(
|
||||
}
|
||||
}
|
||||
|
||||
let mut last_line_end_x = origin.x + layout.width;
|
||||
if let Some(boundary) = wrap_boundaries.last() {
|
||||
let run = &layout.runs[boundary.run_ix];
|
||||
let glyph = &run.glyphs[boundary.glyph_ix];
|
||||
last_line_end_x -= glyph.position.x;
|
||||
}
|
||||
|
||||
if let Some((background_origin, background_color)) = current_background.take() {
|
||||
let line_end_x = origin.x + wrap_width.unwrap_or(Pixels::MAX).min(layout.width);
|
||||
cx.paint_quad(
|
||||
Bounds {
|
||||
origin: background_origin,
|
||||
size: size(line_end_x - background_origin.x, line_height),
|
||||
size: size(last_line_end_x - background_origin.x, line_height),
|
||||
},
|
||||
Corners::default(),
|
||||
background_color,
|
||||
@@ -243,10 +250,9 @@ fn paint_line(
|
||||
}
|
||||
|
||||
if let Some((underline_start, underline_style)) = current_underline.take() {
|
||||
let line_end_x = origin.x + wrap_width.unwrap_or(Pixels::MAX).min(layout.width);
|
||||
cx.paint_underline(
|
||||
underline_start,
|
||||
line_end_x - underline_start.x,
|
||||
last_line_end_x - underline_start.x,
|
||||
&underline_style,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user