diff --git a/Cargo.lock b/Cargo.lock index 6f3c14d17e..e168c790e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6875,7 +6875,6 @@ dependencies = [ "parking_lot", "postage", "pretty_assertions", - "pulldown-cmark 0.12.2", "rand 0.8.5", "regex", "rpc", diff --git a/crates/assistant2/src/active_thread.rs b/crates/assistant2/src/active_thread.rs index bf1a31158c..aad2aad265 100644 --- a/crates/assistant2/src/active_thread.rs +++ b/crates/assistant2/src/active_thread.rs @@ -183,7 +183,6 @@ impl ActiveThread { markdown_style, Some(self.language_registry.clone()), None, - window, cx, ) }); @@ -215,7 +214,7 @@ impl ActiveThread { ThreadEvent::StreamedAssistantText(message_id, text) => { if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) { markdown.update(cx, |markdown, cx| { - markdown.append(text, window, cx); + markdown.append(text, cx); }); } } diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 0176651f2f..bfb5969945 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -603,7 +603,6 @@ impl CompletionsMenu { hover_markdown_style(window, cx), languages, language, - window, cx, ) .copy_code_block_buttons(false) @@ -611,7 +610,7 @@ impl CompletionsMenu { }) }); markdown.update(cx, |markdown, cx| { - markdown.reset(parsed.clone(), window, cx); + markdown.reset(parsed.clone(), cx); }); div().child(markdown.clone()) } diff --git a/crates/editor/src/commit_tooltip.rs b/crates/editor/src/commit_tooltip.rs index f45b8d2b62..4bcb73f8ca 100644 --- a/crates/editor/src/commit_tooltip.rs +++ b/crates/editor/src/commit_tooltip.rs @@ -2,10 +2,10 @@ use futures::Future; use git::blame::BlameEntry; use git::PullRequest; use gpui::{ - App, Asset, ClipboardItem, Element, ParentElement, Render, ScrollHandle, - StatefulInteractiveElement, WeakEntity, + App, Asset, ClipboardItem, Element, Entity, MouseButton, ParentElement, Render, ScrollHandle, + StatefulInteractiveElement, }; -use language::ParsedMarkdown; +use markdown::Markdown; use settings::Settings; use std::hash::Hash; use theme::ThemeSettings; @@ -13,10 +13,9 @@ use time::{OffsetDateTime, UtcOffset}; use time_format::format_local_timestamp; use ui::{prelude::*, tooltip_container, Avatar, Divider, IconButtonShape}; use url::Url; -use workspace::Workspace; use crate::git::blame::GitRemote; -use crate::EditorStyle; +use crate::hover_popover::hover_markdown_style; #[derive(Clone, Debug)] pub struct CommitDetails { @@ -30,7 +29,6 @@ pub struct CommitDetails { #[derive(Clone, Debug, Default)] pub struct ParsedCommitMessage { pub message: SharedString, - pub parsed_message: ParsedMarkdown, pub permalink: Option, pub pull_request: Option, pub remote: Option, @@ -115,48 +113,65 @@ impl Asset for CommitAvatarAsset { pub struct CommitTooltip { commit: CommitDetails, - editor_style: EditorStyle, - workspace: Option>, scroll_handle: ScrollHandle, + markdown: Entity, } impl CommitTooltip { pub fn blame_entry( - blame: BlameEntry, + blame: &BlameEntry, details: Option, - style: EditorStyle, - workspace: Option>, + window: &mut Window, + cx: &mut Context, ) -> Self { let commit_time = blame .committer_time .and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok()) .unwrap_or(OffsetDateTime::now_utc()); + Self::new( CommitDetails { sha: blame.sha.to_string().into(), commit_time, committer_name: blame .committer_name + .clone() .unwrap_or("".to_string()) .into(), - committer_email: blame.committer_email.unwrap_or("".to_string()).into(), + committer_email: blame + .committer_email + .clone() + .unwrap_or("".to_string()) + .into(), message: details, }, - style, - workspace, + window, + cx, ) } - pub fn new( - commit: CommitDetails, - editor_style: EditorStyle, - workspace: Option>, - ) -> Self { + pub fn new(commit: CommitDetails, window: &mut Window, cx: &mut Context) -> Self { + let mut style = hover_markdown_style(window, cx); + if let Some(code_block) = &style.code_block.text { + style.base_text_style.refine(code_block); + } + let markdown = cx.new(|cx| { + Markdown::new( + commit + .message + .as_ref() + .map(|message| message.message.clone()) + .unwrap_or_default(), + style, + None, + None, + cx, + ) + }); Self { - editor_style, commit, - workspace, scroll_handle: ScrollHandle::new(), + markdown, } } } @@ -186,16 +201,7 @@ impl Render for CommitTooltip { .commit .message .as_ref() - .map(|details| { - crate::render_parsed_markdown( - "blame-message", - &details.parsed_message, - &self.editor_style, - self.workspace.clone(), - cx, - ) - .into_any() - }) + .map(|_| self.markdown.clone().into_any_element()) .unwrap_or("".into_any()); let pull_request = self @@ -210,6 +216,7 @@ impl Render for CommitTooltip { tooltip_container(window, cx, move |this, _, cx| { this.occlude() .on_mouse_move(|_, _, cx| cx.stop_propagation()) + .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) .child( v_flex() .w(gpui::rems(30.)) @@ -235,7 +242,6 @@ impl Render for CommitTooltip { .child( div() .id("inline-blame-commit-message") - .occlude() .child(message) .max_h(message_max_height) .overflow_y_scroll() diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9198129e17..aaa49a1390 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -81,11 +81,11 @@ use git::blame::GitBlame; use gpui::{ div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, AvailableSpace, Background, Bounds, - ClipboardEntry, ClipboardItem, Context, DispatchPhase, ElementId, Entity, EntityInputHandler, + ClipboardEntry, ClipboardItem, Context, DispatchPhase, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, - HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers, MouseButton, MouseDownEvent, - PaintQuad, ParentElement, Pixels, Render, SharedString, Size, Styled, StyledText, Subscription, - Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, + HighlightStyle, Hsla, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad, + ParentElement, Pixels, Render, SharedString, Size, Styled, StyledText, Subscription, Task, + TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; @@ -98,7 +98,7 @@ pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; use language::{ language_settings::{self, all_language_settings, language_settings, InlayHintSettings}, - markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel, + point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel, CursorShape, Diagnostic, DiskState, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, @@ -214,72 +214,6 @@ const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers { function: false, }; -pub fn render_parsed_markdown( - element_id: impl Into, - parsed: &language::ParsedMarkdown, - editor_style: &EditorStyle, - workspace: Option>, - cx: &mut App, -) -> 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 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_highlights(&editor_style.text, highlights), - ) - .on_click( - link_ranges, - move |clicked_range_ix, window, 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, window, cx) - .detach(); - }); - } - } - }, - ) -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum InlayId { InlineCompletion(usize), @@ -745,6 +679,7 @@ pub struct Editor { show_git_blame_gutter: bool, show_git_blame_inline: bool, show_git_blame_inline_delay_task: Option>, + git_blame_inline_tooltip: Option>, distinguish_unstaged_diff_hunks: bool, git_blame_inline_enabled: bool, serialize_dirty_buffers: bool, @@ -1454,6 +1389,7 @@ impl Editor { distinguish_unstaged_diff_hunks: false, show_selection_menu: None, show_git_blame_inline_delay_task: None, + git_blame_inline_tooltip: None, git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(), serialize_dirty_buffers: ProjectSettings::get_global(cx) .session @@ -13434,7 +13370,12 @@ impl Editor { pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool { self.show_git_blame_inline - && self.focus_handle.is_focused(window) + && (self.focus_handle.is_focused(window) + || self + .git_blame_inline_tooltip + .as_ref() + .and_then(|t| t.upgrade()) + .is_some()) && !self.newest_selection_head_on_empty_line(cx) && self.has_blame_entries(cx) } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 144782b9a4..0f85e81a70 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -21,17 +21,14 @@ use language::{ BracketPairConfig, Capability::ReadWrite, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName, - Override, ParsedMarkdown, Point, + Override, Point, }; use language_settings::{Formatter, FormatterList, IndentGuideSettings}; use multi_buffer::IndentGuide; use parking_lot::Mutex; use pretty_assertions::{assert_eq, assert_ne}; +use project::project_settings::{LspSettings, ProjectSettings}; use project::FakeFs; -use project::{ - lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT, - project_settings::{LspSettings, ProjectSettings}, -}; use serde_json::{self, json}; use std::{cell::RefCell, future::Future, rc::Rc, time::Instant}; use std::{ @@ -8118,12 +8115,10 @@ async fn test_handle_input_for_show_signature_help_auto_signature_help_true( cx.editor(|editor, _, _| { let signature_help_state = editor.signature_help_state.popover().cloned(); - assert!(signature_help_state.is_some()); - let ParsedMarkdown { - text, highlights, .. - } = signature_help_state.unwrap().parsed_content; - assert_eq!(text, "param1: u8, param2: u8"); - assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]); + assert_eq!( + signature_help_state.unwrap().label, + "param1: u8, param2: u8" + ); }); } @@ -8291,11 +8286,10 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui: cx.update_editor(|editor, _, _| { let signature_help_state = editor.signature_help_state.popover().cloned(); assert!(signature_help_state.is_some()); - let ParsedMarkdown { - text, highlights, .. - } = signature_help_state.unwrap().parsed_content; - assert_eq!(text, "param1: u8, param2: u8"); - assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]); + assert_eq!( + signature_help_state.unwrap().label, + "param1: u8, param2: u8" + ); editor.signature_help_state = SignatureHelpState::default(); }); @@ -8333,11 +8327,10 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui: cx.editor(|editor, _, _| { let signature_help_state = editor.signature_help_state.popover().cloned(); assert!(signature_help_state.is_some()); - let ParsedMarkdown { - text, highlights, .. - } = signature_help_state.unwrap().parsed_content; - assert_eq!(text, "param1: u8, param2: u8"); - assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]); + assert_eq!( + signature_help_state.unwrap().label, + "param1: u8, param2: u8" + ); }); } @@ -8395,11 +8388,10 @@ async fn test_signature_help(cx: &mut gpui::TestAppContext) { cx.editor(|editor, _, _| { let signature_help_state = editor.signature_help_state.popover().cloned(); assert!(signature_help_state.is_some()); - let ParsedMarkdown { - text, highlights, .. - } = signature_help_state.unwrap().parsed_content; - assert_eq!(text, "param1: u8, param2: u8"); - assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]); + assert_eq!( + signature_help_state.unwrap().label, + "param1: u8, param2: u8" + ); }); // When exiting outside from inside the brackets, `signature_help` is closed. diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2ff272d996..ae7923380a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -38,8 +38,7 @@ use gpui::{ GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Keystroke, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, - StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement, - WeakEntity, Window, + StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement, Window, }; use itertools::Itertools; use language::{ @@ -76,7 +75,7 @@ use ui::{ }; use unicode_segmentation::UnicodeSegmentation; use util::{RangeExt, ResultExt}; -use workspace::{item::Item, notifications::NotifyTaskExt, Workspace}; +use workspace::{item::Item, notifications::NotifyTaskExt}; const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.; const MIN_SCROLL_THUMB_SIZE: f32 = 25.; @@ -1630,13 +1629,6 @@ impl EditorElement { return None; } - let workspace = self - .editor - .read(cx) - .workspace - .as_ref() - .map(|(w, _)| w.clone()); - let editor = self.editor.read(cx); let blame = editor.blame.clone()?; let padding = { @@ -1665,7 +1657,7 @@ impl EditorElement { .flatten()?; let mut element = - render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx); + render_inline_blame_entry(self.editor.clone(), &blame, blame_entry, &self.style, cx); let start_y = content_origin.y + line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height); @@ -4251,12 +4243,7 @@ impl EditorElement { let maybe_element = self.editor.update(cx, |editor, cx| { if let Some(popover) = editor.signature_help_state.popover_mut() { - let element = popover.render( - &self.style, - max_size, - editor.workspace.as_ref().map(|(w, _)| w.clone()), - cx, - ); + let element = popover.render(max_size, cx); Some(element) } else { None @@ -5903,10 +5890,10 @@ fn prepaint_gutter_button( } fn render_inline_blame_entry( + editor: Entity, blame: &gpui::Entity, blame_entry: BlameEntry, style: &EditorStyle, - workspace: Option>, cx: &mut App, ) -> AnyElement { let relative_timestamp = blame_entry_relative_timestamp(&blame_entry); @@ -5922,11 +5909,8 @@ fn render_inline_blame_entry( } _ => format!("{}, {}", author, relative_timestamp), }; - - let details = blame.read(cx).details_for_entry(&blame_entry); - - let tooltip = - cx.new(|_| CommitTooltip::blame_entry(blame_entry, details, style.clone(), workspace)); + let blame = blame.clone(); + let blame_entry = blame_entry.clone(); h_flex() .id("inline-blame") @@ -5937,7 +5921,15 @@ fn render_inline_blame_entry( .child(Icon::new(IconName::FileGit).color(Color::Hint)) .child(text) .gap_2() - .hoverable_tooltip(move |_, _| tooltip.clone().into()) + .hoverable_tooltip(move |window, cx| { + let details = blame.read(cx).details_for_entry(&blame_entry); + let tooltip = + cx.new(|cx| CommitTooltip::blame_entry(&blame_entry, details, window, cx)); + editor.update(cx, |editor, _| { + editor.git_blame_inline_tooltip = Some(tooltip.downgrade()) + }); + tooltip.into() + }) .into_any() } @@ -5971,20 +5963,8 @@ fn render_blame_entry( let author_name = blame_entry.author.as_deref().unwrap_or(""); let name = util::truncate_and_trailoff(author_name, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED); - let details = blame.read(cx).details_for_entry(&blame_entry); - let workspace = editor.read(cx).workspace.as_ref().map(|(w, _)| w.clone()); - - let tooltip = cx.new(|_| { - CommitTooltip::blame_entry( - blame_entry.clone(), - details.clone(), - style.clone(), - workspace, - ) - }); - h_flex() .w_full() .justify_between() @@ -6018,16 +5998,20 @@ fn render_blame_entry( }) .hover(|style| style.bg(cx.theme().colors().element_hover)) .when_some( - details.and_then(|details| details.permalink), + details + .as_ref() + .and_then(|details| details.permalink.clone()), |this, url| { - let url = url.clone(); this.cursor_pointer().on_click(move |_, _, cx| { cx.stop_propagation(); cx.open_url(url.as_str()) }) }, ) - .hoverable_tooltip(move |_, _| tooltip.clone().into()) + .hoverable_tooltip(move |window, cx| { + cx.new(|cx| CommitTooltip::blame_entry(&blame_entry, details.clone(), window, cx)) + .into() + }) .into_any() } @@ -7068,12 +7052,11 @@ impl Element for EditorElement { blame.blame_for_rows(&[row_infos], cx).next() }) .flatten()?; - let workspace = editor.workspace.as_ref().map(|(w, _)| w.to_owned()); let mut element = render_inline_blame_entry( + self.editor.clone(), blame, blame_entry, &style, - workspace, cx, ); let inline_blame_padding = INLINE_BLAME_PADDING_EM_WIDTHS * em_advance; diff --git a/crates/editor/src/git/blame.rs b/crates/editor/src/git/blame.rs index 268738ab25..0ef829786f 100644 --- a/crates/editor/src/git/blame.rs +++ b/crates/editor/src/git/blame.rs @@ -6,7 +6,7 @@ use git::{ }; use gpui::{App, AppContext as _, Context, Entity, Subscription, Task}; use http_client::HttpClient; -use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown}; +use language::{Bias, Buffer, BufferSnapshot, Edit}; use multi_buffer::RowInfo; use project::{Project, ProjectItem}; use smallvec::SmallVec; @@ -229,6 +229,9 @@ impl GitBlame { } pub fn focus(&mut self, cx: &mut Context) { + if self.focused { + return; + } self.focused = true; if self.changed_while_blurred { self.changed_while_blurred = false; @@ -355,7 +358,6 @@ impl GitBlame { let buffer_edits = self.buffer.update(cx, |buffer, _| buffer.subscribe()); let snapshot = self.buffer.read(cx).snapshot(); let blame = self.project.read(cx).blame_buffer(&self.buffer, None, cx); - let languages = self.project.read(cx).languages().clone(); let provider_registry = GitHostingProviderRegistry::default_global(cx); self.task = cx.spawn(|this, mut cx| async move { @@ -379,7 +381,6 @@ impl GitBlame { remote_url, &permalinks, provider_registry, - &languages, ) .await; @@ -475,7 +476,6 @@ async fn parse_commit_messages( remote_url: Option, deprecated_permalinks: &HashMap, provider_registry: Arc, - languages: &Arc, ) -> HashMap { let mut commit_details = HashMap::default(); @@ -484,8 +484,6 @@ async fn parse_commit_messages( .and_then(|remote_url| parse_git_remote_url(provider_registry, remote_url)); for (oid, message) in messages { - let parsed_message = parse_markdown(&message, languages).await; - let permalink = if let Some((provider, git_remote)) = parsed_remote_url.as_ref() { Some(provider.build_commit_permalink( git_remote, @@ -517,7 +515,6 @@ async fn parse_commit_messages( oid, ParsedCommitMessage { message: message.into(), - parsed_message, permalink, remote, pull_request, @@ -528,23 +525,6 @@ async fn parse_commit_messages( commit_details } -async fn parse_markdown(text: &str, language_registry: &Arc) -> ParsedMarkdown { - let mut parsed_message = ParsedMarkdown::default(); - - markdown::parse_markdown_block( - text, - Some(language_registry), - None, - &mut parsed_message.text, - &mut parsed_message.highlights, - &mut parsed_message.region_ranges, - &mut parsed_message.regions, - ) - .await; - - parsed_message -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index bec413329a..e4d7676a7f 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -358,15 +358,8 @@ fn show_hover( }, ..Default::default() }; - Markdown::new_text( - SharedString::new(text), - markdown_style.clone(), - None, - None, - window, - cx, - ) - .open_url(open_markdown_url) + Markdown::new_text(SharedString::new(text), markdown_style.clone(), cx) + .open_url(open_markdown_url) }) .ok(); @@ -573,7 +566,6 @@ async fn parse_blocks( hover_markdown_style(window, cx), Some(language_registry.clone()), fallback_language_name, - window, cx, ) .copy_code_block_buttons(false) diff --git a/crates/editor/src/signature_help.rs b/crates/editor/src/signature_help.rs index f2e479f877..c75e45c1e4 100644 --- a/crates/editor/src/signature_help.rs +++ b/crates/editor/src/signature_help.rs @@ -1,17 +1,19 @@ -mod popover; -mod state; - use crate::actions::ShowSignatureHelp; use crate::{Editor, EditorSettings, ToggleAutoSignatureHelp}; -use gpui::{App, Context, Window}; -use language::markdown::parse_markdown; +use gpui::{ + combine_highlights, App, Context, HighlightStyle, MouseButton, Size, StyledText, Task, + TextStyle, Window, +}; use language::BufferSnapshot; use multi_buffer::{Anchor, ToOffset}; use settings::Settings; use std::ops::Range; - -pub use popover::SignatureHelpPopover; -pub use state::SignatureHelpState; +use text::Rope; +use theme::ThemeSettings; +use ui::{ + div, relative, ActiveTheme, AnyElement, InteractiveElement, IntoElement, ParentElement, Pixels, + SharedString, StatefulInteractiveElement, Styled, StyledExt, +}; // Language-specific settings may define quotes as "brackets", so filter them out separately. const QUOTE_PAIRS: [(&str, &str); 3] = [("'", "'"), ("\"", "\""), ("`", "`")]; @@ -168,67 +170,149 @@ impl Editor { else { return; }; + let Some(lsp_store) = self.project.as_ref().map(|p| p.read(cx).lsp_store()) else { + return; + }; + let task = lsp_store.update(cx, |lsp_store, cx| { + lsp_store.signature_help(&buffer, buffer_position, cx) + }); + let language = self.language_at(position, cx); self.signature_help_state .set_task(cx.spawn_in(window, move |editor, mut cx| async move { - let signature_help = editor - .update(&mut cx, |editor, cx| { - let language = editor.language_at(position, cx); - let project = editor.project.clone()?; - let (markdown, language_registry) = { - project.update(cx, |project, cx| { - let language_registry = project.languages().clone(); - ( - project.signature_help(&buffer, buffer_position, cx), - language_registry, - ) - }) - }; - Some((markdown, language_registry, language)) - }) - .ok() - .flatten(); - let signature_help_popover = if let Some(( - signature_help_task, - language_registry, - language, - )) = signature_help - { - // TODO allow multiple signature helps inside the same popover - if let Some(mut signature_help) = signature_help_task.await.into_iter().next() { - let mut parsed_content = parse_markdown( - signature_help.markdown.as_str(), - Some(&language_registry), - language, - ) - .await; - parsed_content - .highlights - .append(&mut signature_help.highlights); - Some(SignatureHelpPopover { parsed_content }) - } else { - None - } - } else { - None - }; + let signature_help = task.await; editor .update(&mut cx, |editor, cx| { - let previous_popover = editor.signature_help_state.popover(); - if previous_popover != signature_help_popover.as_ref() { - if let Some(signature_help_popover) = signature_help_popover { - editor - .signature_help_state - .set_popover(signature_help_popover); - } else { - editor - .signature_help_state - .hide(SignatureHelpHiddenBy::AutoClose); - } - cx.notify(); + let Some(mut signature_help) = signature_help.into_iter().next() else { + editor + .signature_help_state + .hide(SignatureHelpHiddenBy::AutoClose); + return; + }; + + if let Some(language) = language { + let text = Rope::from(signature_help.label.clone()); + let highlights = language + .highlight_text(&text, 0..signature_help.label.len()) + .into_iter() + .flat_map(|(range, highlight_id)| { + Some((range, highlight_id.style(&cx.theme().syntax())?)) + }); + signature_help.highlights = + combine_highlights(signature_help.highlights, highlights).collect() } + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: cx.theme().colors().text, + font_family: settings.buffer_font.family.clone(), + font_fallbacks: settings.buffer_font.fallbacks.clone(), + font_size: settings.buffer_font_size.into(), + font_weight: settings.buffer_font.weight, + line_height: relative(settings.buffer_line_height.value()), + ..Default::default() + }; + + let signature_help_popover = SignatureHelpPopover { + label: signature_help.label.into(), + highlights: signature_help.highlights, + style: text_style, + }; + editor + .signature_help_state + .set_popover(signature_help_popover); + cx.notify(); }) .ok(); })); } } + +#[derive(Default, Debug)] +pub struct SignatureHelpState { + task: Option>, + popover: Option, + hidden_by: Option, + backspace_pressed: bool, +} + +impl SignatureHelpState { + pub fn set_task(&mut self, task: Task<()>) { + self.task = Some(task); + self.hidden_by = None; + } + + pub fn kill_task(&mut self) { + self.task = None; + } + + #[cfg(test)] + pub fn popover(&self) -> Option<&SignatureHelpPopover> { + self.popover.as_ref() + } + + pub fn popover_mut(&mut self) -> Option<&mut SignatureHelpPopover> { + self.popover.as_mut() + } + + pub fn backspace_pressed(&self) -> bool { + self.backspace_pressed + } + + pub fn set_backspace_pressed(&mut self, backspace_pressed: bool) { + self.backspace_pressed = backspace_pressed; + } + + pub fn set_popover(&mut self, popover: SignatureHelpPopover) { + self.popover = Some(popover); + self.hidden_by = None; + } + + pub fn hide(&mut self, hidden_by: SignatureHelpHiddenBy) { + if self.hidden_by.is_none() { + self.popover = None; + self.hidden_by = Some(hidden_by); + } + } + + pub fn hidden_by_selection(&self) -> bool { + self.hidden_by == Some(SignatureHelpHiddenBy::Selection) + } + + pub fn is_shown(&self) -> bool { + self.popover.is_some() + } +} + +#[cfg(test)] +impl SignatureHelpState { + pub fn task(&self) -> Option<&Task<()>> { + self.task.as_ref() + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SignatureHelpPopover { + pub label: SharedString, + pub style: TextStyle, + pub highlights: Vec<(Range, HighlightStyle)>, +} + +impl SignatureHelpPopover { + pub fn render(&mut self, max_size: Size, cx: &mut Context) -> AnyElement { + div() + .id("signature_help_popover") + .elevation_2(cx) + .overflow_y_scroll() + .max_w(max_size.width) + .max_h(max_size.height) + .on_mouse_move(|_, _, cx| cx.stop_propagation()) + .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) + .child( + div().px_4().pb_1().child( + StyledText::new(self.label.clone()) + .with_highlights(&self.style, self.highlights.iter().cloned()), + ), + ) + .into_any_element() + } +} diff --git a/crates/editor/src/signature_help/popover.rs b/crates/editor/src/signature_help/popover.rs deleted file mode 100644 index dbf0a4a2fe..0000000000 --- a/crates/editor/src/signature_help/popover.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{Editor, EditorStyle}; -use gpui::{ - div, AnyElement, Context, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, - Size, StatefulInteractiveElement, Styled, WeakEntity, -}; -use language::ParsedMarkdown; -use ui::StyledExt; -use workspace::Workspace; - -#[derive(Clone, Debug)] -pub struct SignatureHelpPopover { - pub parsed_content: ParsedMarkdown, -} - -impl PartialEq for SignatureHelpPopover { - fn eq(&self, other: &Self) -> bool { - let str_equality = self.parsed_content.text.as_str() == other.parsed_content.text.as_str(); - let highlight_equality = self.parsed_content.highlights == other.parsed_content.highlights; - str_equality && highlight_equality - } -} - -impl SignatureHelpPopover { - pub fn render( - &mut self, - style: &EditorStyle, - max_size: Size, - workspace: Option>, - cx: &mut Context, - ) -> AnyElement { - div() - .id("signature_help_popover") - .elevation_2(cx) - .overflow_y_scroll() - .max_w(max_size.width) - .max_h(max_size.height) - .on_mouse_move(|_, _, cx| cx.stop_propagation()) - .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) - .child(div().p_2().child(crate::render_parsed_markdown( - "signature_help_popover_content", - &self.parsed_content, - style, - workspace, - cx, - ))) - .into_any_element() - } -} diff --git a/crates/editor/src/signature_help/state.rs b/crates/editor/src/signature_help/state.rs deleted file mode 100644 index 8bb61ebd71..0000000000 --- a/crates/editor/src/signature_help/state.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::signature_help::popover::SignatureHelpPopover; -use crate::signature_help::SignatureHelpHiddenBy; -use gpui::Task; - -#[derive(Default, Debug)] -pub struct SignatureHelpState { - task: Option>, - popover: Option, - hidden_by: Option, - backspace_pressed: bool, -} - -impl SignatureHelpState { - pub fn set_task(&mut self, task: Task<()>) { - self.task = Some(task); - self.hidden_by = None; - } - - pub fn kill_task(&mut self) { - self.task = None; - } - - pub fn popover(&self) -> Option<&SignatureHelpPopover> { - self.popover.as_ref() - } - - pub fn popover_mut(&mut self) -> Option<&mut SignatureHelpPopover> { - self.popover.as_mut() - } - - pub fn backspace_pressed(&self) -> bool { - self.backspace_pressed - } - - pub fn set_backspace_pressed(&mut self, backspace_pressed: bool) { - self.backspace_pressed = backspace_pressed; - } - - pub fn set_popover(&mut self, popover: SignatureHelpPopover) { - self.popover = Some(popover); - self.hidden_by = None; - } - - pub fn hide(&mut self, hidden_by: SignatureHelpHiddenBy) { - if self.hidden_by.is_none() { - self.popover = None; - self.hidden_by = Some(hidden_by); - } - } - - pub fn hidden_by_selection(&self) -> bool { - self.hidden_by == Some(SignatureHelpHiddenBy::Selection) - } - - pub fn is_shown(&self) -> bool { - self.popover.is_some() - } -} - -#[cfg(test)] -impl SignatureHelpState { - pub fn task(&self) -> Option<&Task<()>> { - self.task.as_ref() - } -} diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 6f3207611d..1bc4063aee 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -16,7 +16,7 @@ use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged}; use git::{DiscardTrackedChanges, StageAll, TrashUntrackedFiles, UnstageAll}; use gpui::*; use itertools::Itertools; -use language::{markdown, Buffer, File, ParsedMarkdown}; +use language::{Buffer, File}; use menu::{Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; use multi_buffer::ExcerptInfo; use panel::{panel_editor_container, panel_editor_style, panel_filled_button, PanelHeader}; @@ -2380,31 +2380,14 @@ impl GitPanelMessageTooltip { window: &mut Window, cx: &mut App, ) -> Entity { - let workspace = git_panel.read(cx).workspace.clone(); cx.new(|cx| { cx.spawn_in(window, |this, mut cx| async move { - let language_registry = workspace.update(&mut cx, |workspace, _cx| { - workspace.app_state().languages.clone() - })?; - let details = git_panel .update(&mut cx, |git_panel, cx| { git_panel.load_commit_details(&sha, cx) })? .await?; - let mut parsed_message = ParsedMarkdown::default(); - markdown::parse_markdown_block( - &details.message, - Some(&language_registry), - None, - &mut parsed_message.text, - &mut parsed_message.highlights, - &mut parsed_message.region_ranges, - &mut parsed_message.regions, - ) - .await; - let commit_details = editor::commit_tooltip::CommitDetails { sha: details.sha.clone(), committer_name: details.committer_name.clone(), @@ -2412,19 +2395,13 @@ impl GitPanelMessageTooltip { commit_time: OffsetDateTime::from_unix_timestamp(details.commit_timestamp)?, message: Some(editor::commit_tooltip::ParsedCommitMessage { message: details.message.clone(), - parsed_message, ..Default::default() }), }; this.update_in(&mut cx, |this: &mut GitPanelMessageTooltip, window, cx| { - this.commit_tooltip = Some(cx.new(move |cx| { - CommitTooltip::new( - commit_details, - panel_editor_style(true, window, cx), - Some(workspace), - ) - })); + this.commit_tooltip = + Some(cx.new(move |cx| CommitTooltip::new(commit_details, window, cx))); cx.notify(); }) }) diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index c38eea518f..9e5fd55c96 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -42,7 +42,6 @@ log.workspace = true lsp.workspace = true parking_lot.workspace = true postage.workspace = true -pulldown-cmark.workspace = true rand = { workspace = true, optional = true } regex.workspace = true rpc.workspace = true diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index fa6909f2e9..c23eeae533 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,7 +1,6 @@ pub use crate::{ diagnostic_set::DiagnosticSet, highlight_map::{HighlightId, HighlightMap}, - markdown::ParsedMarkdown, proto, Grammar, Language, LanguageRegistry, }; use crate::{ diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 06459df30b..b6c5bf6225 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -19,7 +19,6 @@ mod toolchain; #[cfg(test)] pub mod buffer_tests; -pub mod markdown; pub use crate::language_settings::EditPredictionsMode; use crate::language_settings::SoftWrap; diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs deleted file mode 100644 index 9f823e53e5..0000000000 --- a/crates/language/src/markdown.rs +++ /dev/null @@ -1,389 +0,0 @@ -//! Provides Markdown-related constructs. - -use std::sync::Arc; -use std::{ops::Range, path::PathBuf}; - -use crate::{HighlightId, Language, LanguageRegistry}; -use gpui::{px, FontStyle, FontWeight, HighlightStyle, StrikethroughStyle, UnderlineStyle}; -use pulldown_cmark::{CodeBlockKind, Event, Parser, Tag, TagEnd}; - -/// Parsed Markdown content. -#[derive(Debug, Clone, Default)] -pub struct ParsedMarkdown { - /// The Markdown text. - pub text: String, - /// The list of highlights contained in the Markdown document. - pub highlights: Vec<(Range, MarkdownHighlight)>, - /// The regions of the various ranges in the Markdown document. - pub region_ranges: Vec>, - /// The regions of the Markdown document. - pub regions: Vec, -} - -/// A run of highlighted Markdown text. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum MarkdownHighlight { - /// A styled Markdown highlight. - Style(MarkdownHighlightStyle), - /// A highlighted code block. - Code(HighlightId), -} - -impl MarkdownHighlight { - /// Converts this [`MarkdownHighlight`] to a [`HighlightStyle`]. - pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option { - match self { - MarkdownHighlight::Style(style) => { - let mut highlight = HighlightStyle::default(); - - if style.italic { - highlight.font_style = Some(FontStyle::Italic); - } - - if style.underline { - highlight.underline = Some(UnderlineStyle { - thickness: px(1.), - ..Default::default() - }); - } - - if style.strikethrough { - highlight.strikethrough = Some(StrikethroughStyle { - thickness: px(1.), - ..Default::default() - }); - } - - if style.weight != FontWeight::default() { - highlight.font_weight = Some(style.weight); - } - - Some(highlight) - } - - MarkdownHighlight::Code(id) => id.style(theme), - } - } -} - -/// The style for a Markdown highlight. -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct MarkdownHighlightStyle { - /// Whether the text should be italicized. - pub italic: bool, - /// Whether the text should be underlined. - pub underline: bool, - /// Whether the text should be struck through. - pub strikethrough: bool, - /// The weight of the text. - pub weight: FontWeight, -} - -/// A parsed region in a Markdown document. -#[derive(Debug, Clone)] -pub struct ParsedRegion { - /// Whether the region is a code block. - pub code: bool, - /// The link contained in this region, if it has one. - pub link: Option, -} - -/// A Markdown link. -#[derive(Debug, Clone)] -pub enum Link { - /// A link to a webpage. - Web { - /// The URL of the webpage. - url: String, - }, - /// A link to a path on the filesystem. - Path { - /// The path to the item. - path: PathBuf, - }, -} - -impl Link { - fn identify(text: String) -> Option { - if text.starts_with("http") { - return Some(Link::Web { url: text }); - } - - let path = PathBuf::from(text); - if path.is_absolute() { - return Some(Link::Path { path }); - } - - None - } -} - -/// Parses a string of Markdown. -pub async fn parse_markdown( - markdown: &str, - language_registry: Option<&Arc>, - language: Option>, -) -> ParsedMarkdown { - let mut text = String::new(); - let mut highlights = Vec::new(); - let mut region_ranges = Vec::new(); - let mut regions = Vec::new(); - - parse_markdown_block( - markdown, - language_registry, - language, - &mut text, - &mut highlights, - &mut region_ranges, - &mut regions, - ) - .await; - - ParsedMarkdown { - text, - highlights, - region_ranges, - regions, - } -} - -/// Parses a Markdown block. -pub async fn parse_markdown_block( - markdown: &str, - language_registry: Option<&Arc>, - language: Option>, - text: &mut String, - highlights: &mut Vec<(Range, MarkdownHighlight)>, - region_ranges: &mut Vec>, - regions: &mut Vec, -) { - let mut bold_depth = 0; - let mut italic_depth = 0; - let mut strikethrough_depth = 0; - let mut link_url = None; - let mut current_language = None; - let mut list_stack = Vec::new(); - - let mut options = pulldown_cmark::Options::all(); - options.remove(pulldown_cmark::Options::ENABLE_DEFINITION_LIST); - options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS); - - for event in Parser::new_ext(markdown, options) { - let prev_len = text.len(); - match event { - Event::Text(t) => { - if let Some(language) = ¤t_language { - highlight_code(text, highlights, t.as_ref(), language); - } else { - text.push_str(t.as_ref()); - - let mut style = MarkdownHighlightStyle::default(); - - if bold_depth > 0 { - style.weight = FontWeight::BOLD; - } - - if italic_depth > 0 { - style.italic = true; - } - - if strikethrough_depth > 0 { - style.strikethrough = true; - } - - if let Some(link) = link_url.clone().and_then(Link::identify) { - region_ranges.push(prev_len..text.len()); - regions.push(ParsedRegion { - code: false, - link: Some(link), - }); - style.underline = true; - } - - if style != MarkdownHighlightStyle::default() { - let mut new_highlight = true; - if let Some((last_range, MarkdownHighlight::Style(last_style))) = - highlights.last_mut() - { - if last_range.end == prev_len && last_style == &style { - last_range.end = text.len(); - new_highlight = false; - } - } - if new_highlight { - let range = prev_len..text.len(); - highlights.push((range, MarkdownHighlight::Style(style))); - } - } - } - } - - Event::Code(t) => { - text.push_str(t.as_ref()); - region_ranges.push(prev_len..text.len()); - - let link = link_url.clone().and_then(Link::identify); - if link.is_some() { - highlights.push(( - prev_len..text.len(), - MarkdownHighlight::Style(MarkdownHighlightStyle { - underline: true, - ..Default::default() - }), - )); - } - regions.push(ParsedRegion { code: true, link }); - } - - Event::Start(tag) => match tag { - Tag::Paragraph => new_paragraph(text, &mut list_stack), - - Tag::Heading { .. } => { - new_paragraph(text, &mut list_stack); - bold_depth += 1; - } - - Tag::CodeBlock(kind) => { - new_paragraph(text, &mut list_stack); - current_language = if let CodeBlockKind::Fenced(language) = kind { - match language_registry { - None => None, - Some(language_registry) => language_registry - .language_for_name_or_extension(language.as_ref()) - .await - .ok(), - } - } else { - language.clone() - } - } - - Tag::Emphasis => italic_depth += 1, - - Tag::Strong => bold_depth += 1, - - Tag::Strikethrough => strikethrough_depth += 1, - - Tag::Link { dest_url, .. } => link_url = Some(dest_url.to_string()), - - Tag::List(number) => { - list_stack.push((number, false)); - } - - Tag::Item => { - let len = list_stack.len(); - if let Some((list_number, has_content)) = list_stack.last_mut() { - *has_content = false; - if !text.is_empty() && !text.ends_with('\n') { - text.push('\n'); - } - for _ in 0..len - 1 { - text.push_str(" "); - } - if let Some(number) = list_number { - text.push_str(&format!("{}. ", number)); - *number += 1; - *has_content = false; - } else { - text.push_str("- "); - } - } - } - - _ => {} - }, - - Event::End(tag) => match tag { - TagEnd::Heading(_) => bold_depth -= 1, - TagEnd::CodeBlock => current_language = None, - TagEnd::Emphasis => italic_depth -= 1, - TagEnd::Strong => bold_depth -= 1, - TagEnd::Strikethrough => strikethrough_depth -= 1, - TagEnd::Link => link_url = None, - TagEnd::List(_) => drop(list_stack.pop()), - _ => {} - }, - - Event::HardBreak => text.push('\n'), - - Event::SoftBreak => text.push(' '), - - _ => {} - } - } -} - -/// Appends a highlighted run of text to the provided `text` buffer. -pub fn highlight_code( - text: &mut String, - highlights: &mut Vec<(Range, MarkdownHighlight)>, - content: &str, - language: &Arc, -) { - let prev_len = text.len(); - text.push_str(content); - for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { - let highlight = MarkdownHighlight::Code(highlight_id); - highlights.push((prev_len + range.start..prev_len + range.end, highlight)); - } -} - -/// Appends a new paragraph to the provided `text` buffer. -pub fn new_paragraph(text: &mut String, list_stack: &mut [(Option, bool)]) { - let mut is_subsequent_paragraph_of_list = false; - if let Some((_, has_content)) = list_stack.last_mut() { - if *has_content { - is_subsequent_paragraph_of_list = true; - } else { - *has_content = true; - return; - } - } - - if !text.is_empty() { - if !text.ends_with('\n') { - text.push('\n'); - } - text.push('\n'); - } - for _ in 0..list_stack.len().saturating_sub(1) { - text.push_str(" "); - } - if is_subsequent_paragraph_of_list { - text.push_str(" "); - } -} - -#[cfg(test)] -mod tests { - - #[test] - fn test_dividers() { - let input = r#" -### instance-method `format` - ---- -→ `void` -Parameters: -- `const int &` -- `const std::tm &` -- `int & dest` - ---- -```cpp -// In my_formatter_flag -public: void format(const int &, const std::tm &, int &dest) -``` -"#; - - let mut options = pulldown_cmark::Options::all(); - options.remove(pulldown_cmark::Options::ENABLE_DEFINITION_LIST); - options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS); - - let parser = pulldown_cmark::Parser::new_ext(input, options); - for event in parser.into_iter() { - println!("{:?}", event); - } - } -} diff --git a/crates/markdown/examples/markdown.rs b/crates/markdown/examples/markdown.rs index f4eafc5e1f..bcd210a0db 100644 --- a/crates/markdown/examples/markdown.rs +++ b/crates/markdown/examples/markdown.rs @@ -46,7 +46,7 @@ pub fn main() { Assets.load_fonts(cx).unwrap(); cx.activate(true); - cx.open_window(WindowOptions::default(), |window, cx| { + cx.open_window(WindowOptions::default(), |_, cx| { cx.new(|cx| { let markdown_style = MarkdownStyle { base_text_style: gpui::TextStyle { @@ -92,7 +92,6 @@ pub fn main() { MARKDOWN_EXAMPLE.into(), markdown_style, language_registry, - window, cx, ) }) @@ -110,7 +109,6 @@ impl MarkdownExample { text: SharedString, style: MarkdownStyle, language_registry: Arc, - window: &mut Window, cx: &mut App, ) -> Self { let markdown = cx.new(|cx| { @@ -119,7 +117,6 @@ impl MarkdownExample { style, Some(language_registry), Some("TypeScript".to_string()), - window, cx, ) }); diff --git a/crates/markdown/examples/markdown_as_child.rs b/crates/markdown/examples/markdown_as_child.rs index 5aa543a4fc..aa5a59f794 100644 --- a/crates/markdown/examples/markdown_as_child.rs +++ b/crates/markdown/examples/markdown_as_child.rs @@ -35,7 +35,7 @@ pub fn main() { Assets.load_fonts(cx).unwrap(); cx.activate(true); - let _ = cx.open_window(WindowOptions::default(), |window, cx| { + let _ = cx.open_window(WindowOptions::default(), |_, cx| { cx.new(|cx| { let markdown_style = MarkdownStyle { base_text_style: gpui::TextStyle { @@ -86,14 +86,7 @@ pub fn main() { heading: Default::default(), }; let markdown = cx.new(|cx| { - Markdown::new( - MARKDOWN_EXAMPLE.into(), - markdown_style, - None, - None, - window, - cx, - ) + Markdown::new(MARKDOWN_EXAMPLE.into(), markdown_style, None, None, cx) }); HelloWorld { markdown } diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index 11cbda57ee..d31d768c30 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -77,7 +77,6 @@ impl Markdown { style: MarkdownStyle, language_registry: Option>, fallback_code_block_language: Option, - window: &mut Window, cx: &mut Context, ) -> Self { let focus_handle = cx.focus_handle(); @@ -99,7 +98,7 @@ impl Markdown { }, open_url: None, }; - this.parse(window, cx); + this.parse(cx); this } @@ -113,14 +112,7 @@ impl Markdown { } } - pub fn new_text( - source: SharedString, - style: MarkdownStyle, - language_registry: Option>, - fallback_code_block_language: Option, - window: &mut Window, - cx: &mut Context, - ) -> Self { + pub fn new_text(source: SharedString, style: MarkdownStyle, cx: &mut Context) -> Self { let focus_handle = cx.focus_handle(); let mut this = Self { source, @@ -132,15 +124,15 @@ impl Markdown { parsed_markdown: ParsedMarkdown::default(), pending_parse: None, focus_handle, - language_registry, - fallback_code_block_language, + language_registry: None, + fallback_code_block_language: None, options: Options { parse_links_only: true, copy_code_block_buttons: true, }, open_url: None, }; - this.parse(window, cx); + this.parse(cx); this } @@ -148,12 +140,12 @@ impl Markdown { &self.source } - pub fn append(&mut self, text: &str, window: &mut Window, cx: &mut Context) { + pub fn append(&mut self, text: &str, cx: &mut Context) { self.source = SharedString::new(self.source.to_string() + text); - self.parse(window, cx); + self.parse(cx); } - pub fn reset(&mut self, source: SharedString, window: &mut Window, cx: &mut Context) { + pub fn reset(&mut self, source: SharedString, cx: &mut Context) { if source == self.source() { return; } @@ -163,7 +155,7 @@ impl Markdown { self.pending_parse = None; self.should_reparse = false; self.parsed_markdown = ParsedMarkdown::default(); - self.parse(window, cx); + self.parse(cx); } pub fn parsed_markdown(&self) -> &ParsedMarkdown { @@ -178,7 +170,7 @@ impl Markdown { cx.write_to_clipboard(ClipboardItem::new_string(text)); } - fn parse(&mut self, window: &mut Window, cx: &mut Context) { + fn parse(&mut self, cx: &mut Context) { if self.source.is_empty() { return; } @@ -224,14 +216,14 @@ impl Markdown { }); self.should_reparse = false; - self.pending_parse = Some(cx.spawn_in(window, |this, mut cx| { + self.pending_parse = Some(cx.spawn(|this, mut cx| { async move { let parsed = parsed.await?; - this.update_in(&mut cx, |this, window, cx| { + this.update(&mut cx, |this, cx| { this.parsed_markdown = parsed; this.pending_parse.take(); if this.should_reparse { - this.parse(window, cx); + this.parse(cx); } cx.notify(); }) @@ -294,7 +286,7 @@ impl Selection { } } -#[derive(Clone, Default)] +#[derive(Default)] pub struct ParsedMarkdown { source: SharedString, events: Arc<[(Range, MarkdownEvent)]>, @@ -554,7 +546,7 @@ impl Element for MarkdownElement { self.style.base_text_style.clone(), self.style.syntax.clone(), ); - let parsed_markdown = self.markdown.read(cx).parsed_markdown.clone(); + let parsed_markdown = &self.markdown.read(cx).parsed_markdown; let markdown_end = if let Some(last) = parsed_markdown.events.last() { last.0.end } else { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 6cd22fc852..947af390fc 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -30,9 +30,7 @@ use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature}; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; use text::{BufferId, LineEnding}; -pub use signature_help::{ - SignatureHelp, SIGNATURE_HELP_HIGHLIGHT_CURRENT, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD, -}; +pub use signature_help::SignatureHelp; pub fn lsp_formatting_options(settings: &LanguageSettings) -> lsp::FormattingOptions { lsp::FormattingOptions { @@ -1511,12 +1509,11 @@ impl LspCommand for GetSignatureHelp { self, message: Option, _: Entity, - buffer: Entity, + _: Entity, _: LanguageServerId, - mut cx: AsyncApp, + _: AsyncApp, ) -> Result { - let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?; - Ok(message.and_then(|message| SignatureHelp::new(message, language))) + Ok(message.and_then(SignatureHelp::new)) } fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest { @@ -1568,14 +1565,13 @@ impl LspCommand for GetSignatureHelp { self, response: proto::GetSignatureHelpResponse, _: Entity, - buffer: Entity, - mut cx: AsyncApp, + _: Entity, + _: AsyncApp, ) -> Result { - let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?; Ok(response .signature_help .map(proto_to_lsp_signature) - .and_then(|lsp_help| SignatureHelp::new(lsp_help, language))) + .and_then(SignatureHelp::new)) } fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result { diff --git a/crates/project/src/lsp_command/signature_help.rs b/crates/project/src/lsp_command/signature_help.rs index c641a931b4..37bd43fcce 100644 --- a/crates/project/src/lsp_command/signature_help.rs +++ b/crates/project/src/lsp_command/signature_help.rs @@ -1,37 +1,17 @@ -use std::{ops::Range, sync::Arc}; +use std::ops::Range; -use gpui::FontWeight; -use language::{ - markdown::{MarkdownHighlight, MarkdownHighlightStyle}, - Language, -}; +use gpui::{FontStyle, FontWeight, HighlightStyle}; use rpc::proto::{self, documentation}; -pub const SIGNATURE_HELP_HIGHLIGHT_CURRENT: MarkdownHighlight = - MarkdownHighlight::Style(MarkdownHighlightStyle { - italic: false, - underline: false, - strikethrough: false, - weight: FontWeight::EXTRA_BOLD, - }); - -pub const SIGNATURE_HELP_HIGHLIGHT_OVERLOAD: MarkdownHighlight = - MarkdownHighlight::Style(MarkdownHighlightStyle { - italic: true, - underline: false, - strikethrough: false, - weight: FontWeight::NORMAL, - }); - #[derive(Debug)] pub struct SignatureHelp { - pub markdown: String, - pub highlights: Vec<(Range, MarkdownHighlight)>, + pub label: String, + pub highlights: Vec<(Range, HighlightStyle)>, pub(super) original_data: lsp::SignatureHelp, } impl SignatureHelp { - pub fn new(help: lsp::SignatureHelp, language: Option>) -> Option { + pub fn new(help: lsp::SignatureHelp) -> Option { let function_options_count = help.signatures.len(); let signature_information = help @@ -45,7 +25,7 @@ impl SignatureHelp { .as_ref() .map_or(0, |parameters| parameters.len()); let mut highlight_start = 0; - let (markdown, mut highlights): (Vec<_>, Vec<_>) = signature_information + let (strings, mut highlights): (Vec<_>, Vec<_>) = signature_information .parameters .as_ref()? .iter() @@ -66,7 +46,10 @@ impl SignatureHelp { if i == active_parameter as usize { Some(( highlight_start..(highlight_start + label_length), - SIGNATURE_HELP_HIGHLIGHT_CURRENT, + HighlightStyle { + font_weight: Some(FontWeight::EXTRA_BOLD), + ..Default::default() + }, )) } else { None @@ -81,28 +64,27 @@ impl SignatureHelp { }) .unzip(); - if markdown.is_empty() { + if strings.is_empty() { None } else { - let markdown = markdown.join(str_for_join); - let language_name = language - .map(|n| n.name().as_ref().to_lowercase()) - .unwrap_or_default(); + let mut label = strings.join(str_for_join); - let markdown = if function_options_count >= 2 { + if function_options_count >= 2 { let suffix = format!("(+{} overload)", function_options_count - 1); - let highlight_start = markdown.len() + 1; + let highlight_start = label.len() + 1; highlights.push(Some(( highlight_start..(highlight_start + suffix.len()), - SIGNATURE_HELP_HIGHLIGHT_OVERLOAD, + HighlightStyle { + font_style: Some(FontStyle::Italic), + ..Default::default() + }, ))); - format!("```{language_name}\n{markdown} {suffix}") - } else { - format!("```{language_name}\n{markdown}") + label.push(' '); + label.push_str(&suffix); }; Some(Self { - markdown, + label, highlights: highlights.into_iter().flatten().collect(), original_data: help, }) @@ -224,9 +206,23 @@ fn proto_to_lsp_documentation(documentation: proto::Documentation) -> Option HighlightStyle { + HighlightStyle { + font_weight: Some(FontWeight::EXTRA_BOLD), + ..Default::default() + } + } + + fn overload() -> HighlightStyle { + HighlightStyle { + font_style: Some(FontStyle::Italic), + ..Default::default() + } + } #[test] fn test_create_signature_help_markdown_string_1() { @@ -249,16 +245,16 @@ mod tests { active_signature: Some(0), active_parameter: Some(0), }; - let maybe_markdown = SignatureHelp::new(signature_help, None); + let maybe_markdown = SignatureHelp::new(signature_help); assert!(maybe_markdown.is_some()); let markdown = maybe_markdown.unwrap(); - let markdown = (markdown.markdown, markdown.highlights); + let markdown = (markdown.label, markdown.highlights); assert_eq!( markdown, ( - "```\nfoo: u8, bar: &str".to_string(), - vec![(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT)] + "foo: u8, bar: &str".to_string(), + vec![(0..7, current_parameter())] ) ); } @@ -284,16 +280,16 @@ mod tests { active_signature: Some(0), active_parameter: Some(1), }; - let maybe_markdown = SignatureHelp::new(signature_help, None); + let maybe_markdown = SignatureHelp::new(signature_help); assert!(maybe_markdown.is_some()); let markdown = maybe_markdown.unwrap(); - let markdown = (markdown.markdown, markdown.highlights); + let markdown = (markdown.label, markdown.highlights); assert_eq!( markdown, ( - "```\nfoo: u8, bar: &str".to_string(), - vec![(9..18, SIGNATURE_HELP_HIGHLIGHT_CURRENT)] + "foo: u8, bar: &str".to_string(), + vec![(9..18, current_parameter())] ) ); } @@ -336,19 +332,16 @@ mod tests { active_signature: Some(0), active_parameter: Some(0), }; - let maybe_markdown = SignatureHelp::new(signature_help, None); + let maybe_markdown = SignatureHelp::new(signature_help); assert!(maybe_markdown.is_some()); let markdown = maybe_markdown.unwrap(); - let markdown = (markdown.markdown, markdown.highlights); + let markdown = (markdown.label, markdown.highlights); assert_eq!( markdown, ( - "```\nfoo: u8, bar: &str (+1 overload)".to_string(), - vec![ - (0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT), - (19..32, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD) - ] + "foo: u8, bar: &str (+1 overload)".to_string(), + vec![(0..7, current_parameter()), (19..32, overload())] ) ); } @@ -391,19 +384,16 @@ mod tests { active_signature: Some(1), active_parameter: Some(0), }; - let maybe_markdown = SignatureHelp::new(signature_help, None); + let maybe_markdown = SignatureHelp::new(signature_help); assert!(maybe_markdown.is_some()); let markdown = maybe_markdown.unwrap(); - let markdown = (markdown.markdown, markdown.highlights); + let markdown = (markdown.label, markdown.highlights); assert_eq!( markdown, ( - "```\nhoge: String, fuga: bool (+1 overload)".to_string(), - vec![ - (0..12, SIGNATURE_HELP_HIGHLIGHT_CURRENT), - (25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD) - ] + "hoge: String, fuga: bool (+1 overload)".to_string(), + vec![(0..12, current_parameter()), (25..38, overload())] ) ); } @@ -446,19 +436,16 @@ mod tests { active_signature: Some(1), active_parameter: Some(1), }; - let maybe_markdown = SignatureHelp::new(signature_help, None); + let maybe_markdown = SignatureHelp::new(signature_help); assert!(maybe_markdown.is_some()); let markdown = maybe_markdown.unwrap(); - let markdown = (markdown.markdown, markdown.highlights); + let markdown = (markdown.label, markdown.highlights); assert_eq!( markdown, ( - "```\nhoge: String, fuga: bool (+1 overload)".to_string(), - vec![ - (14..24, SIGNATURE_HELP_HIGHLIGHT_CURRENT), - (25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD) - ] + "hoge: String, fuga: bool (+1 overload)".to_string(), + vec![(14..24, current_parameter()), (25..38, overload())] ) ); } @@ -501,16 +488,16 @@ mod tests { active_signature: Some(1), active_parameter: None, }; - let maybe_markdown = SignatureHelp::new(signature_help, None); + let maybe_markdown = SignatureHelp::new(signature_help); assert!(maybe_markdown.is_some()); let markdown = maybe_markdown.unwrap(); - let markdown = (markdown.markdown, markdown.highlights); + let markdown = (markdown.label, markdown.highlights); assert_eq!( markdown, ( - "```\nhoge: String, fuga: bool (+1 overload)".to_string(), - vec![(25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)] + "hoge: String, fuga: bool (+1 overload)".to_string(), + vec![(25..38, overload())] ) ); } @@ -568,19 +555,16 @@ mod tests { active_signature: Some(2), active_parameter: Some(1), }; - let maybe_markdown = SignatureHelp::new(signature_help, None); + let maybe_markdown = SignatureHelp::new(signature_help); assert!(maybe_markdown.is_some()); let markdown = maybe_markdown.unwrap(); - let markdown = (markdown.markdown, markdown.highlights); + let markdown = (markdown.label, markdown.highlights); assert_eq!( markdown, ( - "```\none: usize, two: u32 (+2 overload)".to_string(), - vec![ - (12..20, SIGNATURE_HELP_HIGHLIGHT_CURRENT), - (21..34, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD) - ] + "one: usize, two: u32 (+2 overload)".to_string(), + vec![(12..20, current_parameter()), (21..34, overload())] ) ); } @@ -592,7 +576,7 @@ mod tests { active_signature: None, active_parameter: None, }; - let maybe_markdown = SignatureHelp::new(signature_help, None); + let maybe_markdown = SignatureHelp::new(signature_help); assert!(maybe_markdown.is_none()); } @@ -617,16 +601,16 @@ mod tests { active_signature: Some(0), active_parameter: Some(0), }; - let maybe_markdown = SignatureHelp::new(signature_help, None); + let maybe_markdown = SignatureHelp::new(signature_help); assert!(maybe_markdown.is_some()); let markdown = maybe_markdown.unwrap(); - let markdown = (markdown.markdown, markdown.highlights); + let markdown = (markdown.label, markdown.highlights); assert_eq!( markdown, ( - "```\nfoo: u8, bar: &str".to_string(), - vec![(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT)] + "foo: u8, bar: &str".to_string(), + vec![(0..7, current_parameter())] ) ); } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 38bb0400dc..427ebd1b59 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -4769,7 +4769,7 @@ impl LspStore { .await .into_iter() .flatten() - .filter(|help| !help.markdown.is_empty()) + .filter(|help| !help.label.is_empty()) .collect::>() }) } diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 97f7106d1b..ea733c2137 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -207,8 +207,7 @@ impl SshPrompt { selection_background_color: cx.theme().players().local().selection, ..Default::default() }; - let markdown = - cx.new(|cx| Markdown::new_text(prompt.into(), markdown_style, None, None, window, cx)); + let markdown = cx.new(|cx| Markdown::new_text(prompt.into(), markdown_style, cx)); self.prompt = Some((markdown, tx)); self.status_message.take(); window.focus(&self.editor.focus_handle(cx)); diff --git a/crates/zed/src/zed/linux_prompts.rs b/crates/zed/src/zed/linux_prompts.rs index e838c8b029..09d1eabf84 100644 --- a/crates/zed/src/zed/linux_prompts.rs +++ b/crates/zed/src/zed/linux_prompts.rs @@ -48,14 +48,7 @@ pub fn fallback_prompt_renderer( selection_background_color: { cx.theme().players().local().selection }, ..Default::default() }; - Markdown::new( - SharedString::new(text), - markdown_style, - None, - None, - window, - cx, - ) + Markdown::new(SharedString::new(text), markdown_style, None, None, cx) }) }), }