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:
Julia 2023-10-13 13:26:45 -04:00 committed by GitHub
commit 2323fd17b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1237 additions and 345 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -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

View file

@ -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>)

View file

@ -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

View file

@ -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>,

View file

@ -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"]

View file

@ -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(

View file

@ -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,14 +293,10 @@ 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.
}
// Create symbol range of anchors for highlighting and filtering
// of future requests.
let range = if let Some(range) = hover_result.range { let range = if let Some(range) = hover_result.range {
let start = snapshot let start = snapshot
.buffer_snapshot .buffer_snapshot
@ -309,14 +310,21 @@ fn show_hover(
anchor..anchor 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 { Some(InfoPopover {
project: project.clone(), project: project.clone(),
symbol_range: RangeInEditor::Text(range), symbol_range: RangeInEditor::Text(range),
blocks: hover_result.contents, blocks,
language: hover_result.language, parsed_content,
rendered_content: None,
}) })
}); }
_ => 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![HoverBlock { blocks: vec![HoverBlock {
text: "one [two](the-url) 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"
); );

View file

@ -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,7 +283,13 @@ 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(
scroll_state.type_tag,
*id,
0,
bounds,
Default::default(),
)
.on_scroll({ .on_scroll({
let axis = self.axis; let axis = self.axis;
move |e, _: &mut V, cx| { move |e, _: &mut V, cx| {

View file

@ -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

View file

@ -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,
} }

View file

@ -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;

View 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) = &current_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(" ");
}
}

View file

@ -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,
}) })

View file

@ -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>>>,

View file

@ -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,
} }

View file

@ -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>,

View file

@ -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;

View file

@ -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,

View file

@ -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;

View file

@ -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();

View file

@ -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)]

View file

@ -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),