From 0baa9a782bc90fec7e60f191bfaf55f2fa2c8577 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 24 Nov 2023 17:28:59 +0100 Subject: [PATCH] Start on wiring up render_parsed_markdown --- crates/editor2/src/editor.rs | 203 +++++++++++++++-------------------- crates/fuzzy2/src/strings.rs | 2 + 2 files changed, 90 insertions(+), 115 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 5754f8be2a710d40b15def4d971ca4dfa17d7c27..aa34f9ec4761eafe9b49694a95a41140d4f5afe7 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -40,7 +40,7 @@ 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, InteractiveText, KeyContext, Model, MouseButton, ParentElement, Pixels, Render, RenderOnce, SharedString, Styled, StyledText, Subscription, Task, TextRun, TextStyle, @@ -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,71 +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( -// element_id: impl Into, -// parsed: &language::ParsedMarkdown, -// editor_style: &EditorStyle, -// workspace: Option>, -// cx: &mut ViewContext, -// ) -> InteractiveText { -// enum RenderedMarkdown {} - -// let parsed = parsed.clone(); -// let view_id = cx.view_id(); -// let code_span_background_color = editor_style.document_highlight_read_background; - -// let mut region_id = 0; - -// 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::>(), -// // ) -// // .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::(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) -// } +pub fn render_parsed_markdown( + element_id: impl Into, + parsed: &language::ParsedMarkdown, + editor_style: &EditorStyle, + workspace: Option>, + cx: &mut ViewContext, +) -> InteractiveText { + let code_span_background_color = cx + .theme() + .colors() + .editor_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); + + // 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()); + } + } + + 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 { @@ -1254,6 +1254,18 @@ impl CompletionsMenu { let selected_item = self.selected_item; let style = style.clone(); + let multiline_docs = { + let mat = &self.matches[selected_item]; + 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, + } + }; let list = uniform_list( cx.view().clone(), "completions", @@ -1332,51 +1344,12 @@ impl CompletionsMenu { .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::(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::(0, None, cx) - // .with_child(render_parsed_markdown::( - // 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.id("multiline_docs").overflow_y_scroll()) + }) + .into_any_element() } pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) { diff --git a/crates/fuzzy2/src/strings.rs b/crates/fuzzy2/src/strings.rs index ab3de3fa445860b523b2421573ee2ad3fae0debc..5028a43fd7bb3459bc1f2772a4e521bb3dec3408 100644 --- a/crates/fuzzy2/src/strings.rs +++ b/crates/fuzzy2/src/strings.rs @@ -66,6 +66,8 @@ impl StringMatch { if end == **next_start { end += self.char_len_at_index(end); positions.next(); + } else { + break; } }