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
This commit is contained in:
parent
686978d7c5
commit
119bd896b0
25 changed files with 348 additions and 947 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -6875,7 +6875,6 @@ dependencies = [
|
|||
"parking_lot",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
"pulldown-cmark 0.12.2",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rpc",
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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<Url>,
|
||||
pub pull_request: Option<PullRequest>,
|
||||
pub remote: Option<GitRemote>,
|
||||
|
@ -115,48 +113,65 @@ impl Asset for CommitAvatarAsset {
|
|||
|
||||
pub struct CommitTooltip {
|
||||
commit: CommitDetails,
|
||||
editor_style: EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
scroll_handle: ScrollHandle,
|
||||
markdown: Entity<Markdown>,
|
||||
}
|
||||
|
||||
impl CommitTooltip {
|
||||
pub fn blame_entry(
|
||||
blame: BlameEntry,
|
||||
blame: &BlameEntry,
|
||||
details: Option<ParsedCommitMessage>,
|
||||
style: EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> 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("<no name>".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<WeakEntity<Workspace>>,
|
||||
) -> Self {
|
||||
pub fn new(commit: CommitDetails, window: &mut Window, cx: &mut Context<Self>) -> 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("<no commit message>".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()
|
||||
|
|
|
@ -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<ElementId>,
|
||||
parsed: &language::ParsedMarkdown,
|
||||
editor_style: &EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
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<Task<()>>,
|
||||
git_blame_inline_tooltip: Option<WeakEntity<crate::commit_tooltip::CommitTooltip>>,
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<Editor>,
|
||||
blame: &gpui::Entity<GitBlame>,
|
||||
blame_entry: BlameEntry,
|
||||
style: &EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
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("<no name>");
|
||||
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;
|
||||
|
|
|
@ -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<Self>) {
|
||||
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<String>,
|
||||
deprecated_permalinks: &HashMap<Oid, Url>,
|
||||
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
languages: &Arc<LanguageRegistry>,
|
||||
) -> HashMap<Oid, ParsedCommitMessage> {
|
||||
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<LanguageRegistry>) -> 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::*;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<Task<()>>,
|
||||
popover: Option<SignatureHelpPopover>,
|
||||
hidden_by: Option<SignatureHelpHiddenBy>,
|
||||
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<usize>, HighlightStyle)>,
|
||||
}
|
||||
|
||||
impl SignatureHelpPopover {
|
||||
pub fn render(&mut self, max_size: Size<Pixels>, cx: &mut Context<Editor>) -> 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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Pixels>,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> 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()
|
||||
}
|
||||
}
|
|
@ -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<Task<()>>,
|
||||
popover: Option<SignatureHelpPopover>,
|
||||
hidden_by: Option<SignatureHelpHiddenBy>,
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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<Self> {
|
||||
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();
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
pub use crate::{
|
||||
diagnostic_set::DiagnosticSet,
|
||||
highlight_map::{HighlightId, HighlightMap},
|
||||
markdown::ParsedMarkdown,
|
||||
proto, Grammar, Language, LanguageRegistry,
|
||||
};
|
||||
use crate::{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<usize>, MarkdownHighlight)>,
|
||||
/// The regions of the various ranges in the Markdown document.
|
||||
pub region_ranges: Vec<Range<usize>>,
|
||||
/// The regions of the Markdown document.
|
||||
pub regions: Vec<ParsedRegion>,
|
||||
}
|
||||
|
||||
/// 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<HighlightStyle> {
|
||||
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<Link>,
|
||||
}
|
||||
|
||||
/// 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<Link> {
|
||||
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<LanguageRegistry>>,
|
||||
language: Option<Arc<Language>>,
|
||||
) -> 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<LanguageRegistry>>,
|
||||
language: Option<Arc<Language>>,
|
||||
text: &mut String,
|
||||
highlights: &mut Vec<(Range<usize>, MarkdownHighlight)>,
|
||||
region_ranges: &mut Vec<Range<usize>>,
|
||||
regions: &mut Vec<ParsedRegion>,
|
||||
) {
|
||||
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<usize>, MarkdownHighlight)>,
|
||||
content: &str,
|
||||
language: &Arc<Language>,
|
||||
) {
|
||||
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<u64>, 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<LanguageRegistry>,
|
||||
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,
|
||||
)
|
||||
});
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -77,7 +77,6 @@ impl Markdown {
|
|||
style: MarkdownStyle,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> 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<Arc<LanguageRegistry>>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
pub fn new_text(source: SharedString, style: MarkdownStyle, cx: &mut Context<Self>) -> 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<Self>) {
|
||||
pub fn append(&mut self, text: &str, cx: &mut Context<Self>) {
|
||||
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<Self>) {
|
||||
pub fn reset(&mut self, source: SharedString, cx: &mut Context<Self>) {
|
||||
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<Self>) {
|
||||
fn parse(&mut self, cx: &mut Context<Self>) {
|
||||
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<usize>, 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 {
|
||||
|
|
|
@ -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<lsp::SignatureHelp>,
|
||||
_: Entity<LspStore>,
|
||||
buffer: Entity<Buffer>,
|
||||
_: Entity<Buffer>,
|
||||
_: LanguageServerId,
|
||||
mut cx: AsyncApp,
|
||||
_: AsyncApp,
|
||||
) -> Result<Self::Response> {
|
||||
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<LspStore>,
|
||||
buffer: Entity<Buffer>,
|
||||
mut cx: AsyncApp,
|
||||
_: Entity<Buffer>,
|
||||
_: AsyncApp,
|
||||
) -> Result<Self::Response> {
|
||||
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<BufferId> {
|
||||
|
|
|
@ -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<usize>, MarkdownHighlight)>,
|
||||
pub label: String,
|
||||
pub highlights: Vec<(Range<usize>, HighlightStyle)>,
|
||||
pub(super) original_data: lsp::SignatureHelp,
|
||||
}
|
||||
|
||||
impl SignatureHelp {
|
||||
pub fn new(help: lsp::SignatureHelp, language: Option<Arc<Language>>) -> Option<Self> {
|
||||
pub fn new(help: lsp::SignatureHelp) -> Option<Self> {
|
||||
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<lsp
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::lsp_command::signature_help::{
|
||||
SignatureHelp, SIGNATURE_HELP_HIGHLIGHT_CURRENT, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD,
|
||||
};
|
||||
use gpui::{FontStyle, FontWeight, HighlightStyle};
|
||||
|
||||
use crate::lsp_command::signature_help::SignatureHelp;
|
||||
|
||||
fn current_parameter() -> 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())]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4769,7 +4769,7 @@ impl LspStore {
|
|||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|help| !help.markdown.is_empty())
|
||||
.filter(|help| !help.label.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}),
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue