Autocomplete docs (#3126)
Release Notes: - Added documentation display for autocomplete items. - Fixed autocomplete filtering blocking the Zed UI, causing hitches and input delays with large completion lists. - Fixed hover popup link not firing if the mouse moved a slight amount while clicking. - Added support for absolute path file links in hover popup and autocomplete docs.
This commit is contained in:
commit
2323fd17b0
24 changed files with 1237 additions and 345 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2404,7 +2404,6 @@ dependencies = [
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
"postage",
|
"postage",
|
||||||
"project",
|
"project",
|
||||||
"pulldown-cmark",
|
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"rich_text",
|
"rich_text",
|
||||||
"rpc",
|
"rpc",
|
||||||
|
@ -3989,6 +3988,7 @@ dependencies = [
|
||||||
"lsp",
|
"lsp",
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
"postage",
|
"postage",
|
||||||
|
"pulldown-cmark",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
"rpc",
|
"rpc",
|
||||||
|
|
|
@ -50,6 +50,9 @@
|
||||||
// Whether to pop the completions menu while typing in an editor without
|
// Whether to pop the completions menu while typing in an editor without
|
||||||
// explicitly requesting it.
|
// explicitly requesting it.
|
||||||
"show_completions_on_input": true,
|
"show_completions_on_input": true,
|
||||||
|
// Whether to display inline and alongside documentation for items in the
|
||||||
|
// completions menu
|
||||||
|
"show_completion_documentation": true,
|
||||||
// Whether to show wrap guides in the editor. Setting this to true will
|
// Whether to show wrap guides in the editor. Setting this to true will
|
||||||
// show a guide at the 'preferred_line_length' value if softwrap is set to
|
// show a guide at the 'preferred_line_length' value if softwrap is set to
|
||||||
// 'preferred_line_length', and will show any additional guides as specified
|
// 'preferred_line_length', and will show any additional guides as specified
|
||||||
|
|
|
@ -225,6 +225,7 @@ impl Server {
|
||||||
.add_request_handler(forward_project_request::<proto::OpenBufferByPath>)
|
.add_request_handler(forward_project_request::<proto::OpenBufferByPath>)
|
||||||
.add_request_handler(forward_project_request::<proto::GetCompletions>)
|
.add_request_handler(forward_project_request::<proto::GetCompletions>)
|
||||||
.add_request_handler(forward_project_request::<proto::ApplyCompletionAdditionalEdits>)
|
.add_request_handler(forward_project_request::<proto::ApplyCompletionAdditionalEdits>)
|
||||||
|
.add_request_handler(forward_project_request::<proto::ResolveCompletionDocumentation>)
|
||||||
.add_request_handler(forward_project_request::<proto::GetCodeActions>)
|
.add_request_handler(forward_project_request::<proto::GetCodeActions>)
|
||||||
.add_request_handler(forward_project_request::<proto::ApplyCodeAction>)
|
.add_request_handler(forward_project_request::<proto::ApplyCodeAction>)
|
||||||
.add_request_handler(forward_project_request::<proto::PrepareRename>)
|
.add_request_handler(forward_project_request::<proto::PrepareRename>)
|
||||||
|
|
|
@ -57,7 +57,6 @@ log.workspace = true
|
||||||
ordered-float.workspace = true
|
ordered-float.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,6 +7,7 @@ pub struct EditorSettings {
|
||||||
pub cursor_blink: bool,
|
pub cursor_blink: bool,
|
||||||
pub hover_popover_enabled: bool,
|
pub hover_popover_enabled: bool,
|
||||||
pub show_completions_on_input: bool,
|
pub show_completions_on_input: bool,
|
||||||
|
pub show_completion_documentation: bool,
|
||||||
pub use_on_type_format: bool,
|
pub use_on_type_format: bool,
|
||||||
pub scrollbar: Scrollbar,
|
pub scrollbar: Scrollbar,
|
||||||
pub relative_line_numbers: bool,
|
pub relative_line_numbers: bool,
|
||||||
|
@ -33,6 +34,7 @@ pub struct EditorSettingsContent {
|
||||||
pub cursor_blink: Option<bool>,
|
pub cursor_blink: Option<bool>,
|
||||||
pub hover_popover_enabled: Option<bool>,
|
pub hover_popover_enabled: Option<bool>,
|
||||||
pub show_completions_on_input: Option<bool>,
|
pub show_completions_on_input: Option<bool>,
|
||||||
|
pub show_completion_documentation: Option<bool>,
|
||||||
pub use_on_type_format: Option<bool>,
|
pub use_on_type_format: Option<bool>,
|
||||||
pub scrollbar: Option<ScrollbarContent>,
|
pub scrollbar: Option<ScrollbarContent>,
|
||||||
pub relative_line_numbers: Option<bool>,
|
pub relative_line_numbers: Option<bool>,
|
||||||
|
|
|
@ -5430,9 +5430,9 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
|
||||||
additional edit
|
additional edit
|
||||||
"});
|
"});
|
||||||
cx.simulate_keystroke(" ");
|
cx.simulate_keystroke(" ");
|
||||||
assert!(cx.editor(|e, _| e.context_menu.is_none()));
|
assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
|
||||||
cx.simulate_keystroke("s");
|
cx.simulate_keystroke("s");
|
||||||
assert!(cx.editor(|e, _| e.context_menu.is_none()));
|
assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
|
||||||
|
|
||||||
cx.assert_editor_state(indoc! {"
|
cx.assert_editor_state(indoc! {"
|
||||||
one.second_completion
|
one.second_completion
|
||||||
|
@ -5494,12 +5494,12 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
|
||||||
});
|
});
|
||||||
cx.set_state("editorˇ");
|
cx.set_state("editorˇ");
|
||||||
cx.simulate_keystroke(".");
|
cx.simulate_keystroke(".");
|
||||||
assert!(cx.editor(|e, _| e.context_menu.is_none()));
|
assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
|
||||||
cx.simulate_keystroke("c");
|
cx.simulate_keystroke("c");
|
||||||
cx.simulate_keystroke("l");
|
cx.simulate_keystroke("l");
|
||||||
cx.simulate_keystroke("o");
|
cx.simulate_keystroke("o");
|
||||||
cx.assert_editor_state("editor.cloˇ");
|
cx.assert_editor_state("editor.cloˇ");
|
||||||
assert!(cx.editor(|e, _| e.context_menu.is_none()));
|
assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
editor.show_completions(&ShowCompletions, cx);
|
editor.show_completions(&ShowCompletions, cx);
|
||||||
});
|
});
|
||||||
|
@ -7788,7 +7788,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
|
||||||
cx.simulate_keystroke("-");
|
cx.simulate_keystroke("-");
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
cx.update_editor(|editor, _| {
|
cx.update_editor(|editor, _| {
|
||||||
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
|
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||||
&["bg-red", "bg-blue", "bg-yellow"]
|
&["bg-red", "bg-blue", "bg-yellow"]
|
||||||
|
@ -7801,7 +7801,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
|
||||||
cx.simulate_keystroke("l");
|
cx.simulate_keystroke("l");
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
cx.update_editor(|editor, _| {
|
cx.update_editor(|editor, _| {
|
||||||
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
|
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||||
&["bg-blue", "bg-yellow"]
|
&["bg-blue", "bg-yellow"]
|
||||||
|
@ -7817,7 +7817,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
|
||||||
cx.simulate_keystroke("l");
|
cx.simulate_keystroke("l");
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
cx.update_editor(|editor, _| {
|
cx.update_editor(|editor, _| {
|
||||||
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
|
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||||
&["bg-yellow"]
|
&["bg-yellow"]
|
||||||
|
|
|
@ -2428,7 +2428,7 @@ impl Element<Editor> for EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
let active = matches!(
|
let active = matches!(
|
||||||
editor.context_menu,
|
editor.context_menu.read().as_ref(),
|
||||||
Some(crate::ContextMenu::CodeActions(_))
|
Some(crate::ContextMenu::CodeActions(_))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2439,9 +2439,13 @@ impl Element<Editor> for EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
let visible_rows = start_row..start_row + line_layouts.len() as u32;
|
let visible_rows = start_row..start_row + line_layouts.len() as u32;
|
||||||
let mut hover = editor
|
let mut hover = editor.hover_state.render(
|
||||||
.hover_state
|
&snapshot,
|
||||||
.render(&snapshot, &style, visible_rows, cx);
|
&style,
|
||||||
|
visible_rows,
|
||||||
|
editor.workspace.as_ref().map(|(w, _)| w.clone()),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
let mode = editor.mode;
|
let mode = editor.mode;
|
||||||
|
|
||||||
let mut fold_indicators = editor.render_fold_indicators(
|
let mut fold_indicators = editor.render_fold_indicators(
|
||||||
|
|
|
@ -9,13 +9,15 @@ use gpui::{
|
||||||
actions,
|
actions,
|
||||||
elements::{Flex, MouseEventHandler, Padding, ParentElement, Text},
|
elements::{Flex, MouseEventHandler, Padding, ParentElement, Text},
|
||||||
platform::{CursorStyle, MouseButton},
|
platform::{CursorStyle, MouseButton},
|
||||||
AnyElement, AppContext, Element, ModelHandle, Task, ViewContext,
|
AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, WeakViewHandle,
|
||||||
|
};
|
||||||
|
use language::{
|
||||||
|
markdown, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, ParsedMarkdown,
|
||||||
};
|
};
|
||||||
use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry};
|
|
||||||
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
|
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
|
||||||
use rich_text::{new_paragraph, render_code, render_markdown_mut, RichText};
|
|
||||||
use std::{ops::Range, sync::Arc, time::Duration};
|
use std::{ops::Range, sync::Arc, time::Duration};
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub const HOVER_DELAY_MILLIS: u64 = 350;
|
pub const HOVER_DELAY_MILLIS: u64 = 350;
|
||||||
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
|
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
|
||||||
|
@ -105,12 +107,15 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
|
||||||
this.hover_state.diagnostic_popover = None;
|
this.hover_state.diagnostic_popover = None;
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let language_registry = project.update(&mut cx, |p, _| p.languages().clone());
|
||||||
|
let blocks = vec![inlay_hover.tooltip];
|
||||||
|
let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
|
||||||
|
|
||||||
let hover_popover = InfoPopover {
|
let hover_popover = InfoPopover {
|
||||||
project: project.clone(),
|
project: project.clone(),
|
||||||
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
|
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
|
||||||
blocks: vec![inlay_hover.tooltip],
|
blocks,
|
||||||
language: None,
|
parsed_content,
|
||||||
rendered_content: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
|
@ -288,35 +293,38 @@ fn show_hover(
|
||||||
});
|
});
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Construct new hover popover from hover request
|
let hover_result = hover_request.await.ok().flatten();
|
||||||
let hover_popover = hover_request.await.ok().flatten().and_then(|hover_result| {
|
let hover_popover = match hover_result {
|
||||||
if hover_result.is_empty() {
|
Some(hover_result) if !hover_result.is_empty() => {
|
||||||
return None;
|
// Create symbol range of anchors for highlighting and filtering of future requests.
|
||||||
|
let range = if let Some(range) = hover_result.range {
|
||||||
|
let start = snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.anchor_in_excerpt(excerpt_id.clone(), range.start);
|
||||||
|
let end = snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.anchor_in_excerpt(excerpt_id.clone(), range.end);
|
||||||
|
|
||||||
|
start..end
|
||||||
|
} else {
|
||||||
|
anchor..anchor
|
||||||
|
};
|
||||||
|
|
||||||
|
let language_registry = project.update(&mut cx, |p, _| p.languages().clone());
|
||||||
|
let blocks = hover_result.contents;
|
||||||
|
let language = hover_result.language;
|
||||||
|
let parsed_content = parse_blocks(&blocks, &language_registry, language).await;
|
||||||
|
|
||||||
|
Some(InfoPopover {
|
||||||
|
project: project.clone(),
|
||||||
|
symbol_range: RangeInEditor::Text(range),
|
||||||
|
blocks,
|
||||||
|
parsed_content,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create symbol range of anchors for highlighting and filtering
|
_ => None,
|
||||||
// of future requests.
|
};
|
||||||
let range = if let Some(range) = hover_result.range {
|
|
||||||
let start = snapshot
|
|
||||||
.buffer_snapshot
|
|
||||||
.anchor_in_excerpt(excerpt_id.clone(), range.start);
|
|
||||||
let end = snapshot
|
|
||||||
.buffer_snapshot
|
|
||||||
.anchor_in_excerpt(excerpt_id.clone(), range.end);
|
|
||||||
|
|
||||||
start..end
|
|
||||||
} else {
|
|
||||||
anchor..anchor
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(InfoPopover {
|
|
||||||
project: project.clone(),
|
|
||||||
symbol_range: RangeInEditor::Text(range),
|
|
||||||
blocks: hover_result.contents,
|
|
||||||
language: hover_result.language,
|
|
||||||
rendered_content: None,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if let Some(symbol_range) = hover_popover
|
if let Some(symbol_range) = hover_popover
|
||||||
|
@ -345,44 +353,56 @@ fn show_hover(
|
||||||
editor.hover_state.info_task = Some(task);
|
editor.hover_state.info_task = Some(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_blocks(
|
async fn parse_blocks(
|
||||||
blocks: &[HoverBlock],
|
blocks: &[HoverBlock],
|
||||||
language_registry: &Arc<LanguageRegistry>,
|
language_registry: &Arc<LanguageRegistry>,
|
||||||
language: Option<&Arc<Language>>,
|
language: Option<Arc<Language>>,
|
||||||
) -> RichText {
|
) -> markdown::ParsedMarkdown {
|
||||||
let mut data = RichText {
|
let mut text = String::new();
|
||||||
text: Default::default(),
|
let mut highlights = Vec::new();
|
||||||
highlights: Default::default(),
|
let mut region_ranges = Vec::new();
|
||||||
region_ranges: Default::default(),
|
let mut regions = Vec::new();
|
||||||
regions: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for block in blocks {
|
for block in blocks {
|
||||||
match &block.kind {
|
match &block.kind {
|
||||||
HoverBlockKind::PlainText => {
|
HoverBlockKind::PlainText => {
|
||||||
new_paragraph(&mut data.text, &mut Vec::new());
|
markdown::new_paragraph(&mut text, &mut Vec::new());
|
||||||
data.text.push_str(&block.text);
|
text.push_str(&block.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverBlockKind::Markdown => {
|
HoverBlockKind::Markdown => {
|
||||||
render_markdown_mut(&block.text, language_registry, language, &mut data)
|
markdown::parse_markdown_block(
|
||||||
|
&block.text,
|
||||||
|
language_registry,
|
||||||
|
language.clone(),
|
||||||
|
&mut text,
|
||||||
|
&mut highlights,
|
||||||
|
&mut region_ranges,
|
||||||
|
&mut regions,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverBlockKind::Code { language } => {
|
HoverBlockKind::Code { language } => {
|
||||||
if let Some(language) = language_registry
|
if let Some(language) = language_registry
|
||||||
.language_for_name(language)
|
.language_for_name(language)
|
||||||
.now_or_never()
|
.now_or_never()
|
||||||
.and_then(Result::ok)
|
.and_then(Result::ok)
|
||||||
{
|
{
|
||||||
render_code(&mut data.text, &mut data.highlights, &block.text, &language);
|
markdown::highlight_code(&mut text, &mut highlights, &block.text, &language);
|
||||||
} else {
|
} else {
|
||||||
data.text.push_str(&block.text);
|
text.push_str(&block.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.text = data.text.trim().to_string();
|
ParsedMarkdown {
|
||||||
|
text: text.trim().to_string(),
|
||||||
data
|
highlights,
|
||||||
|
region_ranges,
|
||||||
|
regions,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -403,6 +423,7 @@ impl HoverState {
|
||||||
snapshot: &EditorSnapshot,
|
snapshot: &EditorSnapshot,
|
||||||
style: &EditorStyle,
|
style: &EditorStyle,
|
||||||
visible_rows: Range<u32>,
|
visible_rows: Range<u32>,
|
||||||
|
workspace: Option<WeakViewHandle<Workspace>>,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> {
|
) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> {
|
||||||
// If there is a diagnostic, position the popovers based on that.
|
// If there is a diagnostic, position the popovers based on that.
|
||||||
|
@ -432,7 +453,7 @@ impl HoverState {
|
||||||
elements.push(diagnostic_popover.render(style, cx));
|
elements.push(diagnostic_popover.render(style, cx));
|
||||||
}
|
}
|
||||||
if let Some(info_popover) = self.info_popover.as_mut() {
|
if let Some(info_popover) = self.info_popover.as_mut() {
|
||||||
elements.push(info_popover.render(style, cx));
|
elements.push(info_popover.render(style, workspace, cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((point, elements))
|
Some((point, elements))
|
||||||
|
@ -444,32 +465,23 @@ pub struct InfoPopover {
|
||||||
pub project: ModelHandle<Project>,
|
pub project: ModelHandle<Project>,
|
||||||
symbol_range: RangeInEditor,
|
symbol_range: RangeInEditor,
|
||||||
pub blocks: Vec<HoverBlock>,
|
pub blocks: Vec<HoverBlock>,
|
||||||
language: Option<Arc<Language>>,
|
parsed_content: ParsedMarkdown,
|
||||||
rendered_content: Option<RichText>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InfoPopover {
|
impl InfoPopover {
|
||||||
pub fn render(
|
pub fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
style: &EditorStyle,
|
style: &EditorStyle,
|
||||||
|
workspace: Option<WeakViewHandle<Workspace>>,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> AnyElement<Editor> {
|
) -> AnyElement<Editor> {
|
||||||
let rendered_content = self.rendered_content.get_or_insert_with(|| {
|
MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
|
||||||
render_blocks(
|
|
||||||
&self.blocks,
|
|
||||||
self.project.read(cx).languages(),
|
|
||||||
self.language.as_ref(),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
MouseEventHandler::new::<InfoPopover, _>(0, cx, move |_, cx| {
|
|
||||||
let code_span_background_color = style.document_highlight_read_background;
|
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.scrollable::<HoverBlock>(1, None, cx)
|
.scrollable::<HoverBlock>(0, None, cx)
|
||||||
.with_child(rendered_content.element(
|
.with_child(crate::render_parsed_markdown::<HoverBlock>(
|
||||||
style.syntax.clone(),
|
&self.parsed_content,
|
||||||
style.text.clone(),
|
style,
|
||||||
code_span_background_color,
|
workspace,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
.contained()
|
.contained()
|
||||||
|
@ -572,7 +584,6 @@ mod tests {
|
||||||
use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
|
use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
|
||||||
use lsp::LanguageServerId;
|
use lsp::LanguageServerId;
|
||||||
use project::{HoverBlock, HoverBlockKind};
|
use project::{HoverBlock, HoverBlockKind};
|
||||||
use rich_text::Highlight;
|
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
use util::test::marked_text_ranges;
|
use util::test::marked_text_ranges;
|
||||||
|
@ -793,7 +804,7 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
|
||||||
let rendered = render_blocks(&blocks, &Default::default(), None);
|
let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rendered.text,
|
rendered.text,
|
||||||
code_str.trim(),
|
code_str.trim(),
|
||||||
|
@ -900,7 +911,7 @@ mod tests {
|
||||||
// Links
|
// Links
|
||||||
Row {
|
Row {
|
||||||
blocks: vec three".to_string(),
|
text: "one [two](https://the-url) three".to_string(),
|
||||||
kind: HoverBlockKind::Markdown,
|
kind: HoverBlockKind::Markdown,
|
||||||
}],
|
}],
|
||||||
expected_marked_text: "one «two» three".to_string(),
|
expected_marked_text: "one «two» three".to_string(),
|
||||||
|
@ -921,7 +932,7 @@ mod tests {
|
||||||
- a
|
- a
|
||||||
- b
|
- b
|
||||||
* two
|
* two
|
||||||
- [c](the-url)
|
- [c](https://the-url)
|
||||||
- d"
|
- d"
|
||||||
.unindent(),
|
.unindent(),
|
||||||
kind: HoverBlockKind::Markdown,
|
kind: HoverBlockKind::Markdown,
|
||||||
|
@ -985,7 +996,7 @@ mod tests {
|
||||||
expected_styles,
|
expected_styles,
|
||||||
} in &rows[0..]
|
} in &rows[0..]
|
||||||
{
|
{
|
||||||
let rendered = render_blocks(&blocks, &Default::default(), None);
|
let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
|
||||||
|
|
||||||
let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
|
let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
|
||||||
let expected_highlights = ranges
|
let expected_highlights = ranges
|
||||||
|
@ -1001,11 +1012,8 @@ mod tests {
|
||||||
.highlights
|
.highlights
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(range, highlight)| {
|
.filter_map(|(range, highlight)| {
|
||||||
let style = match highlight {
|
let highlight = highlight.to_highlight_style(&style.syntax)?;
|
||||||
Highlight::Id(id) => id.style(&style.syntax)?,
|
Some((range.clone(), highlight))
|
||||||
Highlight::Highlight(style) => style.clone(),
|
|
||||||
};
|
|
||||||
Some((range.clone(), style))
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -1258,11 +1266,7 @@ mod tests {
|
||||||
"Popover range should match the new type label part"
|
"Popover range should match the new type label part"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
popover
|
popover.parsed_content.text,
|
||||||
.rendered_content
|
|
||||||
.as_ref()
|
|
||||||
.expect("should have label text for new type hint")
|
|
||||||
.text,
|
|
||||||
format!("A tooltip for `{new_type_label}`"),
|
format!("A tooltip for `{new_type_label}`"),
|
||||||
"Rendered text should not anyhow alter backticks"
|
"Rendered text should not anyhow alter backticks"
|
||||||
);
|
);
|
||||||
|
@ -1316,11 +1320,7 @@ mod tests {
|
||||||
"Popover range should match the struct label part"
|
"Popover range should match the struct label part"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
popover
|
popover.parsed_content.text,
|
||||||
.rendered_content
|
|
||||||
.as_ref()
|
|
||||||
.expect("should have label text for struct hint")
|
|
||||||
.text,
|
|
||||||
format!("A tooltip for {struct_label}"),
|
format!("A tooltip for {struct_label}"),
|
||||||
"Rendered markdown element should remove backticks from text"
|
"Rendered markdown element should remove backticks from text"
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
json::{self, ToJson, Value},
|
json::{self, ToJson, Value},
|
||||||
AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, Vector2FExt, ViewContext,
|
AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, TypeTag, Vector2FExt,
|
||||||
|
ViewContext,
|
||||||
};
|
};
|
||||||
use pathfinder_geometry::{
|
use pathfinder_geometry::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
|
@ -10,10 +11,10 @@ use pathfinder_geometry::{
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct ScrollState {
|
struct ScrollState {
|
||||||
scroll_to: Cell<Option<usize>>,
|
scroll_to: Cell<Option<usize>>,
|
||||||
scroll_position: Cell<f32>,
|
scroll_position: Cell<f32>,
|
||||||
|
type_tag: TypeTag,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Flex<V> {
|
pub struct Flex<V> {
|
||||||
|
@ -66,8 +67,14 @@ impl<V: 'static> Flex<V> {
|
||||||
where
|
where
|
||||||
Tag: 'static,
|
Tag: 'static,
|
||||||
{
|
{
|
||||||
let scroll_state = cx.default_element_state::<Tag, Rc<ScrollState>>(element_id);
|
let scroll_state = cx.element_state::<Tag, Rc<ScrollState>>(
|
||||||
scroll_state.read(cx).scroll_to.set(scroll_to);
|
element_id,
|
||||||
|
Rc::new(ScrollState {
|
||||||
|
scroll_to: Cell::new(scroll_to),
|
||||||
|
scroll_position: Default::default(),
|
||||||
|
type_tag: TypeTag::new::<Tag>(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
self.scroll_state = Some((scroll_state, cx.handle().id()));
|
self.scroll_state = Some((scroll_state, cx.handle().id()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -276,38 +283,44 @@ impl<V: 'static> Element<V> for Flex<V> {
|
||||||
if let Some((scroll_state, id)) = &self.scroll_state {
|
if let Some((scroll_state, id)) = &self.scroll_state {
|
||||||
let scroll_state = scroll_state.read(cx).clone();
|
let scroll_state = scroll_state.read(cx).clone();
|
||||||
cx.scene().push_mouse_region(
|
cx.scene().push_mouse_region(
|
||||||
crate::MouseRegion::new::<Self>(*id, 0, bounds)
|
crate::MouseRegion::from_handlers(
|
||||||
.on_scroll({
|
scroll_state.type_tag,
|
||||||
let axis = self.axis;
|
*id,
|
||||||
move |e, _: &mut V, cx| {
|
0,
|
||||||
if remaining_space < 0. {
|
bounds,
|
||||||
let scroll_delta = e.delta.raw();
|
Default::default(),
|
||||||
|
)
|
||||||
|
.on_scroll({
|
||||||
|
let axis = self.axis;
|
||||||
|
move |e, _: &mut V, cx| {
|
||||||
|
if remaining_space < 0. {
|
||||||
|
let scroll_delta = e.delta.raw();
|
||||||
|
|
||||||
let mut delta = match axis {
|
let mut delta = match axis {
|
||||||
Axis::Horizontal => {
|
Axis::Horizontal => {
|
||||||
if scroll_delta.x().abs() >= scroll_delta.y().abs() {
|
if scroll_delta.x().abs() >= scroll_delta.y().abs() {
|
||||||
scroll_delta.x()
|
scroll_delta.x()
|
||||||
} else {
|
} else {
|
||||||
scroll_delta.y()
|
scroll_delta.y()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Axis::Vertical => scroll_delta.y(),
|
|
||||||
};
|
|
||||||
if !e.delta.precise() {
|
|
||||||
delta *= 20.;
|
|
||||||
}
|
}
|
||||||
|
Axis::Vertical => scroll_delta.y(),
|
||||||
scroll_state
|
};
|
||||||
.scroll_position
|
if !e.delta.precise() {
|
||||||
.set(scroll_state.scroll_position.get() - delta);
|
delta *= 20.;
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
} else {
|
|
||||||
cx.propagate_event();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scroll_state
|
||||||
|
.scroll_position
|
||||||
|
.set(scroll_state.scroll_position.get() - delta);
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
} else {
|
||||||
|
cx.propagate_event();
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.on_move(|_, _: &mut V, _| { /* Capture move events */ }),
|
})
|
||||||
|
.on_move(|_, _: &mut V, _| { /* Capture move events */ }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ lazy_static.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
|
pulldown-cmark = { version = "0.9.2", default-features = false }
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
diagnostic_set::DiagnosticSet,
|
diagnostic_set::DiagnosticSet,
|
||||||
highlight_map::{HighlightId, HighlightMap},
|
highlight_map::{HighlightId, HighlightMap},
|
||||||
|
markdown::ParsedMarkdown,
|
||||||
proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT,
|
proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT,
|
||||||
};
|
};
|
||||||
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::{
|
||||||
SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
|
SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
|
||||||
|
@ -143,11 +145,51 @@ pub struct Diagnostic {
|
||||||
pub is_unnecessary: bool,
|
pub is_unnecessary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn prepare_completion_documentation(
|
||||||
|
documentation: &lsp::Documentation,
|
||||||
|
language_registry: &Arc<LanguageRegistry>,
|
||||||
|
language: Option<Arc<Language>>,
|
||||||
|
) -> Documentation {
|
||||||
|
match documentation {
|
||||||
|
lsp::Documentation::String(text) => {
|
||||||
|
if text.lines().count() <= 1 {
|
||||||
|
Documentation::SingleLine(text.clone())
|
||||||
|
} else {
|
||||||
|
Documentation::MultiLinePlainText(text.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind {
|
||||||
|
lsp::MarkupKind::PlainText => {
|
||||||
|
if value.lines().count() <= 1 {
|
||||||
|
Documentation::SingleLine(value.clone())
|
||||||
|
} else {
|
||||||
|
Documentation::MultiLinePlainText(value.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lsp::MarkupKind::Markdown => {
|
||||||
|
let parsed = parse_markdown(value, language_registry, language).await;
|
||||||
|
Documentation::MultiLineMarkdown(parsed)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Documentation {
|
||||||
|
Undocumented,
|
||||||
|
SingleLine(String),
|
||||||
|
MultiLinePlainText(String),
|
||||||
|
MultiLineMarkdown(ParsedMarkdown),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Completion {
|
pub struct Completion {
|
||||||
pub old_range: Range<Anchor>,
|
pub old_range: Range<Anchor>,
|
||||||
pub new_text: String,
|
pub new_text: String,
|
||||||
pub label: CodeLabel,
|
pub label: CodeLabel,
|
||||||
|
pub documentation: Option<Documentation>,
|
||||||
pub server_id: LanguageServerId,
|
pub server_id: LanguageServerId,
|
||||||
pub lsp_completion: lsp::CompletionItem,
|
pub lsp_completion: lsp::CompletionItem,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ mod buffer;
|
||||||
mod diagnostic_set;
|
mod diagnostic_set;
|
||||||
mod highlight_map;
|
mod highlight_map;
|
||||||
pub mod language_settings;
|
pub mod language_settings;
|
||||||
|
pub mod markdown;
|
||||||
mod outline;
|
mod outline;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
mod syntax_map;
|
mod syntax_map;
|
||||||
|
|
301
crates/language/src/markdown.rs
Normal file
301
crates/language/src/markdown.rs
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{ops::Range, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::{HighlightId, Language, LanguageRegistry};
|
||||||
|
use gpui::fonts::{self, HighlightStyle, Weight};
|
||||||
|
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ParsedMarkdown {
|
||||||
|
pub text: String,
|
||||||
|
pub highlights: Vec<(Range<usize>, MarkdownHighlight)>,
|
||||||
|
pub region_ranges: Vec<Range<usize>>,
|
||||||
|
pub regions: Vec<ParsedRegion>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum MarkdownHighlight {
|
||||||
|
Style(MarkdownHighlightStyle),
|
||||||
|
Code(HighlightId),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MarkdownHighlight {
|
||||||
|
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.italic = Some(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if style.underline {
|
||||||
|
highlight.underline = Some(fonts::Underline {
|
||||||
|
thickness: 1.0.into(),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if style.weight != fonts::Weight::default() {
|
||||||
|
highlight.weight = Some(style.weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(highlight)
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkdownHighlight::Code(id) => id.style(theme),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||||
|
pub struct MarkdownHighlightStyle {
|
||||||
|
pub italic: bool,
|
||||||
|
pub underline: bool,
|
||||||
|
pub weight: Weight,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ParsedRegion {
|
||||||
|
pub code: bool,
|
||||||
|
pub link: Option<Link>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Link {
|
||||||
|
Web { url: String },
|
||||||
|
Path { 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn parse_markdown(
|
||||||
|
markdown: &str,
|
||||||
|
language_registry: &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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn parse_markdown_block(
|
||||||
|
markdown: &str,
|
||||||
|
language_registry: &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 link_url = None;
|
||||||
|
let mut current_language = None;
|
||||||
|
let mut list_stack = Vec::new();
|
||||||
|
|
||||||
|
for event in Parser::new_ext(&markdown, Options::all()) {
|
||||||
|
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 = Weight::BOLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
if italic_depth > 0 {
|
||||||
|
style.italic = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(link) = link_url.clone().and_then(|u| Link::identify(u)) {
|
||||||
|
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(|u| Link::identify(u));
|
||||||
|
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 {
|
||||||
|
language_registry
|
||||||
|
.language_for_name(language.as_ref())
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
} else {
|
||||||
|
language.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag::Emphasis => italic_depth += 1,
|
||||||
|
|
||||||
|
Tag::Strong => bold_depth += 1,
|
||||||
|
|
||||||
|
Tag::Link(_, url, _) => link_url = Some(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 {
|
||||||
|
Tag::Heading(_, _, _) => bold_depth -= 1,
|
||||||
|
Tag::CodeBlock(_) => current_language = None,
|
||||||
|
Tag::Emphasis => italic_depth -= 1,
|
||||||
|
Tag::Strong => bold_depth -= 1,
|
||||||
|
Tag::Link(_, _, _) => link_url = None,
|
||||||
|
Tag::List(_) => drop(list_stack.pop()),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
|
||||||
|
Event::HardBreak => text.push('\n'),
|
||||||
|
|
||||||
|
Event::SoftBreak => text.push(' '),
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(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(" ");
|
||||||
|
}
|
||||||
|
}
|
|
@ -482,6 +482,7 @@ pub async fn deserialize_completion(
|
||||||
lsp_completion.filter_text.as_deref(),
|
lsp_completion.filter_text.as_deref(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
documentation: None,
|
||||||
server_id: LanguageServerId(completion.server_id as usize),
|
server_id: LanguageServerId(completion.server_id as usize),
|
||||||
lsp_completion,
|
lsp_completion,
|
||||||
})
|
})
|
||||||
|
|
|
@ -466,7 +466,10 @@ impl LanguageServer {
|
||||||
completion_item: Some(CompletionItemCapability {
|
completion_item: Some(CompletionItemCapability {
|
||||||
snippet_support: Some(true),
|
snippet_support: Some(true),
|
||||||
resolve_support: Some(CompletionItemCapabilityResolveSupport {
|
resolve_support: Some(CompletionItemCapabilityResolveSupport {
|
||||||
properties: vec!["additionalTextEdits".to_string()],
|
properties: vec![
|
||||||
|
"documentation".to_string(),
|
||||||
|
"additionalTextEdits".to_string(),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
|
@ -748,6 +751,15 @@ impl LanguageServer {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// some child of string literal (be it "" or ``) which is the child of an attribute
|
||||||
|
|
||||||
|
// <Foo className="bar" />
|
||||||
|
// <Foo className={`bar`} />
|
||||||
|
// <Foo className={something + "bar"} />
|
||||||
|
// <Foo className={something + "bar"} />
|
||||||
|
// const classes = "awesome ";
|
||||||
|
// <Foo className={classes} />
|
||||||
|
|
||||||
fn request_internal<T: request::Request>(
|
fn request_internal<T: request::Request>(
|
||||||
next_id: &AtomicUsize,
|
next_id: &AtomicUsize,
|
||||||
response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
|
response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
|
||||||
|
|
|
@ -10,7 +10,7 @@ use futures::future;
|
||||||
use gpui::{AppContext, AsyncAppContext, ModelHandle};
|
use gpui::{AppContext, AsyncAppContext, ModelHandle};
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::{language_settings, InlayHintKind},
|
language_settings::{language_settings, InlayHintKind},
|
||||||
point_from_lsp, point_to_lsp,
|
point_from_lsp, point_to_lsp, prepare_completion_documentation,
|
||||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||||
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
|
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
|
||||||
CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
|
CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
|
||||||
|
@ -1341,7 +1341,7 @@ impl LspCommand for GetCompletions {
|
||||||
async fn response_from_lsp(
|
async fn response_from_lsp(
|
||||||
self,
|
self,
|
||||||
completions: Option<lsp::CompletionResponse>,
|
completions: Option<lsp::CompletionResponse>,
|
||||||
_: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
|
@ -1358,10 +1358,11 @@ impl LspCommand for GetCompletions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let completions = buffer.read_with(&cx, |buffer, _| {
|
let completions = buffer.read_with(&cx, |buffer, cx| {
|
||||||
|
let language_registry = project.read(cx).languages().clone();
|
||||||
let language = buffer.language().cloned();
|
let language = buffer.language().cloned();
|
||||||
let snapshot = buffer.snapshot();
|
let snapshot = buffer.snapshot();
|
||||||
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
|
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
|
||||||
|
@ -1370,6 +1371,14 @@ impl LspCommand for GetCompletions {
|
||||||
completions
|
completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(move |mut lsp_completion| {
|
.filter_map(move |mut lsp_completion| {
|
||||||
|
if let Some(response_list) = &response_list {
|
||||||
|
if let Some(item_defaults) = &response_list.item_defaults {
|
||||||
|
if let Some(data) = &item_defaults.data {
|
||||||
|
lsp_completion.data = Some(data.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() {
|
let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() {
|
||||||
// If the language server provides a range to overwrite, then
|
// If the language server provides a range to overwrite, then
|
||||||
// check that the range is valid.
|
// check that the range is valid.
|
||||||
|
@ -1445,14 +1454,30 @@ impl LspCommand for GetCompletions {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let language = language.clone();
|
|
||||||
LineEnding::normalize(&mut new_text);
|
LineEnding::normalize(&mut new_text);
|
||||||
|
let language_registry = language_registry.clone();
|
||||||
|
let language = language.clone();
|
||||||
|
|
||||||
Some(async move {
|
Some(async move {
|
||||||
let mut label = None;
|
let mut label = None;
|
||||||
if let Some(language) = language {
|
if let Some(language) = language.as_ref() {
|
||||||
language.process_completion(&mut lsp_completion).await;
|
language.process_completion(&mut lsp_completion).await;
|
||||||
label = language.label_for_completion(&lsp_completion).await;
|
label = language.label_for_completion(&lsp_completion).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let documentation = if let Some(lsp_docs) = &lsp_completion.documentation {
|
||||||
|
Some(
|
||||||
|
prepare_completion_documentation(
|
||||||
|
lsp_docs,
|
||||||
|
&language_registry,
|
||||||
|
language.clone(),
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Completion {
|
Completion {
|
||||||
old_range,
|
old_range,
|
||||||
new_text,
|
new_text,
|
||||||
|
@ -1462,6 +1487,7 @@ impl LspCommand for GetCompletions {
|
||||||
lsp_completion.filter_text.as_deref(),
|
lsp_completion.filter_text.as_deref(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
documentation,
|
||||||
server_id,
|
server_id,
|
||||||
lsp_completion,
|
lsp_completion,
|
||||||
}
|
}
|
||||||
|
|
|
@ -592,6 +592,7 @@ impl Project {
|
||||||
client.add_model_request_handler(Self::handle_apply_code_action);
|
client.add_model_request_handler(Self::handle_apply_code_action);
|
||||||
client.add_model_request_handler(Self::handle_on_type_formatting);
|
client.add_model_request_handler(Self::handle_on_type_formatting);
|
||||||
client.add_model_request_handler(Self::handle_inlay_hints);
|
client.add_model_request_handler(Self::handle_inlay_hints);
|
||||||
|
client.add_model_request_handler(Self::handle_resolve_completion_documentation);
|
||||||
client.add_model_request_handler(Self::handle_resolve_inlay_hint);
|
client.add_model_request_handler(Self::handle_resolve_inlay_hint);
|
||||||
client.add_model_request_handler(Self::handle_refresh_inlay_hints);
|
client.add_model_request_handler(Self::handle_refresh_inlay_hints);
|
||||||
client.add_model_request_handler(Self::handle_reload_buffers);
|
client.add_model_request_handler(Self::handle_reload_buffers);
|
||||||
|
@ -7352,6 +7353,40 @@ impl Project {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_resolve_completion_documentation(
|
||||||
|
this: ModelHandle<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::ResolveCompletionDocumentation>,
|
||||||
|
_: Arc<Client>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<proto::ResolveCompletionDocumentationResponse> {
|
||||||
|
let lsp_completion = serde_json::from_slice(&envelope.payload.lsp_completion)?;
|
||||||
|
|
||||||
|
let completion = this
|
||||||
|
.read_with(&mut cx, |this, _| {
|
||||||
|
let id = LanguageServerId(envelope.payload.language_server_id as usize);
|
||||||
|
let Some(server) = this.language_server_for_id(id) else {
|
||||||
|
return Err(anyhow!("No language server {id}"));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(server.request::<lsp::request::ResolveCompletionItem>(lsp_completion))
|
||||||
|
})?
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut is_markdown = false;
|
||||||
|
let text = match completion.documentation {
|
||||||
|
Some(lsp::Documentation::String(text)) => text,
|
||||||
|
|
||||||
|
Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value })) => {
|
||||||
|
is_markdown = kind == lsp::MarkupKind::Markdown;
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(proto::ResolveCompletionDocumentationResponse { text, is_markdown })
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_apply_code_action(
|
async fn handle_apply_code_action(
|
||||||
this: ModelHandle<Self>,
|
this: ModelHandle<Self>,
|
||||||
envelope: TypedEnvelope<proto::ApplyCodeAction>,
|
envelope: TypedEnvelope<proto::ApplyCodeAction>,
|
||||||
|
|
|
@ -89,88 +89,90 @@ message Envelope {
|
||||||
FormatBuffersResponse format_buffers_response = 70;
|
FormatBuffersResponse format_buffers_response = 70;
|
||||||
GetCompletions get_completions = 71;
|
GetCompletions get_completions = 71;
|
||||||
GetCompletionsResponse get_completions_response = 72;
|
GetCompletionsResponse get_completions_response = 72;
|
||||||
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 73;
|
ResolveCompletionDocumentation resolve_completion_documentation = 73;
|
||||||
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 74;
|
ResolveCompletionDocumentationResponse resolve_completion_documentation_response = 74;
|
||||||
GetCodeActions get_code_actions = 75;
|
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 75;
|
||||||
GetCodeActionsResponse get_code_actions_response = 76;
|
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 76;
|
||||||
GetHover get_hover = 77;
|
GetCodeActions get_code_actions = 77;
|
||||||
GetHoverResponse get_hover_response = 78;
|
GetCodeActionsResponse get_code_actions_response = 78;
|
||||||
ApplyCodeAction apply_code_action = 79;
|
GetHover get_hover = 79;
|
||||||
ApplyCodeActionResponse apply_code_action_response = 80;
|
GetHoverResponse get_hover_response = 80;
|
||||||
PrepareRename prepare_rename = 81;
|
ApplyCodeAction apply_code_action = 81;
|
||||||
PrepareRenameResponse prepare_rename_response = 82;
|
ApplyCodeActionResponse apply_code_action_response = 82;
|
||||||
PerformRename perform_rename = 83;
|
PrepareRename prepare_rename = 83;
|
||||||
PerformRenameResponse perform_rename_response = 84;
|
PrepareRenameResponse prepare_rename_response = 84;
|
||||||
SearchProject search_project = 85;
|
PerformRename perform_rename = 85;
|
||||||
SearchProjectResponse search_project_response = 86;
|
PerformRenameResponse perform_rename_response = 86;
|
||||||
|
SearchProject search_project = 87;
|
||||||
|
SearchProjectResponse search_project_response = 88;
|
||||||
|
|
||||||
UpdateContacts update_contacts = 87;
|
UpdateContacts update_contacts = 89;
|
||||||
UpdateInviteInfo update_invite_info = 88;
|
UpdateInviteInfo update_invite_info = 90;
|
||||||
ShowContacts show_contacts = 89;
|
ShowContacts show_contacts = 91;
|
||||||
|
|
||||||
GetUsers get_users = 90;
|
GetUsers get_users = 92;
|
||||||
FuzzySearchUsers fuzzy_search_users = 91;
|
FuzzySearchUsers fuzzy_search_users = 93;
|
||||||
UsersResponse users_response = 92;
|
UsersResponse users_response = 94;
|
||||||
RequestContact request_contact = 93;
|
RequestContact request_contact = 95;
|
||||||
RespondToContactRequest respond_to_contact_request = 94;
|
RespondToContactRequest respond_to_contact_request = 96;
|
||||||
RemoveContact remove_contact = 95;
|
RemoveContact remove_contact = 97;
|
||||||
|
|
||||||
Follow follow = 96;
|
Follow follow = 98;
|
||||||
FollowResponse follow_response = 97;
|
FollowResponse follow_response = 99;
|
||||||
UpdateFollowers update_followers = 98;
|
UpdateFollowers update_followers = 100;
|
||||||
Unfollow unfollow = 99;
|
Unfollow unfollow = 101;
|
||||||
GetPrivateUserInfo get_private_user_info = 100;
|
GetPrivateUserInfo get_private_user_info = 102;
|
||||||
GetPrivateUserInfoResponse get_private_user_info_response = 101;
|
GetPrivateUserInfoResponse get_private_user_info_response = 103;
|
||||||
UpdateDiffBase update_diff_base = 102;
|
UpdateDiffBase update_diff_base = 104;
|
||||||
|
|
||||||
OnTypeFormatting on_type_formatting = 103;
|
OnTypeFormatting on_type_formatting = 105;
|
||||||
OnTypeFormattingResponse on_type_formatting_response = 104;
|
OnTypeFormattingResponse on_type_formatting_response = 106;
|
||||||
|
|
||||||
UpdateWorktreeSettings update_worktree_settings = 105;
|
UpdateWorktreeSettings update_worktree_settings = 107;
|
||||||
|
|
||||||
InlayHints inlay_hints = 106;
|
InlayHints inlay_hints = 108;
|
||||||
InlayHintsResponse inlay_hints_response = 107;
|
InlayHintsResponse inlay_hints_response = 109;
|
||||||
ResolveInlayHint resolve_inlay_hint = 108;
|
ResolveInlayHint resolve_inlay_hint = 110;
|
||||||
ResolveInlayHintResponse resolve_inlay_hint_response = 109;
|
ResolveInlayHintResponse resolve_inlay_hint_response = 111;
|
||||||
RefreshInlayHints refresh_inlay_hints = 110;
|
RefreshInlayHints refresh_inlay_hints = 112;
|
||||||
|
|
||||||
CreateChannel create_channel = 111;
|
CreateChannel create_channel = 113;
|
||||||
CreateChannelResponse create_channel_response = 112;
|
CreateChannelResponse create_channel_response = 114;
|
||||||
InviteChannelMember invite_channel_member = 113;
|
InviteChannelMember invite_channel_member = 115;
|
||||||
RemoveChannelMember remove_channel_member = 114;
|
RemoveChannelMember remove_channel_member = 116;
|
||||||
RespondToChannelInvite respond_to_channel_invite = 115;
|
RespondToChannelInvite respond_to_channel_invite = 117;
|
||||||
UpdateChannels update_channels = 116;
|
UpdateChannels update_channels = 118;
|
||||||
JoinChannel join_channel = 117;
|
JoinChannel join_channel = 119;
|
||||||
DeleteChannel delete_channel = 118;
|
DeleteChannel delete_channel = 120;
|
||||||
GetChannelMembers get_channel_members = 119;
|
GetChannelMembers get_channel_members = 121;
|
||||||
GetChannelMembersResponse get_channel_members_response = 120;
|
GetChannelMembersResponse get_channel_members_response = 122;
|
||||||
SetChannelMemberAdmin set_channel_member_admin = 121;
|
SetChannelMemberAdmin set_channel_member_admin = 123;
|
||||||
RenameChannel rename_channel = 122;
|
RenameChannel rename_channel = 124;
|
||||||
RenameChannelResponse rename_channel_response = 123;
|
RenameChannelResponse rename_channel_response = 125;
|
||||||
|
|
||||||
JoinChannelBuffer join_channel_buffer = 124;
|
JoinChannelBuffer join_channel_buffer = 126;
|
||||||
JoinChannelBufferResponse join_channel_buffer_response = 125;
|
JoinChannelBufferResponse join_channel_buffer_response = 127;
|
||||||
UpdateChannelBuffer update_channel_buffer = 126;
|
UpdateChannelBuffer update_channel_buffer = 128;
|
||||||
LeaveChannelBuffer leave_channel_buffer = 127;
|
LeaveChannelBuffer leave_channel_buffer = 129;
|
||||||
UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 128;
|
UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 130;
|
||||||
RejoinChannelBuffers rejoin_channel_buffers = 129;
|
RejoinChannelBuffers rejoin_channel_buffers = 131;
|
||||||
RejoinChannelBuffersResponse rejoin_channel_buffers_response = 130;
|
RejoinChannelBuffersResponse rejoin_channel_buffers_response = 132;
|
||||||
AckBufferOperation ack_buffer_operation = 143;
|
AckBufferOperation ack_buffer_operation = 145;
|
||||||
|
|
||||||
JoinChannelChat join_channel_chat = 131;
|
JoinChannelChat join_channel_chat = 133;
|
||||||
JoinChannelChatResponse join_channel_chat_response = 132;
|
JoinChannelChatResponse join_channel_chat_response = 134;
|
||||||
LeaveChannelChat leave_channel_chat = 133;
|
LeaveChannelChat leave_channel_chat = 135;
|
||||||
SendChannelMessage send_channel_message = 134;
|
SendChannelMessage send_channel_message = 136;
|
||||||
SendChannelMessageResponse send_channel_message_response = 135;
|
SendChannelMessageResponse send_channel_message_response = 137;
|
||||||
ChannelMessageSent channel_message_sent = 136;
|
ChannelMessageSent channel_message_sent = 138;
|
||||||
GetChannelMessages get_channel_messages = 137;
|
GetChannelMessages get_channel_messages = 139;
|
||||||
GetChannelMessagesResponse get_channel_messages_response = 138;
|
GetChannelMessagesResponse get_channel_messages_response = 140;
|
||||||
RemoveChannelMessage remove_channel_message = 139;
|
RemoveChannelMessage remove_channel_message = 141;
|
||||||
AckChannelMessage ack_channel_message = 144;
|
AckChannelMessage ack_channel_message = 146;
|
||||||
|
|
||||||
LinkChannel link_channel = 140;
|
LinkChannel link_channel = 142;
|
||||||
UnlinkChannel unlink_channel = 141;
|
UnlinkChannel unlink_channel = 143;
|
||||||
MoveChannel move_channel = 142; // current max: 144
|
MoveChannel move_channel = 144; // current max: 146
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -832,6 +834,17 @@ message ResolveState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ResolveCompletionDocumentation {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 language_server_id = 2;
|
||||||
|
bytes lsp_completion = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResolveCompletionDocumentationResponse {
|
||||||
|
string text = 1;
|
||||||
|
bool is_markdown = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message ResolveInlayHint {
|
message ResolveInlayHint {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 buffer_id = 2;
|
uint64 buffer_id = 2;
|
||||||
|
|
|
@ -205,6 +205,8 @@ messages!(
|
||||||
(OnTypeFormattingResponse, Background),
|
(OnTypeFormattingResponse, Background),
|
||||||
(InlayHints, Background),
|
(InlayHints, Background),
|
||||||
(InlayHintsResponse, Background),
|
(InlayHintsResponse, Background),
|
||||||
|
(ResolveCompletionDocumentation, Background),
|
||||||
|
(ResolveCompletionDocumentationResponse, Background),
|
||||||
(ResolveInlayHint, Background),
|
(ResolveInlayHint, Background),
|
||||||
(ResolveInlayHintResponse, Background),
|
(ResolveInlayHintResponse, Background),
|
||||||
(RefreshInlayHints, Foreground),
|
(RefreshInlayHints, Foreground),
|
||||||
|
@ -318,6 +320,10 @@ request_messages!(
|
||||||
(PrepareRename, PrepareRenameResponse),
|
(PrepareRename, PrepareRenameResponse),
|
||||||
(OnTypeFormatting, OnTypeFormattingResponse),
|
(OnTypeFormatting, OnTypeFormattingResponse),
|
||||||
(InlayHints, InlayHintsResponse),
|
(InlayHints, InlayHintsResponse),
|
||||||
|
(
|
||||||
|
ResolveCompletionDocumentation,
|
||||||
|
ResolveCompletionDocumentationResponse
|
||||||
|
),
|
||||||
(ResolveInlayHint, ResolveInlayHintResponse),
|
(ResolveInlayHint, ResolveInlayHintResponse),
|
||||||
(RefreshInlayHints, Ack),
|
(RefreshInlayHints, Ack),
|
||||||
(ReloadBuffers, ReloadBuffersResponse),
|
(ReloadBuffers, ReloadBuffersResponse),
|
||||||
|
@ -381,6 +387,7 @@ entity_messages!(
|
||||||
PerformRename,
|
PerformRename,
|
||||||
OnTypeFormatting,
|
OnTypeFormatting,
|
||||||
InlayHints,
|
InlayHints,
|
||||||
|
ResolveCompletionDocumentation,
|
||||||
ResolveInlayHint,
|
ResolveInlayHint,
|
||||||
RefreshInlayHints,
|
RefreshInlayHints,
|
||||||
PrepareRename,
|
PrepareRename,
|
||||||
|
|
|
@ -6,4 +6,4 @@ pub use conn::Connection;
|
||||||
pub use peer::*;
|
pub use peer::*;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub const PROTOCOL_VERSION: u32 = 64;
|
pub const PROTOCOL_VERSION: u32 = 65;
|
||||||
|
|
|
@ -150,11 +150,14 @@ impl TerminalView {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
cx.emit(Event::Wakeup);
|
cx.emit(Event::Wakeup);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Bell => {
|
Event::Bell => {
|
||||||
this.has_bell = true;
|
this.has_bell = true;
|
||||||
cx.emit(Event::Wakeup);
|
cx.emit(Event::Wakeup);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::BlinkChanged => this.blinking_on = !this.blinking_on,
|
Event::BlinkChanged => this.blinking_on = !this.blinking_on,
|
||||||
|
|
||||||
Event::TitleChanged => {
|
Event::TitleChanged => {
|
||||||
if let Some(foreground_info) = &this.terminal().read(cx).foreground_process_info {
|
if let Some(foreground_info) = &this.terminal().read(cx).foreground_process_info {
|
||||||
let cwd = foreground_info.cwd.clone();
|
let cwd = foreground_info.cwd.clone();
|
||||||
|
@ -171,6 +174,7 @@ impl TerminalView {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::NewNavigationTarget(maybe_navigation_target) => {
|
Event::NewNavigationTarget(maybe_navigation_target) => {
|
||||||
this.can_navigate_to_selected_word = match maybe_navigation_target {
|
this.can_navigate_to_selected_word = match maybe_navigation_target {
|
||||||
Some(MaybeNavigationTarget::Url(_)) => true,
|
Some(MaybeNavigationTarget::Url(_)) => true,
|
||||||
|
@ -180,8 +184,10 @@ impl TerminalView {
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Open(maybe_navigation_target) => match maybe_navigation_target {
|
Event::Open(maybe_navigation_target) => match maybe_navigation_target {
|
||||||
MaybeNavigationTarget::Url(url) => cx.platform().open_url(url),
|
MaybeNavigationTarget::Url(url) => cx.platform().open_url(url),
|
||||||
|
|
||||||
MaybeNavigationTarget::PathLike(maybe_path) => {
|
MaybeNavigationTarget::PathLike(maybe_path) => {
|
||||||
if !this.can_navigate_to_selected_word {
|
if !this.can_navigate_to_selected_word {
|
||||||
return;
|
return;
|
||||||
|
@ -246,6 +252,7 @@ impl TerminalView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => cx.emit(event.clone()),
|
_ => cx.emit(event.clone()),
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
|
@ -867,9 +867,13 @@ pub struct AutocompleteStyle {
|
||||||
pub selected_item: ContainerStyle,
|
pub selected_item: ContainerStyle,
|
||||||
pub hovered_item: ContainerStyle,
|
pub hovered_item: ContainerStyle,
|
||||||
pub match_highlight: HighlightStyle,
|
pub match_highlight: HighlightStyle,
|
||||||
pub server_name_container: ContainerStyle,
|
pub completion_min_width: f32,
|
||||||
pub server_name_color: Color,
|
pub completion_max_width: f32,
|
||||||
pub server_name_size_percent: f32,
|
pub inline_docs_container: ContainerStyle,
|
||||||
|
pub inline_docs_color: Color,
|
||||||
|
pub inline_docs_size_percent: f32,
|
||||||
|
pub alongside_docs_max_width: f32,
|
||||||
|
pub alongside_docs_container: ContainerStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Deserialize, JsonSchema)]
|
#[derive(Clone, Copy, Default, Deserialize, JsonSchema)]
|
||||||
|
|
|
@ -206,9 +206,13 @@ export default function editor(): any {
|
||||||
match_highlight: foreground(theme.middle, "accent", "active"),
|
match_highlight: foreground(theme.middle, "accent", "active"),
|
||||||
background: background(theme.middle, "active"),
|
background: background(theme.middle, "active"),
|
||||||
},
|
},
|
||||||
server_name_container: { padding: { left: 40 } },
|
completion_min_width: 300,
|
||||||
server_name_color: text(theme.middle, "sans", "disabled", {}).color,
|
completion_max_width: 700,
|
||||||
server_name_size_percent: 0.75,
|
inline_docs_container: { padding: { left: 40 } },
|
||||||
|
inline_docs_color: text(theme.middle, "sans", "disabled", {}).color,
|
||||||
|
inline_docs_size_percent: 0.75,
|
||||||
|
alongside_docs_max_width: 700,
|
||||||
|
alongside_docs_container: { padding: autocomplete_item.padding }
|
||||||
},
|
},
|
||||||
diagnostic_header: {
|
diagnostic_header: {
|
||||||
background: background(theme.middle),
|
background: background(theme.middle),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue