Address some TODOs in editor2 crate (#3445)

* Fix crash when jumping to definition
* Enabling resolution of completions
* Make links in interactive text clickable
* Enable code paths that use `select_anchors`
This commit is contained in:
Max Brunsfeld 2023-11-29 16:32:02 -08:00 committed by GitHub
commit c95a7c7387
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 574 additions and 555 deletions

View file

@ -73,7 +73,7 @@ use ordered_float::OrderedFloat;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
use rand::prelude::*; use rand::prelude::*;
use rpc::proto::*; use rpc::proto::{self, *};
use scroll::{ use scroll::{
autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
}; };
@ -155,7 +155,6 @@ pub fn render_parsed_markdown(
); );
let runs = text_runs_for_highlights(&parsed.text, &editor_style.text, highlights); let runs = text_runs_for_highlights(&parsed.text, &editor_style.text, highlights);
// todo!("add the ability to change cursor style for link ranges")
let mut links = Vec::new(); let mut links = Vec::new();
let mut link_ranges = Vec::new(); let mut link_ranges = Vec::new();
for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) { for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
@ -972,95 +971,94 @@ impl CompletionsMenu {
fn pre_resolve_completion_documentation( fn pre_resolve_completion_documentation(
&self, &self,
_editor: &Editor, editor: &Editor,
_cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Option<Task<()>> { ) -> Option<Task<()>> {
// todo!("implementation below "); let settings = EditorSettings::get_global(cx);
None if !settings.show_completion_documentation {
return None;
}
let Some(project) = editor.project.clone() else {
return None;
};
let client = project.read(cx).client();
let language_registry = project.read(cx).languages().clone();
let is_remote = project.read(cx).is_remote();
let project_id = project.read(cx).remote_id();
let completions = self.completions.clone();
let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
Some(cx.spawn(move |this, mut cx| async move {
if is_remote {
let Some(project_id) = project_id else {
log::error!("Remote project without remote_id");
return;
};
for completion_index in completion_indices {
let completions_guard = completions.read();
let completion = &completions_guard[completion_index];
if completion.documentation.is_some() {
continue;
}
let server_id = completion.server_id;
let completion = completion.lsp_completion.clone();
drop(completions_guard);
Self::resolve_completion_documentation_remote(
project_id,
server_id,
completions.clone(),
completion_index,
completion,
client.clone(),
language_registry.clone(),
)
.await;
_ = this.update(&mut cx, |_, cx| cx.notify());
}
} else {
for completion_index in completion_indices {
let completions_guard = completions.read();
let completion = &completions_guard[completion_index];
if completion.documentation.is_some() {
continue;
}
let server_id = completion.server_id;
let completion = completion.lsp_completion.clone();
drop(completions_guard);
let server = project
.read_with(&mut cx, |project, _| {
project.language_server_for_id(server_id)
})
.ok()
.flatten();
let Some(server) = server else {
return;
};
Self::resolve_completion_documentation_local(
server,
completions.clone(),
completion_index,
completion,
language_registry.clone(),
)
.await;
_ = this.update(&mut cx, |_, cx| cx.notify());
}
}
}))
} }
// {
// let settings = EditorSettings::get_global(cx);
// if !settings.show_completion_documentation {
// return None;
// }
// let Some(project) = editor.project.clone() else {
// return None;
// };
// let client = project.read(cx).client();
// let language_registry = project.read(cx).languages().clone();
// let is_remote = project.read(cx).is_remote();
// let project_id = project.read(cx).remote_id();
// let completions = self.completions.clone();
// let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
// Some(cx.spawn(move |this, mut cx| async move {
// if is_remote {
// let Some(project_id) = project_id else {
// log::error!("Remote project without remote_id");
// return;
// };
// for completion_index in completion_indices {
// let completions_guard = completions.read();
// let completion = &completions_guard[completion_index];
// if completion.documentation.is_some() {
// continue;
// }
// let server_id = completion.server_id;
// let completion = completion.lsp_completion.clone();
// drop(completions_guard);
// Self::resolve_completion_documentation_remote(
// project_id,
// server_id,
// completions.clone(),
// completion_index,
// completion,
// client.clone(),
// language_registry.clone(),
// )
// .await;
// _ = this.update(&mut cx, |_, cx| cx.notify());
// }
// } else {
// for completion_index in completion_indices {
// let completions_guard = completions.read();
// let completion = &completions_guard[completion_index];
// if completion.documentation.is_some() {
// continue;
// }
// let server_id = completion.server_id;
// let completion = completion.lsp_completion.clone();
// drop(completions_guard);
// let server = project.read_with(&mut cx, |project, _| {
// project.language_server_for_id(server_id)
// });
// let Some(server) = server else {
// return;
// };
// Self::resolve_completion_documentation_local(
// server,
// completions.clone(),
// completion_index,
// completion,
// language_registry.clone(),
// )
// .await;
// _ = this.update(&mut cx, |_, cx| cx.notify());
// }
// }
// }))
// }
fn attempt_resolve_selected_completion_documentation( fn attempt_resolve_selected_completion_documentation(
&mut self, &mut self,
@ -1081,10 +1079,9 @@ impl CompletionsMenu {
let completions = self.completions.clone(); let completions = self.completions.clone();
let completions_guard = completions.read(); let completions_guard = completions.read();
let completion = &completions_guard[completion_index]; let completion = &completions_guard[completion_index];
// todo!() if completion.documentation.is_some() {
// if completion.documentation.is_some() { return;
// return; }
// }
let server_id = completion.server_id; let server_id = completion.server_id;
let completion = completion.lsp_completion.clone(); let completion = completion.lsp_completion.clone();
@ -1143,41 +1140,40 @@ impl CompletionsMenu {
client: Arc<Client>, client: Arc<Client>,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
) { ) {
// todo!() let request = proto::ResolveCompletionDocumentation {
// let request = proto::ResolveCompletionDocumentation { project_id,
// project_id, language_server_id: server_id.0 as u64,
// language_server_id: server_id.0 as u64, lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
// lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), };
// };
// let Some(response) = client let Some(response) = client
// .request(request) .request(request)
// .await .await
// .context("completion documentation resolve proto request") .context("completion documentation resolve proto request")
// .log_err() .log_err()
// else { else {
// return; return;
// }; };
// if response.text.is_empty() { if response.text.is_empty() {
// let mut completions = completions.write(); let mut completions = completions.write();
// let completion = &mut completions[completion_index]; let completion = &mut completions[completion_index];
// completion.documentation = Some(Documentation::Undocumented); completion.documentation = Some(Documentation::Undocumented);
// } }
// let documentation = if response.is_markdown { let documentation = if response.is_markdown {
// Documentation::MultiLineMarkdown( Documentation::MultiLineMarkdown(
// markdown::parse_markdown(&response.text, &language_registry, None).await, markdown::parse_markdown(&response.text, &language_registry, None).await,
// ) )
// } else if response.text.lines().count() <= 1 { } else if response.text.lines().count() <= 1 {
// Documentation::SingleLine(response.text) Documentation::SingleLine(response.text)
// } else { } else {
// Documentation::MultiLinePlainText(response.text) Documentation::MultiLinePlainText(response.text)
// }; };
// let mut completions = completions.write(); let mut completions = completions.write();
// let completion = &mut completions[completion_index]; let completion = &mut completions[completion_index];
// completion.documentation = Some(documentation); completion.documentation = Some(documentation);
} }
async fn resolve_completion_documentation_local( async fn resolve_completion_documentation_local(
@ -1187,38 +1183,37 @@ impl CompletionsMenu {
completion: lsp::CompletionItem, completion: lsp::CompletionItem,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
) { ) {
// todo!() let can_resolve = server
// let can_resolve = server .capabilities()
// .capabilities() .completion_provider
// .completion_provider .as_ref()
// .as_ref() .and_then(|options| options.resolve_provider)
// .and_then(|options| options.resolve_provider) .unwrap_or(false);
// .unwrap_or(false); if !can_resolve {
// if !can_resolve { return;
// return; }
// }
// let request = server.request::<lsp::request::ResolveCompletionItem>(completion); let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
// let Some(completion_item) = request.await.log_err() else { let Some(completion_item) = request.await.log_err() else {
// return; return;
// }; };
// if let Some(lsp_documentation) = completion_item.documentation { if let Some(lsp_documentation) = completion_item.documentation {
// let documentation = language::prepare_completion_documentation( let documentation = language::prepare_completion_documentation(
// &lsp_documentation, &lsp_documentation,
// &language_registry, &language_registry,
// None, // TODO: Try to reasonably work out which language the completion is for None, // TODO: Try to reasonably work out which language the completion is for
// ) )
// .await; .await;
// let mut completions = completions.write(); let mut completions = completions.write();
// let completion = &mut completions[completion_index]; let completion = &mut completions[completion_index];
// completion.documentation = Some(documentation); completion.documentation = Some(documentation);
// } else { } else {
// let mut completions = completions.write(); let mut completions = completions.write();
// let completion = &mut completions[completion_index]; let completion = &mut completions[completion_index];
// completion.documentation = Some(Documentation::Undocumented); completion.documentation = Some(Documentation::Undocumented);
// } }
} }
fn visible(&self) -> bool { fn visible(&self) -> bool {

View file

@ -5427,178 +5427,177 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
); );
} }
//todo!(completion) #[gpui::test]
// #[gpui::test] async fn test_completion(cx: &mut gpui::TestAppContext) {
// async fn test_completion(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {});
// init_test(cx, |_| {});
// let mut cx = EditorLspTestContext::new_rust( let mut cx = EditorLspTestContext::new_rust(
// lsp::ServerCapabilities { lsp::ServerCapabilities {
// completion_provider: Some(lsp::CompletionOptions { completion_provider: Some(lsp::CompletionOptions {
// trigger_characters: Some(vec![".".to_string(), ":".to_string()]), trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
// resolve_provider: Some(true), resolve_provider: Some(true),
// ..Default::default() ..Default::default()
// }), }),
// ..Default::default() ..Default::default()
// }, },
// cx, cx,
// ) )
// .await; .await;
// cx.set_state(indoc! {" cx.set_state(indoc! {"
// oneˇ oneˇ
// two two
// three three
// "}); "});
// cx.simulate_keystroke("."); cx.simulate_keystroke(".");
// handle_completion_request( handle_completion_request(
// &mut cx, &mut cx,
// indoc! {" indoc! {"
// one.|<> one.|<>
// two two
// three three
// "}, "},
// vec!["first_completion", "second_completion"], vec!["first_completion", "second_completion"],
// ) )
// .await; .await;
// cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
// .await; .await;
// let apply_additional_edits = cx.update_editor(|editor, cx| { let apply_additional_edits = cx.update_editor(|editor, cx| {
// editor.context_menu_next(&Default::default(), cx); editor.context_menu_next(&Default::default(), cx);
// editor editor
// .confirm_completion(&ConfirmCompletion::default(), cx) .confirm_completion(&ConfirmCompletion::default(), cx)
// .unwrap() .unwrap()
// }); });
// cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
// one.second_completionˇ one.second_completionˇ
// two two
// three three
// "}); "});
// handle_resolve_completion_request( handle_resolve_completion_request(
// &mut cx, &mut cx,
// Some(vec![ Some(vec![
// ( (
// //This overlaps with the primary completion edit which is //This overlaps with the primary completion edit which is
// //misbehavior from the LSP spec, test that we filter it out //misbehavior from the LSP spec, test that we filter it out
// indoc! {" indoc! {"
// one.second_ˇcompletion one.second_ˇcompletion
// two two
// threeˇ threeˇ
// "}, "},
// "overlapping additional edit", "overlapping additional edit",
// ), ),
// ( (
// indoc! {" indoc! {"
// one.second_completion one.second_completion
// two two
// threeˇ threeˇ
// "}, "},
// "\nadditional edit", "\nadditional edit",
// ), ),
// ]), ]),
// ) )
// .await; .await;
// apply_additional_edits.await.unwrap(); apply_additional_edits.await.unwrap();
// cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
// one.second_completionˇ one.second_completionˇ
// two two
// three three
// additional edit additional edit
// "}); "});
// cx.set_state(indoc! {" cx.set_state(indoc! {"
// one.second_completion one.second_completion
// twoˇ twoˇ
// threeˇ threeˇ
// additional edit additional edit
// "}); "});
// cx.simulate_keystroke(" "); cx.simulate_keystroke(" ");
// assert!(cx.editor(|e, _| e.context_menu.read().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.read().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
// two sˇ two
// three sˇ three
// additional edit additional edit
// "}); "});
// handle_completion_request( handle_completion_request(
// &mut cx, &mut cx,
// indoc! {" indoc! {"
// one.second_completion one.second_completion
// two s two s
// three <s|> three <s|>
// additional edit additional edit
// "}, "},
// vec!["fourth_completion", "fifth_completion", "sixth_completion"], vec!["fourth_completion", "fifth_completion", "sixth_completion"],
// ) )
// .await; .await;
// cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
// .await; .await;
// cx.simulate_keystroke("i"); cx.simulate_keystroke("i");
// handle_completion_request( handle_completion_request(
// &mut cx, &mut cx,
// indoc! {" indoc! {"
// one.second_completion one.second_completion
// two si two si
// three <si|> three <si|>
// additional edit additional edit
// "}, "},
// vec!["fourth_completion", "fifth_completion", "sixth_completion"], vec!["fourth_completion", "fifth_completion", "sixth_completion"],
// ) )
// .await; .await;
// cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
// .await; .await;
// let apply_additional_edits = cx.update_editor(|editor, cx| { let apply_additional_edits = cx.update_editor(|editor, cx| {
// editor editor
// .confirm_completion(&ConfirmCompletion::default(), cx) .confirm_completion(&ConfirmCompletion::default(), cx)
// .unwrap() .unwrap()
// }); });
// cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
// one.second_completion one.second_completion
// two sixth_completionˇ two sixth_completionˇ
// three sixth_completionˇ three sixth_completionˇ
// additional edit additional edit
// "}); "});
// handle_resolve_completion_request(&mut cx, None).await; handle_resolve_completion_request(&mut cx, None).await;
// apply_additional_edits.await.unwrap(); apply_additional_edits.await.unwrap();
// cx.update(|cx| { cx.update(|cx| {
// cx.update_global::<SettingsStore, _, _>(|settings, cx| { cx.update_global::<SettingsStore, _>(|settings, cx| {
// settings.update_user_settings::<EditorSettings>(cx, |settings| { settings.update_user_settings::<EditorSettings>(cx, |settings| {
// settings.show_completions_on_input = Some(false); settings.show_completions_on_input = Some(false);
// }); });
// }) })
// }); });
// cx.set_state("editorˇ"); cx.set_state("editorˇ");
// cx.simulate_keystroke("."); cx.simulate_keystroke(".");
// assert!(cx.editor(|e, _| e.context_menu.read().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.read().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);
// }); });
// handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await; handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
// cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
// .await; .await;
// let apply_additional_edits = cx.update_editor(|editor, cx| { let apply_additional_edits = cx.update_editor(|editor, cx| {
// editor editor
// .confirm_completion(&ConfirmCompletion::default(), cx) .confirm_completion(&ConfirmCompletion::default(), cx)
// .unwrap() .unwrap()
// }); });
// cx.assert_editor_state("editor.closeˇ"); cx.assert_editor_state("editor.closeˇ");
// handle_resolve_completion_request(&mut cx, None).await; handle_resolve_completion_request(&mut cx, None).await;
// apply_additional_edits.await.unwrap(); apply_additional_edits.await.unwrap();
// } }
#[gpui::test] #[gpui::test]
async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
@ -7803,197 +7802,196 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
); );
} }
//todo!(completions) #[gpui::test]
// #[gpui::test] async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
// async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {});
// init_test(cx, |_| {});
// let mut cx = EditorLspTestContext::new_rust( let mut cx = EditorLspTestContext::new_rust(
// lsp::ServerCapabilities { lsp::ServerCapabilities {
// completion_provider: Some(lsp::CompletionOptions { completion_provider: Some(lsp::CompletionOptions {
// trigger_characters: Some(vec![".".to_string()]), trigger_characters: Some(vec![".".to_string()]),
// resolve_provider: Some(true), resolve_provider: Some(true),
// ..Default::default() ..Default::default()
// }), }),
// ..Default::default() ..Default::default()
// }, },
// cx, cx,
// ) )
// .await; .await;
// cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"}); cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
// cx.simulate_keystroke("."); cx.simulate_keystroke(".");
// let completion_item = lsp::CompletionItem { let completion_item = lsp::CompletionItem {
// label: "some".into(), label: "some".into(),
// kind: Some(lsp::CompletionItemKind::SNIPPET), kind: Some(lsp::CompletionItemKind::SNIPPET),
// detail: Some("Wrap the expression in an `Option::Some`".to_string()), detail: Some("Wrap the expression in an `Option::Some`".to_string()),
// documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
// kind: lsp::MarkupKind::Markdown, kind: lsp::MarkupKind::Markdown,
// value: "```rust\nSome(2)\n```".to_string(), value: "```rust\nSome(2)\n```".to_string(),
// })), })),
// deprecated: Some(false), deprecated: Some(false),
// sort_text: Some("fffffff2".to_string()), sort_text: Some("fffffff2".to_string()),
// filter_text: Some("some".to_string()), filter_text: Some("some".to_string()),
// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
// range: lsp::Range { range: lsp::Range {
// start: lsp::Position { start: lsp::Position {
// line: 0, line: 0,
// character: 22, character: 22,
// }, },
// end: lsp::Position { end: lsp::Position {
// line: 0, line: 0,
// character: 22, character: 22,
// }, },
// }, },
// new_text: "Some(2)".to_string(), new_text: "Some(2)".to_string(),
// })), })),
// additional_text_edits: Some(vec![lsp::TextEdit { additional_text_edits: Some(vec![lsp::TextEdit {
// range: lsp::Range { range: lsp::Range {
// start: lsp::Position { start: lsp::Position {
// line: 0, line: 0,
// character: 20, character: 20,
// }, },
// end: lsp::Position { end: lsp::Position {
// line: 0, line: 0,
// character: 22, character: 22,
// }, },
// }, },
// new_text: "".to_string(), new_text: "".to_string(),
// }]), }]),
// ..Default::default() ..Default::default()
// }; };
// let closure_completion_item = completion_item.clone(); let closure_completion_item = completion_item.clone();
// let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| { let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
// let task_completion_item = closure_completion_item.clone(); let task_completion_item = closure_completion_item.clone();
// async move { async move {
// Ok(Some(lsp::CompletionResponse::Array(vec![ Ok(Some(lsp::CompletionResponse::Array(vec![
// task_completion_item, task_completion_item,
// ]))) ])))
// } }
// }); });
// request.next().await; request.next().await;
// cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
// .await; .await;
// let apply_additional_edits = cx.update_editor(|editor, cx| { let apply_additional_edits = cx.update_editor(|editor, cx| {
// editor editor
// .confirm_completion(&ConfirmCompletion::default(), cx) .confirm_completion(&ConfirmCompletion::default(), cx)
// .unwrap() .unwrap()
// }); });
// cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"}); cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
// cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| { cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
// let task_completion_item = completion_item.clone(); let task_completion_item = completion_item.clone();
// async move { Ok(task_completion_item) } async move { Ok(task_completion_item) }
// }) })
// .next() .next()
// .await .await
// .unwrap(); .unwrap();
// apply_additional_edits.await.unwrap(); apply_additional_edits.await.unwrap();
// cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"}); cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
// } }
// #[gpui::test] #[gpui::test]
// async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) { async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
// init_test(cx, |_| {}); init_test(cx, |_| {});
// let mut cx = EditorLspTestContext::new( let mut cx = EditorLspTestContext::new(
// Language::new( Language::new(
// LanguageConfig { LanguageConfig {
// path_suffixes: vec!["jsx".into()], path_suffixes: vec!["jsx".into()],
// overrides: [( overrides: [(
// "element".into(), "element".into(),
// LanguageConfigOverride { LanguageConfigOverride {
// word_characters: Override::Set(['-'].into_iter().collect()), word_characters: Override::Set(['-'].into_iter().collect()),
// ..Default::default() ..Default::default()
// }, },
// )] )]
// .into_iter() .into_iter()
// .collect(), .collect(),
// ..Default::default() ..Default::default()
// }, },
// Some(tree_sitter_typescript::language_tsx()), Some(tree_sitter_typescript::language_tsx()),
// ) )
// .with_override_query("(jsx_self_closing_element) @element") .with_override_query("(jsx_self_closing_element) @element")
// .unwrap(), .unwrap(),
// lsp::ServerCapabilities { lsp::ServerCapabilities {
// completion_provider: Some(lsp::CompletionOptions { completion_provider: Some(lsp::CompletionOptions {
// trigger_characters: Some(vec![":".to_string()]), trigger_characters: Some(vec![":".to_string()]),
// ..Default::default() ..Default::default()
// }), }),
// ..Default::default() ..Default::default()
// }, },
// cx, cx,
// ) )
// .await; .await;
// cx.lsp cx.lsp
// .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move { .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
// Ok(Some(lsp::CompletionResponse::Array(vec![ Ok(Some(lsp::CompletionResponse::Array(vec![
// lsp::CompletionItem { lsp::CompletionItem {
// label: "bg-blue".into(), label: "bg-blue".into(),
// ..Default::default() ..Default::default()
// }, },
// lsp::CompletionItem { lsp::CompletionItem {
// label: "bg-red".into(), label: "bg-red".into(),
// ..Default::default() ..Default::default()
// }, },
// lsp::CompletionItem { lsp::CompletionItem {
// label: "bg-yellow".into(), label: "bg-yellow".into(),
// ..Default::default() ..Default::default()
// }, },
// ]))) ])))
// }); });
// cx.set_state(r#"<p class="bgˇ" />"#); cx.set_state(r#"<p class="bgˇ" />"#);
// // Trigger completion when typing a dash, because the dash is an extra // Trigger completion when typing a dash, because the dash is an extra
// // word character in the 'element' scope, which contains the cursor. // word character in the 'element' scope, which contains the cursor.
// cx.simulate_keystroke("-"); cx.simulate_keystroke("-");
// cx.executor().run_until_parked(); cx.executor().run_until_parked();
// cx.update_editor(|editor, _| { cx.update_editor(|editor, _| {
// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { 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"]
// ); );
// } else { } else {
// panic!("expected completion menu to be open"); panic!("expected completion menu to be open");
// } }
// }); });
// cx.simulate_keystroke("l"); cx.simulate_keystroke("l");
// cx.executor().run_until_parked(); cx.executor().run_until_parked();
// cx.update_editor(|editor, _| { cx.update_editor(|editor, _| {
// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { 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"]
// ); );
// } else { } else {
// panic!("expected completion menu to be open"); panic!("expected completion menu to be open");
// } }
// }); });
// // When filtering completions, consider the character after the '-' to // When filtering completions, consider the character after the '-' to
// // be the start of a subword. // be the start of a subword.
// cx.set_state(r#"<p class="yelˇ" />"#); cx.set_state(r#"<p class="yelˇ" />"#);
// cx.simulate_keystroke("l"); cx.simulate_keystroke("l");
// cx.executor().run_until_parked(); cx.executor().run_until_parked();
// cx.update_editor(|editor, _| { cx.update_editor(|editor, _| {
// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { 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"]
// ); );
// } else { } else {
// panic!("expected completion menu to be open"); panic!("expected completion menu to be open");
// } }
// }); });
// } }
#[gpui::test] #[gpui::test]
async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {

View file

@ -5,7 +5,7 @@ use crate::{
Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId, Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId,
SelectPhase, SelectPhase,
}; };
use gpui::{Task, ViewContext}; use gpui::{px, Task, ViewContext};
use language::{Bias, ToOffset}; use language::{Bias, ToOffset};
use lsp::LanguageServerId; use lsp::LanguageServerId;
use project::{ use project::{
@ -13,6 +13,7 @@ use project::{
ResolveState, ResolveState,
}; };
use std::ops::Range; use std::ops::Range;
use theme::ActiveTheme as _;
use util::TryFutureExt; use util::TryFutureExt;
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -485,40 +486,45 @@ pub fn show_link_definition(
}); });
if any_definition_does_not_contain_current_location { if any_definition_does_not_contain_current_location {
// todo!() let style = gpui::HighlightStyle {
// // Highlight symbol using theme link definition highlight style underline: Some(gpui::UnderlineStyle {
// let style = theme::current(cx).editor.link_definition; thickness: px(1.),
// let highlight_range = ..Default::default()
// symbol_range.unwrap_or_else(|| match &trigger_point { }),
// TriggerPoint::Text(trigger_anchor) => { color: Some(gpui::red()),
// let snapshot = &snapshot.buffer_snapshot; ..Default::default()
// // If no symbol range returned from language server, use the surrounding word. };
// let (offset_range, _) = let highlight_range =
// snapshot.surrounding_word(*trigger_anchor); symbol_range.unwrap_or_else(|| match &trigger_point {
// RangeInEditor::Text( TriggerPoint::Text(trigger_anchor) => {
// snapshot.anchor_before(offset_range.start) let snapshot = &snapshot.buffer_snapshot;
// ..snapshot.anchor_after(offset_range.end), // If no symbol range returned from language server, use the surrounding word.
// ) let (offset_range, _) =
// } snapshot.surrounding_word(*trigger_anchor);
// TriggerPoint::InlayHint(highlight, _, _) => { RangeInEditor::Text(
// RangeInEditor::Inlay(highlight.clone()) snapshot.anchor_before(offset_range.start)
// } ..snapshot.anchor_after(offset_range.end),
// }); )
}
TriggerPoint::InlayHint(highlight, _, _) => {
RangeInEditor::Inlay(highlight.clone())
}
});
// match highlight_range { match highlight_range {
// RangeInEditor::Text(text_range) => this RangeInEditor::Text(text_range) => this
// .highlight_text::<LinkGoToDefinitionState>( .highlight_text::<LinkGoToDefinitionState>(
// vec![text_range], vec![text_range],
// style, style,
// cx, cx,
// ), ),
// RangeInEditor::Inlay(highlight) => this RangeInEditor::Inlay(highlight) => this
// .highlight_inlays::<LinkGoToDefinitionState>( .highlight_inlays::<LinkGoToDefinitionState>(
// vec![highlight], vec![highlight],
// style, style,
// cx, cx,
// ), ),
// } }
} else { } else {
hide_link_definition(this, cx); hide_link_definition(this, cx);
} }

View file

@ -595,31 +595,32 @@ impl<'a> MutableSelectionsCollection<'a> {
self.select(selections) self.select(selections)
} }
pub fn select_anchor_ranges<I: IntoIterator<Item = Range<Anchor>>>(&mut self, ranges: I) { pub fn select_anchor_ranges<I>(&mut self, ranges: I)
todo!() where
// let buffer = self.buffer.read(self.cx).snapshot(self.cx); I: IntoIterator<Item = Range<Anchor>>,
// let selections = ranges {
// .into_iter() let buffer = self.buffer.read(self.cx).snapshot(self.cx);
// .map(|range| { let selections = ranges
// let mut start = range.start; .into_iter()
// let mut end = range.end; .map(|range| {
// let reversed = if start.cmp(&end, &buffer).is_gt() { let mut start = range.start;
// mem::swap(&mut start, &mut end); let mut end = range.end;
// true let reversed = if start.cmp(&end, &buffer).is_gt() {
// } else { mem::swap(&mut start, &mut end);
// false true
// }; } else {
// Selection { false
// id: post_inc(&mut self.collection.next_selection_id), };
// start, Selection {
// end, id: post_inc(&mut self.collection.next_selection_id),
// reversed, start,
// goal: SelectionGoal::None, end,
// } reversed,
// }) goal: SelectionGoal::None,
// .collect::<Vec<_>>(); }
})
// self.select_anchors(selections) .collect::<Vec<_>>();
self.select_anchors(selections)
} }
pub fn new_selection_id(&mut self) -> usize { pub fn new_selection_id(&mut self) -> usize {

View file

@ -265,7 +265,9 @@ impl TextState {
pub struct InteractiveText { pub struct InteractiveText {
element_id: ElementId, element_id: ElementId,
text: StyledText, text: StyledText,
click_listener: Option<Box<dyn Fn(InteractiveTextClickEvent, &mut WindowContext<'_>)>>, click_listener:
Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
clickable_ranges: Vec<Range<usize>>,
} }
struct InteractiveTextClickEvent { struct InteractiveTextClickEvent {
@ -284,6 +286,7 @@ impl InteractiveText {
element_id: id.into(), element_id: id.into(),
text, text,
click_listener: None, click_listener: None,
clickable_ranges: Vec::new(),
} }
} }
@ -292,7 +295,7 @@ impl InteractiveText {
ranges: Vec<Range<usize>>, ranges: Vec<Range<usize>>,
listener: impl Fn(usize, &mut WindowContext<'_>) + 'static, listener: impl Fn(usize, &mut WindowContext<'_>) + 'static,
) -> Self { ) -> Self {
self.click_listener = Some(Box::new(move |event, cx| { self.click_listener = Some(Box::new(move |ranges, event, cx| {
for (range_ix, range) in ranges.iter().enumerate() { for (range_ix, range) in ranges.iter().enumerate() {
if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index) if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
{ {
@ -300,6 +303,7 @@ impl InteractiveText {
} }
} }
})); }));
self.clickable_ranges = ranges;
self self
} }
} }
@ -334,6 +338,19 @@ impl Element for InteractiveText {
fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) { fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
if let Some(click_listener) = self.click_listener { if let Some(click_listener) = self.click_listener {
if let Some(ix) = state
.text_state
.index_for_position(bounds, cx.mouse_position())
{
if self
.clickable_ranges
.iter()
.any(|range| range.contains(&ix))
{
cx.set_cursor_style(crate::CursorStyle::PointingHand)
}
}
let text_state = state.text_state.clone(); let text_state = state.text_state.clone();
let mouse_down = state.mouse_down_index.clone(); let mouse_down = state.mouse_down_index.clone();
if let Some(mouse_down_index) = mouse_down.get() { if let Some(mouse_down_index) = mouse_down.get() {
@ -343,6 +360,7 @@ impl Element for InteractiveText {
text_state.index_for_position(bounds, event.position) text_state.index_for_position(bounds, event.position)
{ {
click_listener( click_listener(
&self.clickable_ranges,
InteractiveTextClickEvent { InteractiveTextClickEvent {
mouse_down_index, mouse_down_index,
mouse_up_index, mouse_up_index,

View file

@ -717,8 +717,9 @@ async fn location_links_from_lsp(
})? })?
.await?; .await?;
buffer.update(&mut cx, |origin_buffer, cx| { cx.update(|cx| {
let origin_location = origin_range.map(|origin_range| { let origin_location = origin_range.map(|origin_range| {
let origin_buffer = buffer.read(cx);
let origin_start = let origin_start =
origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left); origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
let origin_end = let origin_end =