Fix clicking on file links in editor (#25117)
Closes #18641 Contributes: #13194 Release Notes: - Open LSP documentation file links in Zed not the system opener - Render completion documentation markdown consistently with documentation markdown
This commit is contained in:
parent
ebbc6a9752
commit
1678e3cbf1
16 changed files with 353 additions and 352 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -7652,7 +7652,6 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assets",
|
"assets",
|
||||||
"env_logger 0.11.6",
|
"env_logger 0.11.6",
|
||||||
"futures 0.3.31",
|
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
"languages",
|
"languages",
|
||||||
|
|
|
@ -179,7 +179,7 @@ impl ActiveThread {
|
||||||
|
|
||||||
let markdown = cx.new(|cx| {
|
let markdown = cx.new(|cx| {
|
||||||
Markdown::new(
|
Markdown::new(
|
||||||
text,
|
text.into(),
|
||||||
markdown_style,
|
markdown_style,
|
||||||
Some(self.language_registry.clone()),
|
Some(self.language_registry.clone()),
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -5,9 +5,9 @@ use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWor
|
||||||
use editor::{CompletionProvider, Editor};
|
use editor::{CompletionProvider, Editor};
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity, Window};
|
use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity, Window};
|
||||||
use language::{Anchor, Buffer, CompletionDocumentation, LanguageServerId, ToPoint};
|
use language::{Anchor, Buffer, LanguageServerId, ToPoint};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::CompletionIntent;
|
use project::{lsp_store::CompletionDocumentation, CompletionIntent};
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
@ -121,7 +121,7 @@ impl SlashCommandCompletionProvider {
|
||||||
Some(project::Completion {
|
Some(project::Completion {
|
||||||
old_range: name_range.clone(),
|
old_range: name_range.clone(),
|
||||||
documentation: Some(CompletionDocumentation::SingleLine(
|
documentation: Some(CompletionDocumentation::SingleLine(
|
||||||
command.description(),
|
command.description().into(),
|
||||||
)),
|
)),
|
||||||
new_text,
|
new_text,
|
||||||
label: command.label(cx),
|
label: command.label(cx),
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, uniform_list, AnyElement, BackgroundExecutor, Div, Entity, FontWeight,
|
div, px, uniform_list, AnyElement, BackgroundExecutor, Div, Entity, Focusable, FontWeight,
|
||||||
ListSizingBehavior, ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText,
|
ListSizingBehavior, ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText,
|
||||||
UniformListScrollHandle, WeakEntity,
|
UniformListScrollHandle,
|
||||||
};
|
};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use language::{CodeLabel, CompletionDocumentation};
|
use language::CodeLabel;
|
||||||
use lsp::LanguageServerId;
|
use lsp::LanguageServerId;
|
||||||
|
use markdown::Markdown;
|
||||||
use multi_buffer::{Anchor, ExcerptId};
|
use multi_buffer::{Anchor, ExcerptId};
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
|
use project::lsp_store::CompletionDocumentation;
|
||||||
use project::{CodeAction, Completion, TaskSourceKind};
|
use project::{CodeAction, Completion, TaskSourceKind};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -21,12 +23,12 @@ use std::{
|
||||||
use task::ResolvedTask;
|
use task::ResolvedTask;
|
||||||
use ui::{prelude::*, Color, IntoElement, ListItem, Pixels, Popover, Styled};
|
use ui::{prelude::*, Color, IntoElement, ListItem, Pixels, Popover, Styled};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
|
||||||
|
|
||||||
|
use crate::hover_popover::{hover_markdown_style, open_markdown_url};
|
||||||
use crate::{
|
use crate::{
|
||||||
actions::{ConfirmCodeAction, ConfirmCompletion},
|
actions::{ConfirmCodeAction, ConfirmCompletion},
|
||||||
render_parsed_markdown, split_words, styled_runs_for_code_label, CodeActionProvider,
|
split_words, styled_runs_for_code_label, CodeActionProvider, CompletionId, CompletionProvider,
|
||||||
CompletionId, CompletionProvider, DisplayRow, Editor, EditorStyle, ResolvedTasks,
|
DisplayRow, Editor, EditorStyle, ResolvedTasks,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const MENU_GAP: Pixels = px(4.);
|
pub const MENU_GAP: Pixels = px(4.);
|
||||||
|
@ -137,17 +139,27 @@ impl CodeContextMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_aside(
|
pub fn render_aside(
|
||||||
&self,
|
&mut self,
|
||||||
style: &EditorStyle,
|
editor: &Editor,
|
||||||
max_size: Size<Pixels>,
|
max_size: Size<Pixels>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
window: &mut Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
match self {
|
match self {
|
||||||
CodeContextMenu::Completions(menu) => menu.render_aside(style, max_size, workspace, cx),
|
CodeContextMenu::Completions(menu) => menu.render_aside(editor, max_size, window, cx),
|
||||||
CodeContextMenu::CodeActions(_) => None,
|
CodeContextMenu::CodeActions(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn focused(&self, window: &mut Window, cx: &mut Context<Editor>) -> bool {
|
||||||
|
match self {
|
||||||
|
CodeContextMenu::Completions(completions_menu) => completions_menu
|
||||||
|
.markdown_element
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|markdown| markdown.focus_handle(cx).contains_focused(window, cx)),
|
||||||
|
CodeContextMenu::CodeActions(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ContextMenuOrigin {
|
pub enum ContextMenuOrigin {
|
||||||
|
@ -169,6 +181,7 @@ pub struct CompletionsMenu {
|
||||||
resolve_completions: bool,
|
resolve_completions: bool,
|
||||||
show_completion_documentation: bool,
|
show_completion_documentation: bool,
|
||||||
last_rendered_range: Rc<RefCell<Option<Range<usize>>>>,
|
last_rendered_range: Rc<RefCell<Option<Range<usize>>>>,
|
||||||
|
markdown_element: Option<Entity<Markdown>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompletionsMenu {
|
impl CompletionsMenu {
|
||||||
|
@ -199,6 +212,7 @@ impl CompletionsMenu {
|
||||||
scroll_handle: UniformListScrollHandle::new(),
|
scroll_handle: UniformListScrollHandle::new(),
|
||||||
resolve_completions: true,
|
resolve_completions: true,
|
||||||
last_rendered_range: RefCell::new(None).into(),
|
last_rendered_range: RefCell::new(None).into(),
|
||||||
|
markdown_element: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,6 +269,7 @@ impl CompletionsMenu {
|
||||||
resolve_completions: false,
|
resolve_completions: false,
|
||||||
show_completion_documentation: false,
|
show_completion_documentation: false,
|
||||||
last_rendered_range: RefCell::new(None).into(),
|
last_rendered_range: RefCell::new(None).into(),
|
||||||
|
markdown_element: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -556,10 +571,10 @@ impl CompletionsMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_aside(
|
fn render_aside(
|
||||||
&self,
|
&mut self,
|
||||||
style: &EditorStyle,
|
editor: &Editor,
|
||||||
max_size: Size<Pixels>,
|
max_size: Size<Pixels>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
window: &mut Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
if !self.show_completion_documentation {
|
if !self.show_completion_documentation {
|
||||||
|
@ -571,17 +586,35 @@ impl CompletionsMenu {
|
||||||
.documentation
|
.documentation
|
||||||
.as_ref()?
|
.as_ref()?
|
||||||
{
|
{
|
||||||
CompletionDocumentation::MultiLinePlainText(text) => {
|
CompletionDocumentation::MultiLinePlainText(text) => div().child(text.clone()),
|
||||||
div().child(SharedString::from(text.clone()))
|
CompletionDocumentation::MultiLineMarkdown(parsed) if !parsed.is_empty() => {
|
||||||
|
let markdown = self.markdown_element.get_or_insert_with(|| {
|
||||||
|
cx.new(|cx| {
|
||||||
|
let languages = editor
|
||||||
|
.workspace
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|(workspace, _)| workspace.upgrade())
|
||||||
|
.map(|workspace| workspace.read(cx).app_state().languages.clone());
|
||||||
|
let language = editor
|
||||||
|
.language_at(self.initial_position, cx)
|
||||||
|
.map(|l| l.name().to_proto());
|
||||||
|
Markdown::new(
|
||||||
|
SharedString::default(),
|
||||||
|
hover_markdown_style(window, cx),
|
||||||
|
languages,
|
||||||
|
language,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.copy_code_block_buttons(false)
|
||||||
|
.open_url(open_markdown_url)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
markdown.update(cx, |markdown, cx| {
|
||||||
|
markdown.reset(parsed.clone(), window, cx);
|
||||||
|
});
|
||||||
|
div().child(markdown.clone())
|
||||||
}
|
}
|
||||||
CompletionDocumentation::MultiLineMarkdown(parsed) if !parsed.text.is_empty() => div()
|
|
||||||
.child(render_parsed_markdown(
|
|
||||||
"completions_markdown",
|
|
||||||
parsed,
|
|
||||||
&style,
|
|
||||||
workspace,
|
|
||||||
cx,
|
|
||||||
)),
|
|
||||||
CompletionDocumentation::MultiLineMarkdown(_) => return None,
|
CompletionDocumentation::MultiLineMarkdown(_) => return None,
|
||||||
CompletionDocumentation::SingleLine(_) => return None,
|
CompletionDocumentation::SingleLine(_) => return None,
|
||||||
CompletionDocumentation::Undocumented => return None,
|
CompletionDocumentation::Undocumented => return None,
|
||||||
|
|
|
@ -99,9 +99,9 @@ use itertools::Itertools;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::{self, all_language_settings, language_settings, InlayHintSettings},
|
language_settings::{self, all_language_settings, language_settings, InlayHintSettings},
|
||||||
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
|
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
|
||||||
CompletionDocumentation, CursorShape, Diagnostic, DiskState, EditPredictionsMode, EditPreview,
|
CursorShape, Diagnostic, DiskState, EditPredictionsMode, EditPreview, HighlightedText,
|
||||||
HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection,
|
IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
|
||||||
SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
|
TransactionId, TreeSitterOptions,
|
||||||
};
|
};
|
||||||
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
|
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
|
||||||
use linked_editing_ranges::refresh_linked_ranges;
|
use linked_editing_ranges::refresh_linked_ranges;
|
||||||
|
@ -132,7 +132,7 @@ use multi_buffer::{
|
||||||
ToOffsetUtf16,
|
ToOffsetUtf16,
|
||||||
};
|
};
|
||||||
use project::{
|
use project::{
|
||||||
lsp_store::{FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
|
lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
|
||||||
project_settings::{GitGutterSetting, ProjectSettings},
|
project_settings::{GitGutterSetting, ProjectSettings},
|
||||||
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
|
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
|
||||||
PrepareRenameResponse, Project, ProjectItem, ProjectTransaction, TaskSourceKind,
|
PrepareRenameResponse, Project, ProjectItem, ProjectTransaction, TaskSourceKind,
|
||||||
|
@ -6221,19 +6221,14 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_context_menu_aside(
|
fn render_context_menu_aside(
|
||||||
&self,
|
&mut self,
|
||||||
style: &EditorStyle,
|
|
||||||
max_size: Size<Pixels>,
|
max_size: Size<Pixels>,
|
||||||
|
window: &mut Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
self.context_menu.borrow().as_ref().and_then(|menu| {
|
self.context_menu.borrow_mut().as_mut().and_then(|menu| {
|
||||||
if menu.visible() {
|
if menu.visible() {
|
||||||
menu.render_aside(
|
menu.render_aside(self, max_size, window, cx)
|
||||||
style,
|
|
||||||
max_size,
|
|
||||||
self.workspace.as_ref().map(|(w, _)| w.clone()),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -14926,8 +14921,14 @@ impl Editor {
|
||||||
if !self.hover_state.focused(window, cx) {
|
if !self.hover_state.focused(window, cx) {
|
||||||
hide_hover(self, cx);
|
hide_hover(self, cx);
|
||||||
}
|
}
|
||||||
|
if !self
|
||||||
self.hide_context_menu(window, cx);
|
.context_menu
|
||||||
|
.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|context_menu| context_menu.focused(window, cx))
|
||||||
|
{
|
||||||
|
self.hide_context_menu(window, cx);
|
||||||
|
}
|
||||||
self.discard_inline_completion(false, cx);
|
self.discard_inline_completion(false, cx);
|
||||||
cx.emit(EditorEvent::Blurred);
|
cx.emit(EditorEvent::Blurred);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -15674,7 +15675,7 @@ fn snippet_completions(
|
||||||
documentation: snippet
|
documentation: snippet
|
||||||
.description
|
.description
|
||||||
.clone()
|
.clone()
|
||||||
.map(CompletionDocumentation::SingleLine),
|
.map(|description| CompletionDocumentation::SingleLine(description.into())),
|
||||||
lsp_completion: lsp::CompletionItem {
|
lsp_completion: lsp::CompletionItem {
|
||||||
label: snippet.prefix.first().unwrap().clone(),
|
label: snippet.prefix.first().unwrap().clone(),
|
||||||
kind: Some(CompletionItemKind::SNIPPET),
|
kind: Some(CompletionItemKind::SNIPPET),
|
||||||
|
|
|
@ -3426,9 +3426,11 @@ impl EditorElement {
|
||||||
available_within_viewport.right - px(1.),
|
available_within_viewport.right - px(1.),
|
||||||
MENU_ASIDE_MAX_WIDTH,
|
MENU_ASIDE_MAX_WIDTH,
|
||||||
);
|
);
|
||||||
let Some(mut aside) =
|
let Some(mut aside) = self.render_context_menu_aside(
|
||||||
self.render_context_menu_aside(size(max_width, max_height - POPOVER_Y_PADDING), cx)
|
size(max_width, max_height - POPOVER_Y_PADDING),
|
||||||
else {
|
window,
|
||||||
|
cx,
|
||||||
|
) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
aside.layout_as_root(AvailableSpace::min_size(), window, cx);
|
aside.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||||
|
@ -3450,7 +3452,7 @@ impl EditorElement {
|
||||||
),
|
),
|
||||||
) - POPOVER_Y_PADDING,
|
) - POPOVER_Y_PADDING,
|
||||||
);
|
);
|
||||||
let Some(mut aside) = self.render_context_menu_aside(max_size, cx) else {
|
let Some(mut aside) = self.render_context_menu_aside(max_size, window, cx) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let actual_size = aside.layout_as_root(AvailableSpace::min_size(), window, cx);
|
let actual_size = aside.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||||
|
@ -3491,7 +3493,7 @@ impl EditorElement {
|
||||||
|
|
||||||
// Skip drawing if it doesn't fit anywhere.
|
// Skip drawing if it doesn't fit anywhere.
|
||||||
if let Some((aside, position)) = positioned_aside {
|
if let Some((aside, position)) = positioned_aside {
|
||||||
window.defer_draw(aside, position, 1);
|
window.defer_draw(aside, position, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3512,14 +3514,14 @@ impl EditorElement {
|
||||||
fn render_context_menu_aside(
|
fn render_context_menu_aside(
|
||||||
&self,
|
&self,
|
||||||
max_size: Size<Pixels>,
|
max_size: Size<Pixels>,
|
||||||
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
if max_size.width < px(100.) || max_size.height < px(12.) {
|
if max_size.width < px(100.) || max_size.height < px(12.) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.render_context_menu_aside(&self.style, max_size, cx)
|
editor.render_context_menu_aside(max_size, window, cx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::{invisibles::is_invisible, InlayOffset, ToDisplayPoint},
|
display_map::{invisibles::is_invisible, InlayOffset, ToDisplayPoint},
|
||||||
hover_links::{InlayHighlight, RangeInEditor},
|
hover_links::{InlayHighlight, RangeInEditor},
|
||||||
scroll::ScrollAmount,
|
scroll::{Autoscroll, ScrollAmount},
|
||||||
Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
|
Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
|
||||||
Hover,
|
Hover,
|
||||||
};
|
};
|
||||||
|
@ -18,12 +18,14 @@ use markdown::{Markdown, MarkdownStyle};
|
||||||
use multi_buffer::ToOffset;
|
use multi_buffer::ToOffset;
|
||||||
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
|
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::rc::Rc;
|
|
||||||
use std::{borrow::Cow, cell::RefCell};
|
use std::{borrow::Cow, cell::RefCell};
|
||||||
use std::{ops::Range, sync::Arc, time::Duration};
|
use std::{ops::Range, sync::Arc, time::Duration};
|
||||||
|
use std::{path::PathBuf, rc::Rc};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{prelude::*, theme_is_transparent, Scrollbar, ScrollbarState};
|
use ui::{prelude::*, theme_is_transparent, Scrollbar, ScrollbarState};
|
||||||
|
use url::Url;
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
|
use workspace::Workspace;
|
||||||
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
|
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
|
||||||
|
|
||||||
pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
|
pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
|
||||||
|
@ -356,7 +358,15 @@ fn show_hover(
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
Markdown::new_text(text, markdown_style.clone(), None, None, window, cx)
|
Markdown::new_text(
|
||||||
|
SharedString::new(text),
|
||||||
|
markdown_style.clone(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.open_url(open_markdown_url)
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
|
@ -558,69 +568,122 @@ async fn parse_blocks(
|
||||||
|
|
||||||
let rendered_block = cx
|
let rendered_block = cx
|
||||||
.new_window_entity(|window, cx| {
|
.new_window_entity(|window, cx| {
|
||||||
let settings = ThemeSettings::get_global(cx);
|
|
||||||
let ui_font_family = settings.ui_font.family.clone();
|
|
||||||
let ui_font_fallbacks = settings.ui_font.fallbacks.clone();
|
|
||||||
let buffer_font_family = settings.buffer_font.family.clone();
|
|
||||||
let buffer_font_fallbacks = settings.buffer_font.fallbacks.clone();
|
|
||||||
|
|
||||||
let mut base_text_style = window.text_style();
|
|
||||||
base_text_style.refine(&TextStyleRefinement {
|
|
||||||
font_family: Some(ui_font_family.clone()),
|
|
||||||
font_fallbacks: ui_font_fallbacks,
|
|
||||||
color: Some(cx.theme().colors().editor_foreground),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let markdown_style = MarkdownStyle {
|
|
||||||
base_text_style,
|
|
||||||
code_block: StyleRefinement::default().my(rems(1.)).font_buffer(cx),
|
|
||||||
inline_code: TextStyleRefinement {
|
|
||||||
background_color: Some(cx.theme().colors().background),
|
|
||||||
font_family: Some(buffer_font_family),
|
|
||||||
font_fallbacks: buffer_font_fallbacks,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
rule_color: cx.theme().colors().border,
|
|
||||||
block_quote_border_color: Color::Muted.color(cx),
|
|
||||||
block_quote: TextStyleRefinement {
|
|
||||||
color: Some(Color::Muted.color(cx)),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
link: TextStyleRefinement {
|
|
||||||
color: Some(cx.theme().colors().editor_foreground),
|
|
||||||
underline: Some(gpui::UnderlineStyle {
|
|
||||||
thickness: px(1.),
|
|
||||||
color: Some(cx.theme().colors().editor_foreground),
|
|
||||||
wavy: false,
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
syntax: cx.theme().syntax().clone(),
|
|
||||||
selection_background_color: { cx.theme().players().local().selection },
|
|
||||||
|
|
||||||
heading: StyleRefinement::default()
|
|
||||||
.font_weight(FontWeight::BOLD)
|
|
||||||
.text_base()
|
|
||||||
.mt(rems(1.))
|
|
||||||
.mb_0(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Markdown::new(
|
Markdown::new(
|
||||||
combined_text,
|
combined_text.into(),
|
||||||
markdown_style.clone(),
|
hover_markdown_style(window, cx),
|
||||||
Some(language_registry.clone()),
|
Some(language_registry.clone()),
|
||||||
fallback_language_name,
|
fallback_language_name,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.copy_code_block_buttons(false)
|
.copy_code_block_buttons(false)
|
||||||
|
.open_url(open_markdown_url)
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
rendered_block
|
rendered_block
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hover_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||||
|
let settings = ThemeSettings::get_global(cx);
|
||||||
|
let ui_font_family = settings.ui_font.family.clone();
|
||||||
|
let ui_font_fallbacks = settings.ui_font.fallbacks.clone();
|
||||||
|
let buffer_font_family = settings.buffer_font.family.clone();
|
||||||
|
let buffer_font_fallbacks = settings.buffer_font.fallbacks.clone();
|
||||||
|
|
||||||
|
let mut base_text_style = window.text_style();
|
||||||
|
base_text_style.refine(&TextStyleRefinement {
|
||||||
|
font_family: Some(ui_font_family.clone()),
|
||||||
|
font_fallbacks: ui_font_fallbacks,
|
||||||
|
color: Some(cx.theme().colors().editor_foreground),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
MarkdownStyle {
|
||||||
|
base_text_style,
|
||||||
|
code_block: StyleRefinement::default().my(rems(1.)).font_buffer(cx),
|
||||||
|
inline_code: TextStyleRefinement {
|
||||||
|
background_color: Some(cx.theme().colors().background),
|
||||||
|
font_family: Some(buffer_font_family),
|
||||||
|
font_fallbacks: buffer_font_fallbacks,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
rule_color: cx.theme().colors().border,
|
||||||
|
block_quote_border_color: Color::Muted.color(cx),
|
||||||
|
block_quote: TextStyleRefinement {
|
||||||
|
color: Some(Color::Muted.color(cx)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
link: TextStyleRefinement {
|
||||||
|
color: Some(cx.theme().colors().editor_foreground),
|
||||||
|
underline: Some(gpui::UnderlineStyle {
|
||||||
|
thickness: px(1.),
|
||||||
|
color: Some(cx.theme().colors().editor_foreground),
|
||||||
|
wavy: false,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
syntax: cx.theme().syntax().clone(),
|
||||||
|
selection_background_color: { cx.theme().players().local().selection },
|
||||||
|
|
||||||
|
heading: StyleRefinement::default()
|
||||||
|
.font_weight(FontWeight::BOLD)
|
||||||
|
.text_base()
|
||||||
|
.mt(rems(1.))
|
||||||
|
.mb_0(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_markdown_url(link: SharedString, window: &mut Window, cx: &mut App) {
|
||||||
|
if let Ok(uri) = Url::parse(&link) {
|
||||||
|
if uri.scheme() == "file" {
|
||||||
|
if let Some(workspace) = window.root::<Workspace>().flatten() {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
let task =
|
||||||
|
workspace.open_abs_path(PathBuf::from(uri.path()), false, window, cx);
|
||||||
|
|
||||||
|
cx.spawn_in(window, |_, mut cx| async move {
|
||||||
|
let item = task.await?;
|
||||||
|
// Ruby LSP uses URLs with #L1,1-4,4
|
||||||
|
// we'll just take the first number and assume it's a line number
|
||||||
|
let Some(fragment) = uri.fragment() else {
|
||||||
|
return anyhow::Ok(());
|
||||||
|
};
|
||||||
|
let mut accum = 0u32;
|
||||||
|
for c in fragment.chars() {
|
||||||
|
if c >= '0' && c <= '9' && accum < u32::MAX / 2 {
|
||||||
|
accum *= 10;
|
||||||
|
accum += c as u32 - '0' as u32;
|
||||||
|
} else if accum > 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if accum == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let Some(editor) = cx.update(|_, cx| item.act_as::<Editor>(cx))? else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
editor.update_in(&mut cx, |editor, window, cx| {
|
||||||
|
editor.change_selections(
|
||||||
|
Some(Autoscroll::fit()),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
|selections| {
|
||||||
|
selections.select_ranges([text::Point::new(accum - 1, 0)
|
||||||
|
..text::Point::new(accum - 1, 0)]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cx.open_url(&link);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct HoverState {
|
pub struct HoverState {
|
||||||
pub info_popovers: Vec<InfoPopover>,
|
pub info_popovers: Vec<InfoPopover>,
|
||||||
|
|
|
@ -7,7 +7,6 @@ pub use crate::{
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
|
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
|
||||||
language_settings::{language_settings, LanguageSettings},
|
language_settings::{language_settings, LanguageSettings},
|
||||||
markdown::parse_markdown,
|
|
||||||
outline::OutlineItem,
|
outline::OutlineItem,
|
||||||
syntax_map::{
|
syntax_map::{
|
||||||
SyntaxLayer, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatch,
|
SyntaxLayer, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatch,
|
||||||
|
@ -231,50 +230,6 @@ pub struct Diagnostic {
|
||||||
pub data: Option<Value>,
|
pub data: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO - move this into the `project` crate and make it private.
|
|
||||||
pub async fn prepare_completion_documentation(
|
|
||||||
documentation: &lsp::Documentation,
|
|
||||||
language_registry: &Arc<LanguageRegistry>,
|
|
||||||
language: Option<Arc<Language>>,
|
|
||||||
) -> CompletionDocumentation {
|
|
||||||
match documentation {
|
|
||||||
lsp::Documentation::String(text) => {
|
|
||||||
if text.lines().count() <= 1 {
|
|
||||||
CompletionDocumentation::SingleLine(text.clone())
|
|
||||||
} else {
|
|
||||||
CompletionDocumentation::MultiLinePlainText(text.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind {
|
|
||||||
lsp::MarkupKind::PlainText => {
|
|
||||||
if value.lines().count() <= 1 {
|
|
||||||
CompletionDocumentation::SingleLine(value.clone())
|
|
||||||
} else {
|
|
||||||
CompletionDocumentation::MultiLinePlainText(value.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lsp::MarkupKind::Markdown => {
|
|
||||||
let parsed = parse_markdown(value, Some(language_registry), language).await;
|
|
||||||
CompletionDocumentation::MultiLineMarkdown(parsed)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum CompletionDocumentation {
|
|
||||||
/// There is no documentation for this completion.
|
|
||||||
Undocumented,
|
|
||||||
/// A single line of documentation.
|
|
||||||
SingleLine(String),
|
|
||||||
/// Multiple lines of plain text documentation.
|
|
||||||
MultiLinePlainText(String),
|
|
||||||
/// Markdown documentation.
|
|
||||||
MultiLineMarkdown(ParsedMarkdown),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An operation used to synchronize this buffer with its other replicas.
|
/// An operation used to synchronize this buffer with its other replicas.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Operation {
|
pub enum Operation {
|
||||||
|
|
|
@ -20,7 +20,6 @@ test-support = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
futures.workspace = true
|
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
linkify.workspace = true
|
linkify.workspace = true
|
||||||
|
@ -34,7 +33,7 @@ util.workspace = true
|
||||||
assets.workspace = true
|
assets.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
languages.workspace = true
|
languages = { workspace = true, features = ["load-grammars"] }
|
||||||
node_runtime.workspace = true
|
node_runtime.workspace = true
|
||||||
settings = { workspace = true, features = ["test-support"] }
|
settings = { workspace = true, features = ["test-support"] }
|
||||||
util = { workspace = true, features = ["test-support"] }
|
util = { workspace = true, features = ["test-support"] }
|
||||||
|
|
|
@ -15,84 +15,12 @@ const MARKDOWN_EXAMPLE: &str = r#"
|
||||||
## Headings
|
## Headings
|
||||||
Headings are created by adding one or more `#` symbols before your heading text. The number of `#` you use will determine the size of the heading.
|
Headings are created by adding one or more `#` symbols before your heading text. The number of `#` you use will determine the size of the heading.
|
||||||
|
|
||||||
```rust
|
|
||||||
gpui::window::ViewContext
|
|
||||||
impl<'a, V> ViewContext<'a, V>
|
|
||||||
pub fn on_blur(&mut self, handle: &FocusHandle, listener: impl FnMut(&mut V, &mut iewContext<V>) + 'static) -> Subscription
|
|
||||||
where
|
|
||||||
// Bounds from impl:
|
|
||||||
V: 'static,
|
|
||||||
```
|
```
|
||||||
|
function a(b: T) {
|
||||||
|
|
||||||
## Emphasis
|
|
||||||
Emphasis can be added with italics or bold. *This text will be italic*. _This will also be italic_
|
|
||||||
|
|
||||||
## Lists
|
|
||||||
|
|
||||||
### Unordered Lists
|
|
||||||
Unordered lists use asterisks `*`, plus `+`, or minus `-` as list markers.
|
|
||||||
|
|
||||||
* Item 1
|
|
||||||
* Item 2
|
|
||||||
* Item 2a
|
|
||||||
* Item 2b
|
|
||||||
|
|
||||||
### Ordered Lists
|
|
||||||
Ordered lists use numbers followed by a period.
|
|
||||||
|
|
||||||
1. Item 1
|
|
||||||
2. Item 2
|
|
||||||
3. Item 3
|
|
||||||
1. Item 3a
|
|
||||||
2. Item 3b
|
|
||||||
|
|
||||||
## Links
|
|
||||||
Links are created using the format [http://zed.dev](https://zed.dev).
|
|
||||||
|
|
||||||
They can also be detected automatically, for example https://zed.dev/blog.
|
|
||||||
|
|
||||||
They may contain dollar signs:
|
|
||||||
|
|
||||||
[https://svelte.dev/docs/svelte/$state](https://svelte.dev/docs/svelte/$state)
|
|
||||||
|
|
||||||
https://svelte.dev/docs/svelte/$state
|
|
||||||
|
|
||||||
## Images
|
|
||||||
Images are like links, but with an exclamation mark `!` in front.
|
|
||||||
|
|
||||||
```markdown
|
|
||||||

|
|
||||||
```
|
|
||||||
|
|
||||||
## Code
|
|
||||||
Inline `code` can be wrapped with backticks `` ` ``.
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
Inline `code` has `back-ticks around` it.
|
|
||||||
```
|
|
||||||
|
|
||||||
Code blocks can be created by indenting lines by four spaces or with triple backticks ```.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function test() {
|
|
||||||
console.log("notice the blank line before this function?");
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Blockquotes
|
|
||||||
Blockquotes are created with `>`.
|
|
||||||
|
|
||||||
> This is a blockquote.
|
|
||||||
|
|
||||||
## Horizontal Rules
|
|
||||||
Horizontal rules are created using three or more asterisks `***`, dashes `---`, or underscores `___`.
|
|
||||||
|
|
||||||
## Line breaks
|
|
||||||
This is a
|
|
||||||
\
|
|
||||||
line break!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Remember, markdown processors may have slight differences and extensions, so always refer to the specific documentation or guides relevant to your platform or editor for the best practices and additional features.
|
Remember, markdown processors may have slight differences and extensions, so always refer to the specific documentation or guides relevant to your platform or editor for the best practices and additional features.
|
||||||
"#;
|
"#;
|
||||||
|
@ -161,7 +89,7 @@ pub fn main() {
|
||||||
};
|
};
|
||||||
|
|
||||||
MarkdownExample::new(
|
MarkdownExample::new(
|
||||||
MARKDOWN_EXAMPLE.to_string(),
|
MARKDOWN_EXAMPLE.into(),
|
||||||
markdown_style,
|
markdown_style,
|
||||||
language_registry,
|
language_registry,
|
||||||
window,
|
window,
|
||||||
|
@ -179,14 +107,22 @@ struct MarkdownExample {
|
||||||
|
|
||||||
impl MarkdownExample {
|
impl MarkdownExample {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
text: String,
|
text: SharedString,
|
||||||
style: MarkdownStyle,
|
style: MarkdownStyle,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let markdown =
|
let markdown = cx.new(|cx| {
|
||||||
cx.new(|cx| Markdown::new(text, style, Some(language_registry), None, window, cx));
|
Markdown::new(
|
||||||
|
text,
|
||||||
|
style,
|
||||||
|
Some(language_registry),
|
||||||
|
Some("TypeScript".to_string()),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
Self { markdown }
|
Self { markdown }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
|
||||||
use crate::parser::CodeBlockKind;
|
use crate::parser::CodeBlockKind;
|
||||||
use futures::FutureExt;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, point, quad, AnyElement, App, Bounds, ClipboardItem, CursorStyle, DispatchPhase,
|
actions, point, quad, AnyElement, App, Bounds, ClipboardItem, CursorStyle, DispatchPhase,
|
||||||
Edges, Entity, FocusHandle, Focusable, FontStyle, FontWeight, GlobalElementId, Hitbox, Hsla,
|
Edges, Entity, FocusHandle, Focusable, FontStyle, FontWeight, GlobalElementId, Hitbox, Hsla,
|
||||||
|
@ -12,7 +11,7 @@ use gpui::{
|
||||||
use language::{Language, LanguageRegistry, Rope};
|
use language::{Language, LanguageRegistry, Rope};
|
||||||
use parser::{parse_links_only, parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
|
use parser::{parse_links_only, parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
|
||||||
|
|
||||||
use std::{iter, mem, ops::Range, rc::Rc, sync::Arc};
|
use std::{collections::HashMap, iter, mem, ops::Range, rc::Rc, sync::Arc};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use ui::{prelude::*, Tooltip};
|
use ui::{prelude::*, Tooltip};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
|
@ -49,7 +48,7 @@ impl Default for MarkdownStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Markdown {
|
pub struct Markdown {
|
||||||
source: String,
|
source: SharedString,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
pressed_link: Option<RenderedLink>,
|
pressed_link: Option<RenderedLink>,
|
||||||
autoscroll_request: Option<usize>,
|
autoscroll_request: Option<usize>,
|
||||||
|
@ -60,6 +59,7 @@ pub struct Markdown {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
language_registry: Option<Arc<LanguageRegistry>>,
|
language_registry: Option<Arc<LanguageRegistry>>,
|
||||||
fallback_code_block_language: Option<String>,
|
fallback_code_block_language: Option<String>,
|
||||||
|
open_url: Option<Box<dyn Fn(SharedString, &mut Window, &mut App)>>,
|
||||||
options: Options,
|
options: Options,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ actions!(markdown, [Copy]);
|
||||||
|
|
||||||
impl Markdown {
|
impl Markdown {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
source: String,
|
source: SharedString,
|
||||||
style: MarkdownStyle,
|
style: MarkdownStyle,
|
||||||
language_registry: Option<Arc<LanguageRegistry>>,
|
language_registry: Option<Arc<LanguageRegistry>>,
|
||||||
fallback_code_block_language: Option<String>,
|
fallback_code_block_language: Option<String>,
|
||||||
|
@ -97,13 +97,24 @@ impl Markdown {
|
||||||
parse_links_only: false,
|
parse_links_only: false,
|
||||||
copy_code_block_buttons: true,
|
copy_code_block_buttons: true,
|
||||||
},
|
},
|
||||||
|
open_url: None,
|
||||||
};
|
};
|
||||||
this.parse(window, cx);
|
this.parse(window, cx);
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn open_url(
|
||||||
|
self,
|
||||||
|
open_url: impl Fn(SharedString, &mut Window, &mut App) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
open_url: Some(Box::new(open_url)),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_text(
|
pub fn new_text(
|
||||||
source: String,
|
source: SharedString,
|
||||||
style: MarkdownStyle,
|
style: MarkdownStyle,
|
||||||
language_registry: Option<Arc<LanguageRegistry>>,
|
language_registry: Option<Arc<LanguageRegistry>>,
|
||||||
fallback_code_block_language: Option<String>,
|
fallback_code_block_language: Option<String>,
|
||||||
|
@ -127,6 +138,7 @@ impl Markdown {
|
||||||
parse_links_only: true,
|
parse_links_only: true,
|
||||||
copy_code_block_buttons: true,
|
copy_code_block_buttons: true,
|
||||||
},
|
},
|
||||||
|
open_url: None,
|
||||||
};
|
};
|
||||||
this.parse(window, cx);
|
this.parse(window, cx);
|
||||||
this
|
this
|
||||||
|
@ -137,11 +149,11 @@ impl Markdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn append(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.source.push_str(text);
|
self.source = SharedString::new(self.source.to_string() + text);
|
||||||
self.parse(window, cx);
|
self.parse(window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self, source: String, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn reset(&mut self, source: SharedString, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if source == self.source() {
|
if source == self.source() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -176,17 +188,38 @@ impl Markdown {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = self.source.clone();
|
let source = self.source.clone();
|
||||||
let parse_text_only = self.options.parse_links_only;
|
let parse_text_only = self.options.parse_links_only;
|
||||||
|
let language_registry = self.language_registry.clone();
|
||||||
|
let fallback = self.fallback_code_block_language.clone();
|
||||||
let parsed = cx.background_spawn(async move {
|
let parsed = cx.background_spawn(async move {
|
||||||
let text = SharedString::from(text);
|
if parse_text_only {
|
||||||
let events = match parse_text_only {
|
return anyhow::Ok(ParsedMarkdown {
|
||||||
true => Arc::from(parse_links_only(text.as_ref())),
|
events: Arc::from(parse_links_only(source.as_ref())),
|
||||||
false => Arc::from(parse_markdown(text.as_ref())),
|
source,
|
||||||
};
|
languages: HashMap::default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let (events, language_names) = parse_markdown(&source);
|
||||||
|
let mut languages = HashMap::with_capacity(language_names.len());
|
||||||
|
for name in language_names {
|
||||||
|
if let Some(registry) = language_registry.as_ref() {
|
||||||
|
let language = if !name.is_empty() {
|
||||||
|
registry.language_for_name(&name)
|
||||||
|
} else if let Some(fallback) = &fallback {
|
||||||
|
registry.language_for_name(fallback)
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Ok(language) = language.await {
|
||||||
|
languages.insert(name, language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
anyhow::Ok(ParsedMarkdown {
|
anyhow::Ok(ParsedMarkdown {
|
||||||
source: text,
|
source,
|
||||||
events,
|
events: Arc::from(events),
|
||||||
|
languages,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -217,12 +250,7 @@ impl Markdown {
|
||||||
|
|
||||||
impl Render for Markdown {
|
impl Render for Markdown {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
MarkdownElement::new(
|
MarkdownElement::new(cx.entity().clone(), self.style.clone())
|
||||||
cx.entity().clone(),
|
|
||||||
self.style.clone(),
|
|
||||||
self.language_registry.clone(),
|
|
||||||
self.fallback_code_block_language.clone(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,6 +298,7 @@ impl Selection {
|
||||||
pub struct ParsedMarkdown {
|
pub struct ParsedMarkdown {
|
||||||
source: SharedString,
|
source: SharedString,
|
||||||
events: Arc<[(Range<usize>, MarkdownEvent)]>,
|
events: Arc<[(Range<usize>, MarkdownEvent)]>,
|
||||||
|
languages: HashMap<SharedString, Arc<Language>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParsedMarkdown {
|
impl ParsedMarkdown {
|
||||||
|
@ -285,61 +314,11 @@ impl ParsedMarkdown {
|
||||||
pub struct MarkdownElement {
|
pub struct MarkdownElement {
|
||||||
markdown: Entity<Markdown>,
|
markdown: Entity<Markdown>,
|
||||||
style: MarkdownStyle,
|
style: MarkdownStyle,
|
||||||
language_registry: Option<Arc<LanguageRegistry>>,
|
|
||||||
fallback_code_block_language: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MarkdownElement {
|
impl MarkdownElement {
|
||||||
fn new(
|
fn new(markdown: Entity<Markdown>, style: MarkdownStyle) -> Self {
|
||||||
markdown: Entity<Markdown>,
|
Self { markdown, style }
|
||||||
style: MarkdownStyle,
|
|
||||||
language_registry: Option<Arc<LanguageRegistry>>,
|
|
||||||
fallback_code_block_language: Option<String>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
markdown,
|
|
||||||
style,
|
|
||||||
language_registry,
|
|
||||||
fallback_code_block_language,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_language(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Option<Arc<Language>> {
|
|
||||||
let language_test = self.language_registry.as_ref()?.language_for_name(name);
|
|
||||||
|
|
||||||
let language_name = match language_test.now_or_never() {
|
|
||||||
Some(Ok(_)) => String::from(name),
|
|
||||||
Some(Err(_)) if !name.is_empty() && self.fallback_code_block_language.is_some() => {
|
|
||||||
self.fallback_code_block_language.clone().unwrap()
|
|
||||||
}
|
|
||||||
_ => String::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let language = self
|
|
||||||
.language_registry
|
|
||||||
.as_ref()?
|
|
||||||
.language_for_name(language_name.as_str())
|
|
||||||
.map(|language| language.ok())
|
|
||||||
.shared();
|
|
||||||
|
|
||||||
match language.clone().now_or_never() {
|
|
||||||
Some(language) => language,
|
|
||||||
None => {
|
|
||||||
let markdown = self.markdown.downgrade();
|
|
||||||
window
|
|
||||||
.spawn(cx, |mut cx| async move {
|
|
||||||
language.await;
|
|
||||||
markdown.update(&mut cx, |_, cx| cx.notify())
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_selection(
|
fn paint_selection(
|
||||||
|
@ -452,7 +431,7 @@ impl MarkdownElement {
|
||||||
pending: true,
|
pending: true,
|
||||||
};
|
};
|
||||||
window.focus(&markdown.focus_handle);
|
window.focus(&markdown.focus_handle);
|
||||||
window.prevent_default()
|
window.prevent_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -492,11 +471,15 @@ impl MarkdownElement {
|
||||||
});
|
});
|
||||||
self.on_mouse_event(window, cx, {
|
self.on_mouse_event(window, cx, {
|
||||||
let rendered_text = rendered_text.clone();
|
let rendered_text = rendered_text.clone();
|
||||||
move |markdown, event: &MouseUpEvent, phase, _, cx| {
|
move |markdown, event: &MouseUpEvent, phase, window, cx| {
|
||||||
if phase.bubble() {
|
if phase.bubble() {
|
||||||
if let Some(pressed_link) = markdown.pressed_link.take() {
|
if let Some(pressed_link) = markdown.pressed_link.take() {
|
||||||
if Some(&pressed_link) == rendered_text.link_for_position(event.position) {
|
if Some(&pressed_link) == rendered_text.link_for_position(event.position) {
|
||||||
cx.open_url(&pressed_link.destination_url);
|
if let Some(open_url) = markdown.open_url.as_mut() {
|
||||||
|
open_url(pressed_link.destination_url, window, cx);
|
||||||
|
} else {
|
||||||
|
cx.open_url(&pressed_link.destination_url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if markdown.selection.pending {
|
} else if markdown.selection.pending {
|
||||||
|
@ -617,7 +600,7 @@ impl Element for MarkdownElement {
|
||||||
}
|
}
|
||||||
MarkdownTag::CodeBlock(kind) => {
|
MarkdownTag::CodeBlock(kind) => {
|
||||||
let language = if let CodeBlockKind::Fenced(language) = kind {
|
let language = if let CodeBlockKind::Fenced(language) = kind {
|
||||||
self.load_language(language.as_ref(), window, cx)
|
parsed_markdown.languages.get(language).cloned()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,15 +2,16 @@ use gpui::SharedString;
|
||||||
use linkify::LinkFinder;
|
use linkify::LinkFinder;
|
||||||
pub use pulldown_cmark::TagEnd as MarkdownTagEnd;
|
pub use pulldown_cmark::TagEnd as MarkdownTagEnd;
|
||||||
use pulldown_cmark::{Alignment, HeadingLevel, LinkType, MetadataBlockKind, Options, Parser};
|
use pulldown_cmark::{Alignment, HeadingLevel, LinkType, MetadataBlockKind, Options, Parser};
|
||||||
use std::ops::Range;
|
use std::{collections::HashSet, ops::Range};
|
||||||
|
|
||||||
pub fn parse_markdown(text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
pub fn parse_markdown(text: &str) -> (Vec<(Range<usize>, MarkdownEvent)>, HashSet<SharedString>) {
|
||||||
let mut options = Options::all();
|
let mut options = Options::all();
|
||||||
options.remove(pulldown_cmark::Options::ENABLE_DEFINITION_LIST);
|
options.remove(pulldown_cmark::Options::ENABLE_DEFINITION_LIST);
|
||||||
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
|
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
|
||||||
options.remove(pulldown_cmark::Options::ENABLE_MATH);
|
options.remove(pulldown_cmark::Options::ENABLE_MATH);
|
||||||
|
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
|
let mut languages = HashSet::new();
|
||||||
let mut within_link = false;
|
let mut within_link = false;
|
||||||
let mut within_metadata = false;
|
let mut within_metadata = false;
|
||||||
for (pulldown_event, mut range) in Parser::new_ext(text, options).into_offset_iter() {
|
for (pulldown_event, mut range) in Parser::new_ext(text, options).into_offset_iter() {
|
||||||
|
@ -27,6 +28,11 @@ pub fn parse_markdown(text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||||
match tag {
|
match tag {
|
||||||
pulldown_cmark::Tag::Link { .. } => within_link = true,
|
pulldown_cmark::Tag::Link { .. } => within_link = true,
|
||||||
pulldown_cmark::Tag::MetadataBlock { .. } => within_metadata = true,
|
pulldown_cmark::Tag::MetadataBlock { .. } => within_metadata = true,
|
||||||
|
pulldown_cmark::Tag::CodeBlock(pulldown_cmark::CodeBlockKind::Fenced(
|
||||||
|
ref language,
|
||||||
|
)) => {
|
||||||
|
languages.insert(SharedString::from(language.to_string()));
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
events.push((range, MarkdownEvent::Start(tag.into())))
|
events.push((range, MarkdownEvent::Start(tag.into())))
|
||||||
|
@ -102,7 +108,7 @@ pub fn parse_markdown(text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||||
pulldown_cmark::Event::InlineMath(_) | pulldown_cmark::Event::DisplayMath(_) => {}
|
pulldown_cmark::Event::InlineMath(_) | pulldown_cmark::Event::DisplayMath(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events
|
(events, languages)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_links_only(mut text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
pub fn parse_links_only(mut text: &str) -> Vec<(Range<usize>, MarkdownEvent)> {
|
||||||
|
|
|
@ -26,7 +26,8 @@ use futures::{
|
||||||
};
|
};
|
||||||
use globset::{Glob, GlobBuilder, GlobMatcher, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobBuilder, GlobMatcher, GlobSet, GlobSetBuilder};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, PromptLevel, Task, WeakEntity,
|
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, PromptLevel, SharedString, Task,
|
||||||
|
WeakEntity,
|
||||||
};
|
};
|
||||||
use http_client::HttpClient;
|
use http_client::HttpClient;
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
|
@ -34,13 +35,12 @@ use language::{
|
||||||
language_settings::{
|
language_settings::{
|
||||||
language_settings, FormatOnSave, Formatter, LanguageSettings, SelectedFormatter,
|
language_settings, FormatOnSave, Formatter, LanguageSettings, SelectedFormatter,
|
||||||
},
|
},
|
||||||
markdown, point_to_lsp, prepare_completion_documentation,
|
point_to_lsp,
|
||||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||||
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel,
|
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel,
|
||||||
CompletionDocumentation, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, File as _, Language,
|
Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, File as _, Language, LanguageRegistry,
|
||||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageToolchainStore, LocalFile, LspAdapter,
|
LanguageServerBinaryStatus, LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate,
|
||||||
LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
|
Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||||
Unclipped,
|
|
||||||
};
|
};
|
||||||
use lsp::{
|
use lsp::{
|
||||||
notification::DidRenameFiles, CodeActionKind, CompletionContext, DiagnosticSeverity,
|
notification::DidRenameFiles, CodeActionKind, CompletionContext, DiagnosticSeverity,
|
||||||
|
@ -4204,14 +4204,8 @@ impl LspStore {
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let completions = task.await?;
|
let completions = task.await?;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
populate_labels_for_completions(
|
populate_labels_for_completions(completions, language, lsp_adapter, &mut result)
|
||||||
completions,
|
.await;
|
||||||
&language_registry,
|
|
||||||
language,
|
|
||||||
lsp_adapter,
|
|
||||||
&mut result,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
})
|
})
|
||||||
} else if let Some(local) = self.as_local() {
|
} else if let Some(local) = self.as_local() {
|
||||||
|
@ -4260,7 +4254,6 @@ impl LspStore {
|
||||||
if let Ok(new_completions) = task.await {
|
if let Ok(new_completions) = task.await {
|
||||||
populate_labels_for_completions(
|
populate_labels_for_completions(
|
||||||
new_completions,
|
new_completions,
|
||||||
&language_registry,
|
|
||||||
language.clone(),
|
language.clone(),
|
||||||
lsp_adapter,
|
lsp_adapter,
|
||||||
&mut completions,
|
&mut completions,
|
||||||
|
@ -4284,7 +4277,6 @@ impl LspStore {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<bool>> {
|
) -> Task<Result<bool>> {
|
||||||
let client = self.upstream_client();
|
let client = self.upstream_client();
|
||||||
let language_registry = self.languages.clone();
|
|
||||||
|
|
||||||
let buffer_id = buffer.read(cx).remote_id();
|
let buffer_id = buffer.read(cx).remote_id();
|
||||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
|
@ -4302,7 +4294,6 @@ impl LspStore {
|
||||||
completions.clone(),
|
completions.clone(),
|
||||||
completion_index,
|
completion_index,
|
||||||
client.clone(),
|
client.clone(),
|
||||||
language_registry.clone(),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.log_err()
|
.log_err()
|
||||||
|
@ -4343,7 +4334,6 @@ impl LspStore {
|
||||||
&buffer_snapshot,
|
&buffer_snapshot,
|
||||||
completions.clone(),
|
completions.clone(),
|
||||||
completion_index,
|
completion_index,
|
||||||
language_registry.clone(),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.log_err();
|
.log_err();
|
||||||
|
@ -4419,22 +4409,14 @@ impl LspStore {
|
||||||
snapshot: &BufferSnapshot,
|
snapshot: &BufferSnapshot,
|
||||||
completions: Rc<RefCell<Box<[Completion]>>>,
|
completions: Rc<RefCell<Box<[Completion]>>>,
|
||||||
completion_index: usize,
|
completion_index: usize,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let completion_item = completions.borrow()[completion_index]
|
let completion_item = completions.borrow()[completion_index]
|
||||||
.lsp_completion
|
.lsp_completion
|
||||||
.clone();
|
.clone();
|
||||||
if let Some(lsp_documentation) = completion_item.documentation.as_ref() {
|
if let Some(lsp_documentation) = completion_item.documentation.clone() {
|
||||||
let documentation = language::prepare_completion_documentation(
|
|
||||||
lsp_documentation,
|
|
||||||
&language_registry,
|
|
||||||
snapshot.language().cloned(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let mut completions = completions.borrow_mut();
|
let mut completions = completions.borrow_mut();
|
||||||
let completion = &mut completions[completion_index];
|
let completion = &mut completions[completion_index];
|
||||||
completion.documentation = Some(documentation);
|
completion.documentation = Some(lsp_documentation.into());
|
||||||
} else {
|
} else {
|
||||||
let mut completions = completions.borrow_mut();
|
let mut completions = completions.borrow_mut();
|
||||||
let completion = &mut completions[completion_index];
|
let completion = &mut completions[completion_index];
|
||||||
|
@ -4487,7 +4469,6 @@ impl LspStore {
|
||||||
completions: Rc<RefCell<Box<[Completion]>>>,
|
completions: Rc<RefCell<Box<[Completion]>>>,
|
||||||
completion_index: usize,
|
completion_index: usize,
|
||||||
client: AnyProtoClient,
|
client: AnyProtoClient,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let lsp_completion = {
|
let lsp_completion = {
|
||||||
let completion = &completions.borrow()[completion_index];
|
let completion = &completions.borrow()[completion_index];
|
||||||
|
@ -4514,14 +4495,11 @@ impl LspStore {
|
||||||
let documentation = if response.documentation.is_empty() {
|
let documentation = if response.documentation.is_empty() {
|
||||||
CompletionDocumentation::Undocumented
|
CompletionDocumentation::Undocumented
|
||||||
} else if response.documentation_is_markdown {
|
} else if response.documentation_is_markdown {
|
||||||
CompletionDocumentation::MultiLineMarkdown(
|
CompletionDocumentation::MultiLineMarkdown(response.documentation.into())
|
||||||
markdown::parse_markdown(&response.documentation, Some(&language_registry), None)
|
|
||||||
.await,
|
|
||||||
)
|
|
||||||
} else if response.documentation.lines().count() <= 1 {
|
} else if response.documentation.lines().count() <= 1 {
|
||||||
CompletionDocumentation::SingleLine(response.documentation)
|
CompletionDocumentation::SingleLine(response.documentation.into())
|
||||||
} else {
|
} else {
|
||||||
CompletionDocumentation::MultiLinePlainText(response.documentation)
|
CompletionDocumentation::MultiLinePlainText(response.documentation.into())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut completions = completions.borrow_mut();
|
let mut completions = completions.borrow_mut();
|
||||||
|
@ -8060,7 +8038,6 @@ fn remove_empty_hover_blocks(mut hover: Hover) -> Option<Hover> {
|
||||||
|
|
||||||
async fn populate_labels_for_completions(
|
async fn populate_labels_for_completions(
|
||||||
mut new_completions: Vec<CoreCompletion>,
|
mut new_completions: Vec<CoreCompletion>,
|
||||||
language_registry: &Arc<LanguageRegistry>,
|
|
||||||
language: Option<Arc<Language>>,
|
language: Option<Arc<Language>>,
|
||||||
lsp_adapter: Option<Arc<CachedLspAdapter>>,
|
lsp_adapter: Option<Arc<CachedLspAdapter>>,
|
||||||
completions: &mut Vec<Completion>,
|
completions: &mut Vec<Completion>,
|
||||||
|
@ -8085,8 +8062,8 @@ async fn populate_labels_for_completions(
|
||||||
.zip(lsp_completions)
|
.zip(lsp_completions)
|
||||||
.zip(labels.into_iter().chain(iter::repeat(None)))
|
.zip(labels.into_iter().chain(iter::repeat(None)))
|
||||||
{
|
{
|
||||||
let documentation = if let Some(docs) = &lsp_completion.documentation {
|
let documentation = if let Some(docs) = lsp_completion.documentation.clone() {
|
||||||
Some(prepare_completion_documentation(docs, language_registry, language.clone()).await)
|
Some(docs.into())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -8477,6 +8454,46 @@ impl DiagnosticSummary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum CompletionDocumentation {
|
||||||
|
/// There is no documentation for this completion.
|
||||||
|
Undocumented,
|
||||||
|
/// A single line of documentation.
|
||||||
|
SingleLine(SharedString),
|
||||||
|
/// Multiple lines of plain text documentation.
|
||||||
|
MultiLinePlainText(SharedString),
|
||||||
|
/// Markdown documentation.
|
||||||
|
MultiLineMarkdown(SharedString),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lsp::Documentation> for CompletionDocumentation {
|
||||||
|
fn from(docs: lsp::Documentation) -> Self {
|
||||||
|
match docs {
|
||||||
|
lsp::Documentation::String(text) => {
|
||||||
|
if text.lines().count() <= 1 {
|
||||||
|
CompletionDocumentation::SingleLine(text.into())
|
||||||
|
} else {
|
||||||
|
CompletionDocumentation::MultiLinePlainText(text.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind {
|
||||||
|
lsp::MarkupKind::PlainText => {
|
||||||
|
if value.lines().count() <= 1 {
|
||||||
|
CompletionDocumentation::SingleLine(value.into())
|
||||||
|
} else {
|
||||||
|
CompletionDocumentation::MultiLinePlainText(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lsp::MarkupKind::Markdown => {
|
||||||
|
CompletionDocumentation::MultiLineMarkdown(value.into())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn glob_literal_prefix(glob: &Path) -> PathBuf {
|
fn glob_literal_prefix(glob: &Path) -> PathBuf {
|
||||||
glob.components()
|
glob.components()
|
||||||
.take_while(|component| match component {
|
.take_while(|component| match component {
|
||||||
|
|
|
@ -58,15 +58,15 @@ use gpui::{
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::InlayHintKind, proto::split_operations, Buffer, BufferEvent, Capability,
|
language_settings::InlayHintKind, proto::split_operations, Buffer, BufferEvent, Capability,
|
||||||
CodeLabel, CompletionDocumentation, File as _, Language, LanguageName, LanguageRegistry,
|
CodeLabel, File as _, Language, LanguageName, LanguageRegistry, PointUtf16, ToOffset,
|
||||||
PointUtf16, ToOffset, ToPointUtf16, Toolchain, ToolchainList, Transaction, Unclipped,
|
ToPointUtf16, Toolchain, ToolchainList, Transaction, Unclipped,
|
||||||
};
|
};
|
||||||
use lsp::{
|
use lsp::{
|
||||||
CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServerId,
|
CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServerId,
|
||||||
LanguageServerName, MessageActionItem,
|
LanguageServerName, MessageActionItem,
|
||||||
};
|
};
|
||||||
use lsp_command::*;
|
use lsp_command::*;
|
||||||
use lsp_store::{LspFormatTarget, OpenLspBufferHandle};
|
use lsp_store::{CompletionDocumentation, LspFormatTarget, OpenLspBufferHandle};
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
pub use prettier_store::PrettierStore;
|
pub use prettier_store::PrettierStore;
|
||||||
|
|
|
@ -208,7 +208,7 @@ impl SshPrompt {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let markdown =
|
let markdown =
|
||||||
cx.new(|cx| Markdown::new_text(prompt, markdown_style, None, None, window, cx));
|
cx.new(|cx| Markdown::new_text(prompt.into(), markdown_style, None, None, window, cx));
|
||||||
self.prompt = Some((markdown, tx));
|
self.prompt = Some((markdown, tx));
|
||||||
self.status_message.take();
|
self.status_message.take();
|
||||||
window.focus(&self.editor.focus_handle(cx));
|
window.focus(&self.editor.focus_handle(cx));
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, FontWeight,
|
div, App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, FontWeight,
|
||||||
InteractiveElement, IntoElement, ParentElement, PromptHandle, PromptLevel, PromptResponse,
|
InteractiveElement, IntoElement, ParentElement, PromptHandle, PromptLevel, PromptResponse,
|
||||||
Refineable, Render, RenderablePromptHandle, Styled, TextStyleRefinement, Window,
|
Refineable, Render, RenderablePromptHandle, SharedString, Styled, TextStyleRefinement, Window,
|
||||||
};
|
};
|
||||||
use markdown::{Markdown, MarkdownStyle};
|
use markdown::{Markdown, MarkdownStyle};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -48,7 +48,14 @@ pub fn fallback_prompt_renderer(
|
||||||
selection_background_color: { cx.theme().players().local().selection },
|
selection_background_color: { cx.theme().players().local().selection },
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
Markdown::new(text.to_string(), markdown_style, None, None, window, cx)
|
Markdown::new(
|
||||||
|
SharedString::new(text),
|
||||||
|
markdown_style,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue