diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index c53403c9c3..99d8a4bb93 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -73,7 +73,7 @@ use ordered_float::OrderedFloat; use parking_lot::{Mutex, RwLock}; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use rand::prelude::*; -use rpc::proto::*; +use rpc::proto::{self, *}; use scroll::{ 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); - // todo!("add the ability to change cursor style for link ranges") let mut links = Vec::new(); let mut link_ranges = Vec::new(); for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) { @@ -972,95 +971,94 @@ impl CompletionsMenu { fn pre_resolve_completion_documentation( &self, - _editor: &Editor, - _cx: &mut ViewContext, + editor: &Editor, + cx: &mut ViewContext, ) -> Option> { - // todo!("implementation below "); - None + 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) + }) + .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( &mut self, @@ -1081,10 +1079,9 @@ impl CompletionsMenu { let completions = self.completions.clone(); let completions_guard = completions.read(); let completion = &completions_guard[completion_index]; - // todo!() - // if completion.documentation.is_some() { - // return; - // } + if completion.documentation.is_some() { + return; + } let server_id = completion.server_id; let completion = completion.lsp_completion.clone(); @@ -1143,41 +1140,40 @@ impl CompletionsMenu { client: Arc, language_registry: Arc, ) { - // todo!() - // let request = proto::ResolveCompletionDocumentation { - // project_id, - // language_server_id: server_id.0 as u64, - // lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), - // }; + let request = proto::ResolveCompletionDocumentation { + project_id, + language_server_id: server_id.0 as u64, + lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), + }; - // let Some(response) = client - // .request(request) - // .await - // .context("completion documentation resolve proto request") - // .log_err() - // else { - // return; - // }; + let Some(response) = client + .request(request) + .await + .context("completion documentation resolve proto request") + .log_err() + else { + return; + }; - // if response.text.is_empty() { - // let mut completions = completions.write(); - // let completion = &mut completions[completion_index]; - // completion.documentation = Some(Documentation::Undocumented); - // } + if response.text.is_empty() { + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(Documentation::Undocumented); + } - // let documentation = if response.is_markdown { - // Documentation::MultiLineMarkdown( - // markdown::parse_markdown(&response.text, &language_registry, None).await, - // ) - // } else if response.text.lines().count() <= 1 { - // Documentation::SingleLine(response.text) - // } else { - // Documentation::MultiLinePlainText(response.text) - // }; + let documentation = if response.is_markdown { + Documentation::MultiLineMarkdown( + markdown::parse_markdown(&response.text, &language_registry, None).await, + ) + } else if response.text.lines().count() <= 1 { + Documentation::SingleLine(response.text) + } else { + Documentation::MultiLinePlainText(response.text) + }; - // let mut completions = completions.write(); - // let completion = &mut completions[completion_index]; - // completion.documentation = Some(documentation); + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(documentation); } async fn resolve_completion_documentation_local( @@ -1187,38 +1183,37 @@ impl CompletionsMenu { completion: lsp::CompletionItem, language_registry: Arc, ) { - // todo!() - // let can_resolve = server - // .capabilities() - // .completion_provider - // .as_ref() - // .and_then(|options| options.resolve_provider) - // .unwrap_or(false); - // if !can_resolve { - // return; - // } + let can_resolve = server + .capabilities() + .completion_provider + .as_ref() + .and_then(|options| options.resolve_provider) + .unwrap_or(false); + if !can_resolve { + return; + } - // let request = server.request::(completion); - // let Some(completion_item) = request.await.log_err() else { - // return; - // }; + let request = server.request::(completion); + let Some(completion_item) = request.await.log_err() else { + return; + }; - // if let Some(lsp_documentation) = completion_item.documentation { - // let documentation = language::prepare_completion_documentation( - // &lsp_documentation, - // &language_registry, - // None, // TODO: Try to reasonably work out which language the completion is for - // ) - // .await; + if let Some(lsp_documentation) = completion_item.documentation { + let documentation = language::prepare_completion_documentation( + &lsp_documentation, + &language_registry, + None, // TODO: Try to reasonably work out which language the completion is for + ) + .await; - // let mut completions = completions.write(); - // let completion = &mut completions[completion_index]; - // completion.documentation = Some(documentation); - // } else { - // let mut completions = completions.write(); - // let completion = &mut completions[completion_index]; - // completion.documentation = Some(Documentation::Undocumented); - // } + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(documentation); + } else { + let mut completions = completions.write(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(Documentation::Undocumented); + } } fn visible(&self) -> bool { diff --git a/crates/editor2/src/editor_tests.rs b/crates/editor2/src/editor_tests.rs index 6865e81cfa..e640be8efe 100644 --- a/crates/editor2/src/editor_tests.rs +++ b/crates/editor2/src/editor_tests.rs @@ -5427,178 +5427,177 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) ); } -//todo!(completion) -// #[gpui::test] -// async fn test_completion(cx: &mut gpui::TestAppContext) { -// init_test(cx, |_| {}); +#[gpui::test] +async fn test_completion(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); -// let mut cx = EditorLspTestContext::new_rust( -// lsp::ServerCapabilities { -// completion_provider: Some(lsp::CompletionOptions { -// trigger_characters: Some(vec![".".to_string(), ":".to_string()]), -// resolve_provider: Some(true), -// ..Default::default() -// }), -// ..Default::default() -// }, -// cx, -// ) -// .await; + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string(), ":".to_string()]), + resolve_provider: Some(true), + ..Default::default() + }), + ..Default::default() + }, + cx, + ) + .await; -// cx.set_state(indoc! {" -// oneˇ -// two -// three -// "}); -// cx.simulate_keystroke("."); -// handle_completion_request( -// &mut cx, -// indoc! {" -// one.|<> -// two -// three -// "}, -// vec!["first_completion", "second_completion"], -// ) -// .await; -// cx.condition(|editor, _| editor.context_menu_visible()) -// .await; -// let apply_additional_edits = cx.update_editor(|editor, cx| { -// editor.context_menu_next(&Default::default(), cx); -// editor -// .confirm_completion(&ConfirmCompletion::default(), cx) -// .unwrap() -// }); -// cx.assert_editor_state(indoc! {" -// one.second_completionˇ -// two -// three -// "}); + cx.set_state(indoc! {" + oneˇ + two + three + "}); + cx.simulate_keystroke("."); + handle_completion_request( + &mut cx, + indoc! {" + one.|<> + two + three + "}, + vec!["first_completion", "second_completion"], + ) + .await; + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + let apply_additional_edits = cx.update_editor(|editor, cx| { + editor.context_menu_next(&Default::default(), cx); + editor + .confirm_completion(&ConfirmCompletion::default(), cx) + .unwrap() + }); + cx.assert_editor_state(indoc! {" + one.second_completionˇ + two + three + "}); -// handle_resolve_completion_request( -// &mut cx, -// Some(vec![ -// ( -// //This overlaps with the primary completion edit which is -// //misbehavior from the LSP spec, test that we filter it out -// indoc! {" -// one.second_ˇcompletion -// two -// threeˇ -// "}, -// "overlapping additional edit", -// ), -// ( -// indoc! {" -// one.second_completion -// two -// threeˇ -// "}, -// "\nadditional edit", -// ), -// ]), -// ) -// .await; -// apply_additional_edits.await.unwrap(); -// cx.assert_editor_state(indoc! {" -// one.second_completionˇ -// two -// three -// additional edit -// "}); + handle_resolve_completion_request( + &mut cx, + Some(vec![ + ( + //This overlaps with the primary completion edit which is + //misbehavior from the LSP spec, test that we filter it out + indoc! {" + one.second_ˇcompletion + two + threeˇ + "}, + "overlapping additional edit", + ), + ( + indoc! {" + one.second_completion + two + threeˇ + "}, + "\nadditional edit", + ), + ]), + ) + .await; + apply_additional_edits.await.unwrap(); + cx.assert_editor_state(indoc! {" + one.second_completionˇ + two + three + additional edit + "}); -// cx.set_state(indoc! {" -// one.second_completion -// twoˇ -// threeˇ -// additional edit -// "}); -// cx.simulate_keystroke(" "); -// assert!(cx.editor(|e, _| e.context_menu.read().is_none())); -// cx.simulate_keystroke("s"); -// assert!(cx.editor(|e, _| e.context_menu.read().is_none())); + cx.set_state(indoc! {" + one.second_completion + twoˇ + threeˇ + additional edit + "}); + cx.simulate_keystroke(" "); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); + cx.simulate_keystroke("s"); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); -// cx.assert_editor_state(indoc! {" -// one.second_completion -// two sˇ -// three sˇ -// additional edit -// "}); -// handle_completion_request( -// &mut cx, -// indoc! {" -// one.second_completion -// two s -// three -// additional edit -// "}, -// vec!["fourth_completion", "fifth_completion", "sixth_completion"], -// ) -// .await; -// cx.condition(|editor, _| editor.context_menu_visible()) -// .await; + cx.assert_editor_state(indoc! {" + one.second_completion + two sˇ + three sˇ + additional edit + "}); + handle_completion_request( + &mut cx, + indoc! {" + one.second_completion + two s + three + additional edit + "}, + vec!["fourth_completion", "fifth_completion", "sixth_completion"], + ) + .await; + cx.condition(|editor, _| editor.context_menu_visible()) + .await; -// cx.simulate_keystroke("i"); + cx.simulate_keystroke("i"); -// handle_completion_request( -// &mut cx, -// indoc! {" -// one.second_completion -// two si -// three -// additional edit -// "}, -// vec!["fourth_completion", "fifth_completion", "sixth_completion"], -// ) -// .await; -// cx.condition(|editor, _| editor.context_menu_visible()) -// .await; + handle_completion_request( + &mut cx, + indoc! {" + one.second_completion + two si + three + additional edit + "}, + vec!["fourth_completion", "fifth_completion", "sixth_completion"], + ) + .await; + cx.condition(|editor, _| editor.context_menu_visible()) + .await; -// let apply_additional_edits = cx.update_editor(|editor, cx| { -// editor -// .confirm_completion(&ConfirmCompletion::default(), cx) -// .unwrap() -// }); -// cx.assert_editor_state(indoc! {" -// one.second_completion -// two sixth_completionˇ -// three sixth_completionˇ -// additional edit -// "}); + let apply_additional_edits = cx.update_editor(|editor, cx| { + editor + .confirm_completion(&ConfirmCompletion::default(), cx) + .unwrap() + }); + cx.assert_editor_state(indoc! {" + one.second_completion + two sixth_completionˇ + three sixth_completionˇ + additional edit + "}); -// handle_resolve_completion_request(&mut cx, None).await; -// apply_additional_edits.await.unwrap(); + handle_resolve_completion_request(&mut cx, None).await; + apply_additional_edits.await.unwrap(); -// cx.update(|cx| { -// cx.update_global::(|settings, cx| { -// settings.update_user_settings::(cx, |settings| { -// settings.show_completions_on_input = Some(false); -// }); -// }) -// }); -// cx.set_state("editorˇ"); -// cx.simulate_keystroke("."); -// assert!(cx.editor(|e, _| e.context_menu.read().is_none())); -// cx.simulate_keystroke("c"); -// cx.simulate_keystroke("l"); -// cx.simulate_keystroke("o"); -// cx.assert_editor_state("editor.cloˇ"); -// assert!(cx.editor(|e, _| e.context_menu.read().is_none())); -// cx.update_editor(|editor, cx| { -// editor.show_completions(&ShowCompletions, cx); -// }); -// handle_completion_request(&mut cx, "editor.", vec!["close", "clobber"]).await; -// cx.condition(|editor, _| editor.context_menu_visible()) -// .await; -// let apply_additional_edits = cx.update_editor(|editor, cx| { -// editor -// .confirm_completion(&ConfirmCompletion::default(), cx) -// .unwrap() -// }); -// cx.assert_editor_state("editor.closeˇ"); -// handle_resolve_completion_request(&mut cx, None).await; -// apply_additional_edits.await.unwrap(); -// } + cx.update(|cx| { + cx.update_global::(|settings, cx| { + settings.update_user_settings::(cx, |settings| { + settings.show_completions_on_input = Some(false); + }); + }) + }); + cx.set_state("editorˇ"); + cx.simulate_keystroke("."); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); + cx.simulate_keystroke("c"); + cx.simulate_keystroke("l"); + cx.simulate_keystroke("o"); + cx.assert_editor_state("editor.cloˇ"); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); + cx.update_editor(|editor, cx| { + editor.show_completions(&ShowCompletions, cx); + }); + handle_completion_request(&mut cx, "editor.", vec!["close", "clobber"]).await; + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + let apply_additional_edits = cx.update_editor(|editor, cx| { + editor + .confirm_completion(&ConfirmCompletion::default(), cx) + .unwrap() + }); + cx.assert_editor_state("editor.closeˇ"); + handle_resolve_completion_request(&mut cx, None).await; + apply_additional_edits.await.unwrap(); +} #[gpui::test] 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] -// async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) { -// init_test(cx, |_| {}); +#[gpui::test] +async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); -// let mut cx = EditorLspTestContext::new_rust( -// lsp::ServerCapabilities { -// completion_provider: Some(lsp::CompletionOptions { -// trigger_characters: Some(vec![".".to_string()]), -// resolve_provider: Some(true), -// ..Default::default() -// }), -// ..Default::default() -// }, -// cx, -// ) -// .await; + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string()]), + resolve_provider: Some(true), + ..Default::default() + }), + ..Default::default() + }, + cx, + ) + .await; -// cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"}); -// cx.simulate_keystroke("."); -// let completion_item = lsp::CompletionItem { -// label: "some".into(), -// kind: Some(lsp::CompletionItemKind::SNIPPET), -// detail: Some("Wrap the expression in an `Option::Some`".to_string()), -// documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { -// kind: lsp::MarkupKind::Markdown, -// value: "```rust\nSome(2)\n```".to_string(), -// })), -// deprecated: Some(false), -// sort_text: Some("fffffff2".to_string()), -// filter_text: Some("some".to_string()), -// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), -// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { -// range: lsp::Range { -// start: lsp::Position { -// line: 0, -// character: 22, -// }, -// end: lsp::Position { -// line: 0, -// character: 22, -// }, -// }, -// new_text: "Some(2)".to_string(), -// })), -// additional_text_edits: Some(vec![lsp::TextEdit { -// range: lsp::Range { -// start: lsp::Position { -// line: 0, -// character: 20, -// }, -// end: lsp::Position { -// line: 0, -// character: 22, -// }, -// }, -// new_text: "".to_string(), -// }]), -// ..Default::default() -// }; + cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"}); + cx.simulate_keystroke("."); + let completion_item = lsp::CompletionItem { + label: "some".into(), + kind: Some(lsp::CompletionItemKind::SNIPPET), + detail: Some("Wrap the expression in an `Option::Some`".to_string()), + documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { + kind: lsp::MarkupKind::Markdown, + value: "```rust\nSome(2)\n```".to_string(), + })), + deprecated: Some(false), + sort_text: Some("fffffff2".to_string()), + filter_text: Some("some".to_string()), + insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 22, + }, + end: lsp::Position { + line: 0, + character: 22, + }, + }, + new_text: "Some(2)".to_string(), + })), + additional_text_edits: Some(vec![lsp::TextEdit { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 20, + }, + end: lsp::Position { + line: 0, + character: 22, + }, + }, + new_text: "".to_string(), + }]), + ..Default::default() + }; -// let closure_completion_item = completion_item.clone(); -// let mut request = cx.handle_request::(move |_, _, _| { -// let task_completion_item = closure_completion_item.clone(); -// async move { -// Ok(Some(lsp::CompletionResponse::Array(vec![ -// task_completion_item, -// ]))) -// } -// }); + let closure_completion_item = completion_item.clone(); + let mut request = cx.handle_request::(move |_, _, _| { + let task_completion_item = closure_completion_item.clone(); + async move { + Ok(Some(lsp::CompletionResponse::Array(vec![ + task_completion_item, + ]))) + } + }); -// request.next().await; + request.next().await; -// cx.condition(|editor, _| editor.context_menu_visible()) -// .await; -// let apply_additional_edits = cx.update_editor(|editor, cx| { -// editor -// .confirm_completion(&ConfirmCompletion::default(), cx) -// .unwrap() -// }); -// cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"}); + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + let apply_additional_edits = cx.update_editor(|editor, cx| { + editor + .confirm_completion(&ConfirmCompletion::default(), cx) + .unwrap() + }); + cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"}); -// cx.handle_request::(move |_, _, _| { -// let task_completion_item = completion_item.clone(); -// async move { Ok(task_completion_item) } -// }) -// .next() -// .await -// .unwrap(); -// apply_additional_edits.await.unwrap(); -// cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"}); -// } + cx.handle_request::(move |_, _, _| { + let task_completion_item = completion_item.clone(); + async move { Ok(task_completion_item) } + }) + .next() + .await + .unwrap(); + apply_additional_edits.await.unwrap(); + cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"}); +} -// #[gpui::test] -// async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) { -// init_test(cx, |_| {}); +#[gpui::test] +async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); -// let mut cx = EditorLspTestContext::new( -// Language::new( -// LanguageConfig { -// path_suffixes: vec!["jsx".into()], -// overrides: [( -// "element".into(), -// LanguageConfigOverride { -// word_characters: Override::Set(['-'].into_iter().collect()), -// ..Default::default() -// }, -// )] -// .into_iter() -// .collect(), -// ..Default::default() -// }, -// Some(tree_sitter_typescript::language_tsx()), -// ) -// .with_override_query("(jsx_self_closing_element) @element") -// .unwrap(), -// lsp::ServerCapabilities { -// completion_provider: Some(lsp::CompletionOptions { -// trigger_characters: Some(vec![":".to_string()]), -// ..Default::default() -// }), -// ..Default::default() -// }, -// cx, -// ) -// .await; + let mut cx = EditorLspTestContext::new( + Language::new( + LanguageConfig { + path_suffixes: vec!["jsx".into()], + overrides: [( + "element".into(), + LanguageConfigOverride { + word_characters: Override::Set(['-'].into_iter().collect()), + ..Default::default() + }, + )] + .into_iter() + .collect(), + ..Default::default() + }, + Some(tree_sitter_typescript::language_tsx()), + ) + .with_override_query("(jsx_self_closing_element) @element") + .unwrap(), + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![":".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + cx, + ) + .await; -// cx.lsp -// .handle_request::(move |_, _| async move { -// Ok(Some(lsp::CompletionResponse::Array(vec![ -// lsp::CompletionItem { -// label: "bg-blue".into(), -// ..Default::default() -// }, -// lsp::CompletionItem { -// label: "bg-red".into(), -// ..Default::default() -// }, -// lsp::CompletionItem { -// label: "bg-yellow".into(), -// ..Default::default() -// }, -// ]))) -// }); + cx.lsp + .handle_request::(move |_, _| async move { + Ok(Some(lsp::CompletionResponse::Array(vec![ + lsp::CompletionItem { + label: "bg-blue".into(), + ..Default::default() + }, + lsp::CompletionItem { + label: "bg-red".into(), + ..Default::default() + }, + lsp::CompletionItem { + label: "bg-yellow".into(), + ..Default::default() + }, + ]))) + }); -// cx.set_state(r#"

"#); + cx.set_state(r#"

"#); -// // Trigger completion when typing a dash, because the dash is an extra -// // word character in the 'element' scope, which contains the cursor. -// cx.simulate_keystroke("-"); -// cx.executor().run_until_parked(); -// cx.update_editor(|editor, _| { -// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { -// assert_eq!( -// menu.matches.iter().map(|m| &m.string).collect::>(), -// &["bg-red", "bg-blue", "bg-yellow"] -// ); -// } else { -// panic!("expected completion menu to be open"); -// } -// }); + // Trigger completion when typing a dash, because the dash is an extra + // word character in the 'element' scope, which contains the cursor. + cx.simulate_keystroke("-"); + cx.executor().run_until_parked(); + cx.update_editor(|editor, _| { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { + assert_eq!( + menu.matches.iter().map(|m| &m.string).collect::>(), + &["bg-red", "bg-blue", "bg-yellow"] + ); + } else { + panic!("expected completion menu to be open"); + } + }); -// cx.simulate_keystroke("l"); -// cx.executor().run_until_parked(); -// cx.update_editor(|editor, _| { -// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { -// assert_eq!( -// menu.matches.iter().map(|m| &m.string).collect::>(), -// &["bg-blue", "bg-yellow"] -// ); -// } else { -// panic!("expected completion menu to be open"); -// } -// }); + cx.simulate_keystroke("l"); + cx.executor().run_until_parked(); + cx.update_editor(|editor, _| { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { + assert_eq!( + menu.matches.iter().map(|m| &m.string).collect::>(), + &["bg-blue", "bg-yellow"] + ); + } else { + panic!("expected completion menu to be open"); + } + }); -// // When filtering completions, consider the character after the '-' to -// // be the start of a subword. -// cx.set_state(r#"

"#); -// cx.simulate_keystroke("l"); -// cx.executor().run_until_parked(); -// cx.update_editor(|editor, _| { -// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { -// assert_eq!( -// menu.matches.iter().map(|m| &m.string).collect::>(), -// &["bg-yellow"] -// ); -// } else { -// panic!("expected completion menu to be open"); -// } -// }); -// } + // When filtering completions, consider the character after the '-' to + // be the start of a subword. + cx.set_state(r#"

"#); + cx.simulate_keystroke("l"); + cx.executor().run_until_parked(); + cx.update_editor(|editor, _| { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { + assert_eq!( + menu.matches.iter().map(|m| &m.string).collect::>(), + &["bg-yellow"] + ); + } else { + panic!("expected completion menu to be open"); + } + }); +} #[gpui::test] async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { diff --git a/crates/editor2/src/link_go_to_definition.rs b/crates/editor2/src/link_go_to_definition.rs index d36762f395..092882573c 100644 --- a/crates/editor2/src/link_go_to_definition.rs +++ b/crates/editor2/src/link_go_to_definition.rs @@ -5,7 +5,7 @@ use crate::{ Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId, SelectPhase, }; -use gpui::{Task, ViewContext}; +use gpui::{px, Task, ViewContext}; use language::{Bias, ToOffset}; use lsp::LanguageServerId; use project::{ @@ -13,6 +13,7 @@ use project::{ ResolveState, }; use std::ops::Range; +use theme::ActiveTheme as _; use util::TryFutureExt; #[derive(Debug, Default)] @@ -485,40 +486,45 @@ pub fn show_link_definition( }); if any_definition_does_not_contain_current_location { - // todo!() - // // Highlight symbol using theme link definition highlight style - // let style = theme::current(cx).editor.link_definition; - // let highlight_range = - // symbol_range.unwrap_or_else(|| match &trigger_point { - // TriggerPoint::Text(trigger_anchor) => { - // let snapshot = &snapshot.buffer_snapshot; - // // If no symbol range returned from language server, use the surrounding word. - // let (offset_range, _) = - // snapshot.surrounding_word(*trigger_anchor); - // RangeInEditor::Text( - // snapshot.anchor_before(offset_range.start) - // ..snapshot.anchor_after(offset_range.end), - // ) - // } - // TriggerPoint::InlayHint(highlight, _, _) => { - // RangeInEditor::Inlay(highlight.clone()) - // } - // }); + let style = gpui::HighlightStyle { + underline: Some(gpui::UnderlineStyle { + thickness: px(1.), + ..Default::default() + }), + color: Some(gpui::red()), + ..Default::default() + }; + let highlight_range = + symbol_range.unwrap_or_else(|| match &trigger_point { + TriggerPoint::Text(trigger_anchor) => { + let snapshot = &snapshot.buffer_snapshot; + // If no symbol range returned from language server, use the surrounding word. + let (offset_range, _) = + snapshot.surrounding_word(*trigger_anchor); + RangeInEditor::Text( + snapshot.anchor_before(offset_range.start) + ..snapshot.anchor_after(offset_range.end), + ) + } + TriggerPoint::InlayHint(highlight, _, _) => { + RangeInEditor::Inlay(highlight.clone()) + } + }); - // match highlight_range { - // RangeInEditor::Text(text_range) => this - // .highlight_text::( - // vec![text_range], - // style, - // cx, - // ), - // RangeInEditor::Inlay(highlight) => this - // .highlight_inlays::( - // vec![highlight], - // style, - // cx, - // ), - // } + match highlight_range { + RangeInEditor::Text(text_range) => this + .highlight_text::( + vec![text_range], + style, + cx, + ), + RangeInEditor::Inlay(highlight) => this + .highlight_inlays::( + vec![highlight], + style, + cx, + ), + } } else { hide_link_definition(this, cx); } diff --git a/crates/editor2/src/selections_collection.rs b/crates/editor2/src/selections_collection.rs index bcf41f135b..6542ace5fb 100644 --- a/crates/editor2/src/selections_collection.rs +++ b/crates/editor2/src/selections_collection.rs @@ -595,31 +595,32 @@ impl<'a> MutableSelectionsCollection<'a> { self.select(selections) } - pub fn select_anchor_ranges>>(&mut self, ranges: I) { - todo!() - // let buffer = self.buffer.read(self.cx).snapshot(self.cx); - // let selections = ranges - // .into_iter() - // .map(|range| { - // let mut start = range.start; - // let mut end = range.end; - // let reversed = if start.cmp(&end, &buffer).is_gt() { - // mem::swap(&mut start, &mut end); - // true - // } else { - // false - // }; - // Selection { - // id: post_inc(&mut self.collection.next_selection_id), - // start, - // end, - // reversed, - // goal: SelectionGoal::None, - // } - // }) - // .collect::>(); - - // self.select_anchors(selections) + pub fn select_anchor_ranges(&mut self, ranges: I) + where + I: IntoIterator>, + { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + let selections = ranges + .into_iter() + .map(|range| { + let mut start = range.start; + let mut end = range.end; + let reversed = if start.cmp(&end, &buffer).is_gt() { + mem::swap(&mut start, &mut end); + true + } else { + false + }; + Selection { + id: post_inc(&mut self.collection.next_selection_id), + start, + end, + reversed, + goal: SelectionGoal::None, + } + }) + .collect::>(); + self.select_anchors(selections) } pub fn new_selection_id(&mut self) -> usize { diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index a0715b81a9..d57daca062 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -265,7 +265,9 @@ impl TextState { pub struct InteractiveText { element_id: ElementId, text: StyledText, - click_listener: Option)>>, + click_listener: + Option], InteractiveTextClickEvent, &mut WindowContext<'_>)>>, + clickable_ranges: Vec>, } struct InteractiveTextClickEvent { @@ -284,6 +286,7 @@ impl InteractiveText { element_id: id.into(), text, click_listener: None, + clickable_ranges: Vec::new(), } } @@ -292,7 +295,7 @@ impl InteractiveText { ranges: Vec>, listener: impl Fn(usize, &mut WindowContext<'_>) + 'static, ) -> 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() { if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index) { @@ -300,6 +303,7 @@ impl InteractiveText { } } })); + self.clickable_ranges = ranges; self } } @@ -334,6 +338,19 @@ impl Element for InteractiveText { fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { 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 mouse_down = state.mouse_down_index.clone(); 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) { click_listener( + &self.clickable_ranges, InteractiveTextClickEvent { mouse_down_index, mouse_up_index, diff --git a/crates/project2/src/lsp_command.rs b/crates/project2/src/lsp_command.rs index 94c277db1e..a2de52b21a 100644 --- a/crates/project2/src/lsp_command.rs +++ b/crates/project2/src/lsp_command.rs @@ -717,8 +717,9 @@ async fn location_links_from_lsp( })? .await?; - buffer.update(&mut cx, |origin_buffer, cx| { + cx.update(|cx| { let origin_location = origin_range.map(|origin_range| { + let origin_buffer = buffer.read(cx); let origin_start = origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left); let origin_end =