From 119bd896b01f519ad41aabd96f061b6dfe6ce2c1 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 19 Feb 2025 08:55:36 -0700 Subject: [PATCH] Remove language::markdown (#25136) The language::markdown crate had been superceded by markdown::Mardown. After #25117, the only two remaining use-cases were rendering git commit messages (which are arguably not really markdown) and the signature help (which is definitely not markdown). Updated the former to use the new markdown component, and the latter to do syntax highlighting manually. Release Notes: - Allow selecting the commit message in git commits --- Cargo.lock | 1 - crates/assistant2/src/active_thread.rs | 3 +- crates/editor/src/code_context_menus.rs | 3 +- crates/editor/src/commit_tooltip.rs | 70 ++-- crates/editor/src/editor.rs | 85 +--- crates/editor/src/editor_tests.rs | 44 +- crates/editor/src/element.rs | 65 ++- crates/editor/src/git/blame.rs | 28 +- crates/editor/src/hover_popover.rs | 12 +- crates/editor/src/signature_help.rs | 206 +++++++--- crates/editor/src/signature_help/popover.rs | 48 --- crates/editor/src/signature_help/state.rs | 65 --- crates/git_ui/src/git_panel.rs | 29 +- crates/language/Cargo.toml | 1 - crates/language/src/buffer.rs | 1 - crates/language/src/language.rs | 1 - crates/language/src/markdown.rs | 389 ------------------ crates/markdown/examples/markdown.rs | 5 +- crates/markdown/examples/markdown_as_child.rs | 11 +- crates/markdown/src/markdown.rs | 38 +- crates/project/src/lsp_command.rs | 18 +- .../project/src/lsp_command/signature_help.rs | 158 ++++--- crates/project/src/lsp_store.rs | 2 +- crates/recent_projects/src/ssh_connections.rs | 3 +- crates/zed/src/zed/linux_prompts.rs | 9 +- 25 files changed, 348 insertions(+), 947 deletions(-) delete mode 100644 crates/editor/src/signature_help/popover.rs delete mode 100644 crates/editor/src/signature_help/state.rs delete mode 100644 crates/language/src/markdown.rs diff --git a/Cargo.lock b/Cargo.lock index 6f3c14d17ea2d8fd65899b2885fce928ec2ed5d0..e168c790e26f54a89a69decf49125e6bd693db40 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 bf1a31158ccd907aac85b66efde5333e2af74ec3..aad2aad2651e127b0e846719654121a9055cfbc9 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 0176651f2fd0fa1706b0f95a8c2e858fd2fe9cec..bfb59699452d67dce82214fdad4ce950d69f4160 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 f45b8d2b62da7770020b31efe1cf7b7c6678af4b..4bcb73f8ca5232e4d7388ff259faaabef3fed75e 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 9198129e1705fe37df384fff473cd91851c93552..aaa49a1390f33d8ae27a437d06c68abb6078e20b 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 144782b9a479d58a38db5c66c416ebe6a92a6f16..0f85e81a70881f3c8fbe2cba1459abb2c7ff8104 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 2ff272d996ebcb1148d119215881479a0701f95e..ae7923380ad6996855203aa0552d5862c6a8b2b3 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 268738ab25d739120d716af4b72d9674adc69a8d..0ef829786fc42871253eb9a752d95a1d049b1dff 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 bec413329a2b4d3dbfc7620e22e4687a2cf0107f..e4d7676a7fdd1b6c48094532d1e2251ac8372070 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 f2e479f877ef353d8ae87efea46d4a43b8c2ae2a..c75e45c1e4fe72c5b2c5a48f0e9c53d901cb163d 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 dbf0a4a2fe82c167aec94eb612bbd41ea5455899..0000000000000000000000000000000000000000 --- 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 8bb61ebd7167ce82f171565d7196922743fc0fae..0000000000000000000000000000000000000000 --- 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 6f3207611d73cdcdcc90214a5f38f8b70096cf75..1bc4063aee2024bc85b9b784e503faf48066916e 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 c38eea518f0c0ac380a8bb037725eb5efa50c015..9e5fd55c967375702e8fe2ea3a2fe6103a113007 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 fa6909f2e9b9eec35b2eae663057ab4441475365..c23eeae533389c226421ccc23055abc18ee70b35 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 06459df30b65100301abb43feb1fb2524fc779d0..b6c5bf6225616db7d8ff89e31f8e67f9a662b5f3 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 9f823e53e575894149d5916fc9082afcc9968b02..0000000000000000000000000000000000000000 --- 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 f4eafc5e1fd7738a0ef0348f6deb992cc95b74c5..bcd210a0db500fb88f76c7dd3391712f8d1c034b 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 5aa543a4fcd8e220002963922920d9d3fa1b82a7..aa5a59f794fd3d8c862e3a0e20e0337afe018be2 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 11cbda57eebaef5cc6ac4d4f207204c161175eba..d31d768c303412ef5ffd0f88f7576b372524d353 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 6cd22fc852026fa88935f70472f814690808d01f..947af390fcf3c5b5fa9feb8c56a6f574cc508e83 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 c641a931b4898de2102ac2629debddf905cef1be..37bd43fcce793faf1aa4eb4a59f981b88631b538 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 38bb0400dc6f48963bbe4f40e06ceb5f3ca20bbd..427ebd1b5962a1eab60aa10640f80df1f9a3b3b8 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 97f7106d1b5ad3c6af03511d132f00a80f27e343..ea733c213742d7a8d80a23ddd74807bf7e9f5e41 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 e838c8b029df41e57bbe0987db0278902b64bdea..09d1eabf84a8a5c33f68d73540a3dac84eb3b256 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) }) }), }