From f62e8616c879255e70052ae35ce7f98bffedac11 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 18 Sep 2020 23:40:11 +0300 Subject: [PATCH 01/18] Add imports in auto completion --- Cargo.lock | 160 ++++++------------ crates/assists/src/handlers/auto_import.rs | 3 +- .../extract_struct_from_enum_variant.rs | 3 +- .../replace_qualified_name_with_use.rs | 2 +- crates/assists/src/utils.rs | 3 +- crates/assists/src/utils/insert_use.rs | 14 +- crates/completion/Cargo.toml | 2 + crates/completion/src/completions.rs | 1 + .../src/completions/complete_magic.rs | 114 +++++++++++++ crates/completion/src/item.rs | 1 + crates/completion/src/lib.rs | 1 + crates/hir/src/code_model.rs | 10 +- crates/hir/src/lib.rs | 1 + crates/ide_db/src/imports_locator.rs | 14 +- 14 files changed, 199 insertions(+), 130 deletions(-) create mode 100644 crates/completion/src/completions/complete_magic.rs diff --git a/Cargo.lock b/Cargo.lock index 715a809789c7..eb4e43ad951f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "addr2line" -version = "0.14.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423" +checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" dependencies = [ "gimli", ] @@ -26,9 +26,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.34" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" +checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c" [[package]] name = "anymap" @@ -42,9 +42,9 @@ version = "0.0.0" [[package]] name = "arrayvec" -version = "0.5.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" [[package]] name = "assists" @@ -81,9 +81,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.54" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2baad346b2d4e94a24347adeee9c7a93f412ee94b9cc26e5b59dea23848e9f28" +checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e" dependencies = [ "addr2line", "cfg-if 1.0.0", @@ -132,7 +132,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5a5f7b42f606b7f23674f6f4d877628350682bc40687d3fae65679a58d55345" dependencies = [ - "semver 0.11.0", + "semver", "serde", "serde_json", ] @@ -255,6 +255,7 @@ version = "0.0.0" dependencies = [ "assists", "base_db", + "either", "expect-test", "hir", "ide_db", @@ -270,17 +271,17 @@ dependencies = [ [[package]] name = "const_fn" -version = "0.4.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" +checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2" [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 0.1.10", ] [[package]] @@ -417,11 +418,11 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "flate2" -version = "1.0.19" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" +checksum = "da80be589a72651dcda34d8b35bcdc9b7254ad06325611074d9cc0fbb19f60ee" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 0.1.10", "crc32fast", "libc", "miniz_oxide", @@ -470,9 +471,9 @@ dependencies = [ [[package]] name = "fst" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d79238883cf0307100b90aba4a755d8051a3182305dfe7f649a1e9dc0517006f" +checksum = "a7293de202dbfe786c0b3fe6110a027836c5438ed06db7b715c9955ff4bfea51" [[package]] name = "fuchsia-zircon" @@ -490,24 +491,11 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -[[package]] -name = "generator" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cdc09201b2e8ca1b19290cf7e65de2246b8e91fb6874279722189c4de7b94dc" -dependencies = [ - "cc", - "libc", - "log", - "rustc_version", - "winapi 0.3.9", -] - [[package]] name = "gimli" -version = "0.23.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" +checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" [[package]] name = "goblin" @@ -735,11 +723,11 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.8" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb1fc4429a33e1f80d41dc9fea4d108a88bec1de8053878898ae448a0b52f613" +checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 0.1.10", ] [[package]] @@ -796,17 +784,17 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.80" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" [[package]] name = "libloading" -version = "0.6.5" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1090080fe06ec2648d0da3881d9453d97e71a45f00eb179af7fdd7e3f686fdb0" +checksum = "3557c9384f7f757f6d139cd3a4c62ef4e850696c16bf27924a5538c8a09717a1" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 0.1.10", "winapi 0.3.9", ] @@ -837,19 +825,6 @@ dependencies = [ "cfg-if 0.1.10", ] -[[package]] -name = "loom" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0e8460f2f2121162705187214720353c517b97bdfb3494c0b1e33d83ebe4bed" -dependencies = [ - "cfg-if 0.1.10", - "generator", - "scoped-tls", - "serde", - "serde_json", -] - [[package]] name = "lsp-server" version = "0.4.1" @@ -912,9 +887,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.3.4" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "memmap" @@ -1010,9 +985,9 @@ dependencies = [ [[package]] name = "notify" -version = "5.0.0-pre.4" +version = "5.0.0-pre.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8b946889dfdad884379cd56367d93b6d0ce8889cc027d26a69a3a31c0a03bb5" +checksum = "77d03607cf88b4b160ba0e9ed425fff3cee3b55ac813f0c685b3a3772da37d0e" dependencies = [ "anymap", "bitflags", @@ -1030,9 +1005,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" dependencies = [ "autocfg", "num-traits", @@ -1040,9 +1015,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" dependencies = [ "autocfg", ] @@ -1059,9 +1034,9 @@ dependencies = [ [[package]] name = "object" -version = "0.22.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" +checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693" [[package]] name = "once_cell" @@ -1310,9 +1285,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "regex" -version = "1.4.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" +checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b" dependencies = [ "regex-syntax", ] @@ -1329,9 +1304,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.21" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" +checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" [[package]] name = "rowan" @@ -1404,9 +1379,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.18" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" +checksum = "b2610b7f643d18c87dff3b489950269617e6601a51f1f05aa5daefee36f64f0b" [[package]] name = "rustc-hash" @@ -1414,15 +1389,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - [[package]] name = "ryu" version = "1.0.5" @@ -1490,40 +1456,25 @@ dependencies = [ [[package]] name = "scroll_derive" -version = "0.10.4" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12bd20b94c7cdfda8c7ba9b92ad0d9a56e3fa018c25fca83b51aa664c9b4c0d" +checksum = "6dfde5d1531034db129e95c76ac857e2baecea3443579d493d02224950b0fb6d" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser 0.7.0", -] - [[package]] name = "semver" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ - "semver-parser 0.10.1", + "semver-parser", "serde", ] -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "semver-parser" version = "0.10.1" @@ -1577,12 +1528,11 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.0" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4921be914e16899a80adefb821f8ddb7974e3f1250223575a44ed994882127" +checksum = "06d5a3f5166fb5b42a5439f2eee8b9de149e235961e3eb21c5808fc3ea17ff3e" dependencies = [ "lazy_static", - "loom", ] [[package]] @@ -1626,9 +1576,9 @@ version = "0.0.0" [[package]] name = "syn" -version = "1.0.48" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +checksum = "ea9c5432ff16d6152371f808fb5a871cd67368171b09bb21b43df8e4a47a3556" dependencies = [ "proc-macro2", "quote", @@ -1805,9 +1755,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.2.15" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401" +checksum = "4ef0a5e15477aa303afbfac3a44cba9b6430fdaad52423b1e6c0dbbe28c3eedd" dependencies = [ "ansi_term", "chrono", diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs index 37dd6126618e..d665837a2f3b 100644 --- a/crates/assists/src/handlers/auto_import.rs +++ b/crates/assists/src/handlers/auto_import.rs @@ -98,7 +98,8 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; let group = import_group_message(import_assets.import_candidate()); - let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?; + let scope = + ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), &ctx.sema)?; for (import, _) in proposed_imports { acc.add_group( &group, diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs index 067afabf2edf..cac77c49bbc4 100644 --- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs @@ -143,8 +143,7 @@ fn insert_import( if let Some(mut mod_path) = mod_path { mod_path.segments.pop(); mod_path.segments.push(variant_hir_name.clone()); - let scope = ImportScope::find_insert_use_container(scope_node, ctx)?; - + let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?; *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge); } Some(()) diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs index d7e1d95805e6..a66db9ae3a15 100644 --- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs @@ -34,7 +34,7 @@ pub(crate) fn replace_qualified_name_with_use( } let target = path.syntax().text_range(); - let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?; + let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?; let syntax = scope.as_syntax_node(); acc.add( AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 7bd338e99c75..caabc44dea50 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs @@ -21,8 +21,7 @@ use crate::{ ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, }; -pub use insert_use::MergeBehaviour; -pub(crate) use insert_use::{insert_use, ImportScope}; +pub use insert_use::{insert_use, ImportScope, MergeBehaviour}; pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { let mut segments = Vec::new(); diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs index af3fc96b6c2f..1aa727e11fe5 100644 --- a/crates/assists/src/utils/insert_use.rs +++ b/crates/assists/src/utils/insert_use.rs @@ -1,6 +1,8 @@ //! Handle syntactic aspects of inserting a new `use`. use std::{cmp::Ordering, iter::successors}; +use hir::Semantics; +use ide_db::RootDatabase; use itertools::{EitherOrBoth, Itertools}; use syntax::{ algo::SyntaxRewriter, @@ -14,7 +16,7 @@ use syntax::{ use test_utils::mark; #[derive(Debug)] -pub(crate) enum ImportScope { +pub enum ImportScope { File(ast::SourceFile), Module(ast::ItemList), } @@ -31,14 +33,14 @@ impl ImportScope { } /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. - pub(crate) fn find_insert_use_container( + pub fn find_insert_use_container( position: &SyntaxNode, - ctx: &crate::assist_context::AssistContext, + sema: &Semantics<'_, RootDatabase>, ) -> Option { - ctx.sema.ancestors_with_macros(position.clone()).find_map(Self::from) + sema.ancestors_with_macros(position.clone()).find_map(Self::from) } - pub(crate) fn as_syntax_node(&self) -> &SyntaxNode { + pub fn as_syntax_node(&self) -> &SyntaxNode { match self { ImportScope::File(file) => file.syntax(), ImportScope::Module(item_list) => item_list.syntax(), @@ -88,7 +90,7 @@ fn is_inner_comment(token: SyntaxToken) -> bool { } /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. -pub(crate) fn insert_use<'a>( +pub fn insert_use<'a>( scope: &ImportScope, path: ast::Path, merge: Option, diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml index 3015ec9e0ea4..799b4a3d5445 100644 --- a/crates/completion/Cargo.toml +++ b/crates/completion/Cargo.toml @@ -13,6 +13,7 @@ doctest = false itertools = "0.9.0" log = "0.4.8" rustc-hash = "1.1.0" +either = "1.6.1" assists = { path = "../assists", version = "0.0.0" } stdx = { path = "../stdx", version = "0.0.0" } @@ -21,6 +22,7 @@ text_edit = { path = "../text_edit", version = "0.0.0" } base_db = { path = "../base_db", version = "0.0.0" } ide_db = { path = "../ide_db", version = "0.0.0" } profile = { path = "../profile", version = "0.0.0" } +assists = { path = "../assists", version = "0.0.0" } test_utils = { path = "../test_utils", version = "0.0.0" } # completions crate should depend only on the top-level `hir` package. if you need diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 75dbb1a23bb5..99db5f9980f3 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -13,6 +13,7 @@ pub(crate) mod postfix; pub(crate) mod macro_in_item_position; pub(crate) mod trait_impl; pub(crate) mod mod_; +pub(crate) mod complete_magic; use hir::{ModPath, ScopeDef, Type}; diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs new file mode 100644 index 000000000000..857a0b620938 --- /dev/null +++ b/crates/completion/src/completions/complete_magic.rs @@ -0,0 +1,114 @@ +//! TODO kb move this into the complete_unqualified_path when starts to work properly + +use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; +use hir::Query; +use itertools::Itertools; +use syntax::AstNode; +use text_edit::TextEdit; + +use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, CompletionItemKind}; + +use super::Completions; + +pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { + if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { + return None; + } + let current_module = ctx.scope.module()?; + let anchor = ctx.name_ref_syntax.as_ref()?; + let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; + // TODO kb now this is the whole file, which is not disjoint with any other change in the same file, fix it + // otherwise it's impossible to correctly add the use statement and also change the completed text into something more meaningful + let import_syntax = import_scope.as_syntax_node(); + + // TODO kb consider heuristics, such as "don't show `hash_map` import if `HashMap` is the import for completion" + // TODO kb module functions are not completed, consider `std::io::stdin` one + let potential_import_name = ctx.token.to_string(); + + let possible_imports = ctx + .krate? + // TODO kb use imports_locator instead? + .query_external_importables(ctx.db, Query::new(&potential_import_name).limit(40)) + .unique() + .filter_map(|import_candidate| match import_candidate { + either::Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), + either::Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), + }) + .filter_map(|mod_path| { + let correct_qualifier = mod_path.segments.last()?.to_string(); + let rewriter = + insert_use(&import_scope, mod_path_to_ast(&mod_path), Some(MergeBehaviour::Full)); + let rewritten_node = rewriter.rewrite(import_syntax); + let insert_use_edit = + TextEdit::replace(import_syntax.text_range(), rewritten_node.to_string()); + let mut completion_edit = + TextEdit::replace(anchor.syntax().text_range(), correct_qualifier); + completion_edit.union(insert_use_edit).expect("TODO kb"); + + let completion_item: CompletionItem = CompletionItem::new( + CompletionKind::Magic, + ctx.source_range(), + mod_path.to_string(), + ) + .kind(CompletionItemKind::Struct) + .text_edit(completion_edit) + .into(); + Some(completion_item) + }); + acc.add_all(possible_imports); + + Some(()) +} + +#[cfg(test)] +mod tests { + use expect_test::{expect, Expect}; + + use crate::{ + item::CompletionKind, + test_utils::{check_edit, completion_list}, + }; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Magic); + expect.assert_eq(&actual) + } + + #[test] + fn case_insensitive_magic_completion_works() { + check( + r#" +//- /lib.rs crate:dep +pub struct TestStruct; + +//- /main.rs crate:main deps:dep +fn main() { + teru<|> +} +"#, + expect![[r#" + st dep::TestStruct + "#]], + ); + + check_edit( + "dep::TestStruct", + r#" +//- /lib.rs crate:dep +pub struct TestStruct; + +//- /main.rs crate:main deps:dep +fn main() { + teru<|> +} +"#, + r#" +use dep::TestStruct; + +fn main() { + TestStruct +} +"#, + ); + } +} diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index 6d1d085f4bc7..f23913935c0e 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs @@ -31,6 +31,7 @@ pub struct CompletionItem { /// /// Typically, replaces `source_range` with new identifier. text_edit: TextEdit, + insert_text_format: InsertTextFormat, /// What item (struct, function, etc) are we completing. diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index cb6e0554e9a7..e920fa6b59d8 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs @@ -118,6 +118,7 @@ pub fn completions( completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); completions::trait_impl::complete_trait_impl(&mut acc, &ctx); completions::mod_::complete_mod(&mut acc, &ctx); + completions::complete_magic::complete_magic(&mut acc, &ctx); Some(acc) } diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index 30a5e45809b9..37ed092ad0ad 100644 --- a/crates/hir/src/code_model.rs +++ b/crates/hir/src/code_model.rs @@ -110,15 +110,9 @@ impl Crate { pub fn query_external_importables( self, db: &dyn DefDatabase, - query: &str, + query: import_map::Query, ) -> impl Iterator> { - import_map::search_dependencies( - db, - self.into(), - import_map::Query::new(query).anchor_end().case_sensitive().limit(40), - ) - .into_iter() - .map(|item| match item { + import_map::search_dependencies(db, self.into(), query).into_iter().map(|item| match item { ItemInNs::Types(mod_id) | ItemInNs::Values(mod_id) => Either::Left(mod_id.into()), ItemInNs::Macros(mac_id) => Either::Right(mac_id.into()), }) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 0d184379f102..ad58a7cfe075 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -49,6 +49,7 @@ pub use hir_def::{ builtin_type::BuiltinType, docs::Documentation, find_path::PrefixKind, + import_map::Query, item_scope::ItemInNs, nameres::ModuleSource, path::{ModPath, PathKind}, diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs index df74be00bb20..e4f4b54274fb 100644 --- a/crates/ide_db/src/imports_locator.rs +++ b/crates/ide_db/src/imports_locator.rs @@ -1,12 +1,12 @@ //! This module contains an import search funcionality that is provided to the assists module. //! Later, this should be moved away to a separate crate that is accessible from the assists module. -use hir::{Crate, MacroDef, ModuleDef, Semantics}; +use hir::{Crate, MacroDef, ModuleDef, Query as ImportMapQuery, Semantics}; use syntax::{ast, AstNode, SyntaxKind::NAME}; use crate::{ defs::{Definition, NameClass}, - symbol_index::{self, FileSymbol, Query}, + symbol_index::{self, FileSymbol, Query as SymbolQuery}, RootDatabase, }; use either::Either; @@ -21,12 +21,16 @@ pub fn find_imports<'a>( let db = sema.db; // Query dependencies first. - let mut candidates: FxHashSet<_> = - krate.query_external_importables(db, name_to_import).collect(); + let mut candidates: FxHashSet<_> = krate + .query_external_importables( + db, + ImportMapQuery::new(name_to_import).anchor_end().case_sensitive().limit(40), + ) + .collect(); // Query the local crate using the symbol index. let local_results = { - let mut query = Query::new(name_to_import.to_string()); + let mut query = SymbolQuery::new(name_to_import.to_string()); query.exact(); query.limit(40); symbol_index::crate_symbols(db, krate.into(), query) From 6866a05e6ff4052bd45744d54f5c032aa737c36a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 8 Nov 2020 00:23:05 +0200 Subject: [PATCH 02/18] Use rewriter api to add both changes --- .../src/completions/complete_magic.rs | 62 +++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs index 857a0b620938..9242b860cc0d 100644 --- a/crates/completion/src/completions/complete_magic.rs +++ b/crates/completion/src/completions/complete_magic.rs @@ -3,7 +3,7 @@ use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; use hir::Query; use itertools::Itertools; -use syntax::AstNode; +use syntax::{algo, AstNode}; use text_edit::TextEdit; use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, CompletionItemKind}; @@ -17,9 +17,6 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let current_module = ctx.scope.module()?; let anchor = ctx.name_ref_syntax.as_ref()?; let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; - // TODO kb now this is the whole file, which is not disjoint with any other change in the same file, fix it - // otherwise it's impossible to correctly add the use statement and also change the completed text into something more meaningful - let import_syntax = import_scope.as_syntax_node(); // TODO kb consider heuristics, such as "don't show `hash_map` import if `HashMap` is the import for completion" // TODO kb module functions are not completed, consider `std::io::stdin` one @@ -35,15 +32,16 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> either::Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), }) .filter_map(|mod_path| { + let mut builder = TextEdit::builder(); + let correct_qualifier = mod_path.segments.last()?.to_string(); + builder.replace(anchor.syntax().text_range(), correct_qualifier); + + // TODO kb: assists already have the merge behaviour setting, need to unite both let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), Some(MergeBehaviour::Full)); - let rewritten_node = rewriter.rewrite(import_syntax); - let insert_use_edit = - TextEdit::replace(import_syntax.text_range(), rewritten_node.to_string()); - let mut completion_edit = - TextEdit::replace(anchor.syntax().text_range(), correct_qualifier); - completion_edit.union(insert_use_edit).expect("TODO kb"); + let old_ast = rewriter.rewrite_root()?; + algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); let completion_item: CompletionItem = CompletionItem::new( CompletionKind::Magic, @@ -51,7 +49,7 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> mod_path.to_string(), ) .kind(CompletionItemKind::Struct) - .text_edit(completion_edit) + .text_edit(builder.finish()) .into(); Some(completion_item) }); @@ -74,6 +72,48 @@ mod tests { expect.assert_eq(&actual) } + #[test] + fn function_magic_completion() { + check( + r#" +//- /lib.rs crate:dep +pub mod io { + pub fn stdin() {} +}; + +//- /main.rs crate:main deps:dep +fn main() { + stdi<|> +} +"#, + expect![[r#" + st dep::io::stdin + "#]], + ); + + check_edit( + "dep::io::stdin", + r#" +//- /lib.rs crate:dep +pub mod io { + pub fn stdin() {} +}; + +//- /main.rs crate:main deps:dep +fn main() { + stdi<|> +} +"#, + r#" +use dep::io::stdin; + +fn main() { + stdin +} +"#, + ); + } + #[test] fn case_insensitive_magic_completion_works() { check( From 6ab97244b88b180c1cafd5b47533bd4366a09177 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 10 Nov 2020 11:08:59 +0200 Subject: [PATCH 03/18] Tidy up the tests --- .../src/completions/complete_magic.rs | 64 +++++-------------- 1 file changed, 15 insertions(+), 49 deletions(-) diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs index 9242b860cc0d..15af2190dc3e 100644 --- a/crates/completion/src/completions/complete_magic.rs +++ b/crates/completion/src/completions/complete_magic.rs @@ -10,6 +10,7 @@ use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, Co use super::Completions; +// TODO kb when typing, completes partial results, need to rerun manually to see the proper ones pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { return None; @@ -19,7 +20,7 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; // TODO kb consider heuristics, such as "don't show `hash_map` import if `HashMap` is the import for completion" - // TODO kb module functions are not completed, consider `std::io::stdin` one + // also apply completion ordering let potential_import_name = ctx.token.to_string(); let possible_imports = ctx @@ -38,6 +39,7 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> builder.replace(anchor.syntax().text_range(), correct_qualifier); // TODO kb: assists already have the merge behaviour setting, need to unite both + // also consider a settings toggle for this particular feature? let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), Some(MergeBehaviour::Full)); let old_ast = rewriter.rewrite_root()?; @@ -60,37 +62,10 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> #[cfg(test)] mod tests { - use expect_test::{expect, Expect}; - - use crate::{ - item::CompletionKind, - test_utils::{check_edit, completion_list}, - }; - - fn check(ra_fixture: &str, expect: Expect) { - let actual = completion_list(ra_fixture, CompletionKind::Magic); - expect.assert_eq(&actual) - } + use crate::test_utils::check_edit; #[test] fn function_magic_completion() { - check( - r#" -//- /lib.rs crate:dep -pub mod io { - pub fn stdin() {} -}; - -//- /main.rs crate:main deps:dep -fn main() { - stdi<|> -} -"#, - expect![[r#" - st dep::io::stdin - "#]], - ); - check_edit( "dep::io::stdin", r#" @@ -116,37 +91,28 @@ fn main() { #[test] fn case_insensitive_magic_completion_works() { - check( - r#" -//- /lib.rs crate:dep -pub struct TestStruct; - -//- /main.rs crate:main deps:dep -fn main() { - teru<|> -} -"#, - expect![[r#" - st dep::TestStruct - "#]], - ); - check_edit( - "dep::TestStruct", + "dep::some_module::ThirdStruct", r#" //- /lib.rs crate:dep -pub struct TestStruct; +pub struct FirstStruct; +pub mod some_module { + pub struct SecondStruct; + pub struct ThirdStruct; +} //- /main.rs crate:main deps:dep +use dep::{FirstStruct, some_module::SecondStruct}; + fn main() { - teru<|> + this<|> } "#, r#" -use dep::TestStruct; +use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}}; fn main() { - TestStruct + ThirdStruct } "#, ); From 1e458efe628215dfc07943f8dd39f66ac059d3de Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 10 Nov 2020 12:00:42 +0200 Subject: [PATCH 04/18] Add braces to functions and macros --- .../src/completions/complete_magic.rs | 66 +++++++++++++++-- crates/completion/src/render/macro_.rs | 72 ++++++++++--------- 2 files changed, 99 insertions(+), 39 deletions(-) diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs index 15af2190dc3e..4cf21e19df0f 100644 --- a/crates/completion/src/completions/complete_magic.rs +++ b/crates/completion/src/completions/complete_magic.rs @@ -1,7 +1,8 @@ //! TODO kb move this into the complete_unqualified_path when starts to work properly use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; -use hir::Query; +use either::Either; +use hir::{db::HirDatabase, MacroDef, ModuleDef, Query}; use itertools::Itertools; use syntax::{algo, AstNode}; use text_edit::TextEdit; @@ -28,14 +29,23 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> // TODO kb use imports_locator instead? .query_external_importables(ctx.db, Query::new(&potential_import_name).limit(40)) .unique() - .filter_map(|import_candidate| match import_candidate { - either::Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), - either::Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), + .filter_map(|import_candidate| { + let use_path = match import_candidate { + Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), + Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), + }?; + // TODO kb need to omit braces when there are some already. + // maybe remove braces completely? + Some((use_path, additional_completion(ctx.db, import_candidate))) }) - .filter_map(|mod_path| { + .filter_map(|(mod_path, additional_completion)| { let mut builder = TextEdit::builder(); - let correct_qualifier = mod_path.segments.last()?.to_string(); + let correct_qualifier = format!( + "{}{}", + mod_path.segments.last()?, + additional_completion.unwrap_or_default() + ); builder.replace(anchor.syntax().text_range(), correct_qualifier); // TODO kb: assists already have the merge behaviour setting, need to unite both @@ -60,6 +70,21 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Some(()) } +fn additional_completion( + db: &dyn HirDatabase, + import_candidate: Either, +) -> Option { + match import_candidate { + Either::Left(ModuleDef::Function(_)) => Some("()".to_string()), + Either::Right(macro_def) => { + let (left_brace, right_brace) = + crate::render::macro_::guess_macro_braces(db, macro_def); + Some(format!("!{}{}", left_brace, right_brace)) + } + _ => None, + } +} + #[cfg(test)] mod tests { use crate::test_utils::check_edit; @@ -83,7 +108,34 @@ fn main() { use dep::io::stdin; fn main() { - stdin + stdin() +} +"#, + ); + } + + #[test] + fn macro_magic_completion() { + check_edit( + "dep::macro_with_curlies", + r#" +//- /lib.rs crate:dep +/// Please call me as macro_with_curlies! {} +#[macro_export] +macro_rules! macro_with_curlies { + () => {} +} + +//- /main.rs crate:main deps:dep +fn main() { + curli<|> +} +"#, + r#" +use dep::macro_with_curlies; + +fn main() { + macro_with_curlies! {} } "#, ); diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index 96be59cc336a..b41c00b987d6 100644 --- a/crates/completion/src/render/macro_.rs +++ b/crates/completion/src/render/macro_.rs @@ -1,6 +1,6 @@ //! Renderer for macro invocations. -use hir::{Documentation, HasSource}; +use hir::{db::HirDatabase, Documentation, HasAttrs, HasSource}; use syntax::display::macro_label; use test_utils::mark; @@ -27,12 +27,48 @@ struct MacroRender<'a> { ket: &'static str, } +pub fn guess_macro_braces( + db: &dyn HirDatabase, + macro_: hir::MacroDef, +) -> (&'static str, &'static str) { + let macro_name = match macro_.name(db) { + Some(name) => name.to_string(), + None => return ("(", ")"), + }; + let macro_docs = macro_.docs(db); + let macro_docs = macro_docs.as_ref().map(Documentation::as_str).unwrap_or(""); + + let mut votes = [0, 0, 0]; + for (idx, s) in macro_docs.match_indices(¯o_name) { + let (before, after) = (¯o_docs[..idx], ¯o_docs[idx + s.len()..]); + // Ensure to match the full word + if after.starts_with('!') + && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) + { + // It may have spaces before the braces like `foo! {}` + match after[1..].chars().find(|&c| !c.is_whitespace()) { + Some('{') => votes[0] += 1, + Some('[') => votes[1] += 1, + Some('(') => votes[2] += 1, + _ => {} + } + } + } + + // Insert a space before `{}`. + // We prefer the last one when some votes equal. + let (_vote, (bra, ket)) = votes + .iter() + .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) + .max_by_key(|&(&vote, _)| vote) + .unwrap(); + (*bra, *ket) +} + impl<'a> MacroRender<'a> { fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { let docs = ctx.docs(macro_); - let docs_str = docs.as_ref().map_or("", |s| s.as_str()); - let (bra, ket) = guess_macro_braces(&name, docs_str); - + let (bra, ket) = guess_macro_braces(ctx.db(), macro_); MacroRender { ctx, name, macro_, docs, bra, ket } } @@ -97,34 +133,6 @@ impl<'a> MacroRender<'a> { } } -fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { - let mut votes = [0, 0, 0]; - for (idx, s) in docs.match_indices(¯o_name) { - let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); - // Ensure to match the full word - if after.starts_with('!') - && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) - { - // It may have spaces before the braces like `foo! {}` - match after[1..].chars().find(|&c| !c.is_whitespace()) { - Some('{') => votes[0] += 1, - Some('[') => votes[1] += 1, - Some('(') => votes[2] += 1, - _ => {} - } - } - } - - // Insert a space before `{}`. - // We prefer the last one when some votes equal. - let (_vote, (bra, ket)) = votes - .iter() - .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) - .max_by_key(|&(&vote, _)| vote) - .unwrap(); - (*bra, *ket) -} - #[cfg(test)] mod tests { use test_utils::mark; From 0e050fc3eba251f3debf964c7779c522e5639cd8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 10 Nov 2020 23:40:07 +0200 Subject: [PATCH 05/18] Allow to configure the merge behavior --- crates/completion/src/completions/complete_magic.rs | 7 ++----- crates/completion/src/config.rs | 4 ++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs index 4cf21e19df0f..58509fc5babf 100644 --- a/crates/completion/src/completions/complete_magic.rs +++ b/crates/completion/src/completions/complete_magic.rs @@ -1,6 +1,6 @@ //! TODO kb move this into the complete_unqualified_path when starts to work properly -use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; +use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; use either::Either; use hir::{db::HirDatabase, MacroDef, ModuleDef, Query}; use itertools::Itertools; @@ -48,10 +48,7 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> ); builder.replace(anchor.syntax().text_range(), correct_qualifier); - // TODO kb: assists already have the merge behaviour setting, need to unite both - // also consider a settings toggle for this particular feature? - let rewriter = - insert_use(&import_scope, mod_path_to_ast(&mod_path), Some(MergeBehaviour::Full)); + let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); let old_ast = rewriter.rewrite_root()?; algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); diff --git a/crates/completion/src/config.rs b/crates/completion/src/config.rs index 71b49ace8bfb..82874ff256a6 100644 --- a/crates/completion/src/config.rs +++ b/crates/completion/src/config.rs @@ -4,12 +4,15 @@ //! module, and we use to statically check that we only produce snippet //! completions if we are allowed to. +use assists::utils::MergeBehaviour; + #[derive(Clone, Debug, PartialEq, Eq)] pub struct CompletionConfig { pub enable_postfix_completions: bool, pub add_call_parenthesis: bool, pub add_call_argument_snippets: bool, pub snippet_cap: Option, + pub merge: Option, } impl CompletionConfig { @@ -30,6 +33,7 @@ impl Default for CompletionConfig { add_call_parenthesis: true, add_call_argument_snippets: true, snippet_cap: Some(SnippetCap { _private: () }), + merge: Some(MergeBehaviour::Full), } } } From d1556550f83b7b8e9dd42c80ab6e08a632dfd256 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Nov 2020 18:32:30 +0200 Subject: [PATCH 06/18] Rename the module --- crates/completion/src/completions.rs | 2 +- .../src/completions/{complete_magic.rs => magic.rs} | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) rename crates/completion/src/completions/{complete_magic.rs => magic.rs} (92%) diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 99db5f9980f3..4abb101564d0 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -13,7 +13,7 @@ pub(crate) mod postfix; pub(crate) mod macro_in_item_position; pub(crate) mod trait_impl; pub(crate) mod mod_; -pub(crate) mod complete_magic; +pub(crate) mod magic; use hir::{ModPath, ScopeDef, Type}; diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/magic.rs similarity index 92% rename from crates/completion/src/completions/complete_magic.rs rename to crates/completion/src/completions/magic.rs index 58509fc5babf..34fc358470d5 100644 --- a/crates/completion/src/completions/complete_magic.rs +++ b/crates/completion/src/completions/magic.rs @@ -11,7 +11,7 @@ use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, Co use super::Completions; -// TODO kb when typing, completes partial results, need to rerun manually to see the proper ones +// TODO kb add a setting toggle for this feature? pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { return None; @@ -20,8 +20,6 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let anchor = ctx.name_ref_syntax.as_ref()?; let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; - // TODO kb consider heuristics, such as "don't show `hash_map` import if `HashMap` is the import for completion" - // also apply completion ordering let potential_import_name = ctx.token.to_string(); let possible_imports = ctx @@ -34,8 +32,6 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), }?; - // TODO kb need to omit braces when there are some already. - // maybe remove braces completely? Some((use_path, additional_completion(ctx.db, import_candidate))) }) .filter_map(|(mod_path, additional_completion)| { From 4c8edd003aa447bd2da10fd81b24f582deacdc11 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Nov 2020 19:16:56 +0200 Subject: [PATCH 07/18] Use imports_locator --- .../replace_derive_with_manual_impl.rs | 28 +++---- crates/assists/src/utils/import_assets.rs | 34 +++++---- crates/completion/src/completions/magic.rs | 74 +++++++++---------- crates/completion/src/lib.rs | 2 +- crates/hir/src/lib.rs | 2 +- crates/ide_db/src/imports_locator.rs | 65 +++++++++++----- 6 files changed, 119 insertions(+), 86 deletions(-) diff --git a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs index 82625516c211..453a6cebfb8c 100644 --- a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs @@ -62,19 +62,21 @@ pub(crate) fn replace_derive_with_manual_impl( let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; let current_crate = current_module.krate(); - let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text()) - .into_iter() - .filter_map(|candidate: either::Either| match candidate { - either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_), - _ => None, - }) - .flat_map(|trait_| { - current_module - .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) - .as_ref() - .map(mod_path_to_ast) - .zip(Some(trait_)) - }); + let found_traits = + imports_locator::find_exact_imports(&ctx.sema, current_crate, trait_token.text()) + .filter_map( + |candidate: either::Either| match candidate { + either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_), + _ => None, + }, + ) + .flat_map(|trait_| { + current_module + .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) + .as_ref() + .map(mod_path_to_ast) + .zip(Some(trait_)) + }); let mut no_traits_found = true; for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs index f47edbb76585..89e62ba79c0c 100644 --- a/crates/assists/src/utils/import_assets.rs +++ b/crates/assists/src/utils/import_assets.rs @@ -179,21 +179,25 @@ impl ImportAssets { } }; - let mut res = imports_locator::find_imports(sema, current_crate, &self.get_search_query()) - .into_iter() - .filter_map(filter) - .filter_map(|candidate| { - let item: hir::ItemInNs = candidate.either(Into::into, Into::into); - if let Some(prefix_kind) = prefixed { - self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind) - } else { - self.module_with_name_to_import.find_use_path(db, item) - } - .map(|path| (path, item)) - }) - .filter(|(use_path, _)| !use_path.segments.is_empty()) - .take(20) - .collect::>(); + let mut res = + imports_locator::find_exact_imports(sema, current_crate, &self.get_search_query()) + .filter_map(filter) + .filter_map(|candidate| { + let item: hir::ItemInNs = candidate.either(Into::into, Into::into); + if let Some(prefix_kind) = prefixed { + self.module_with_name_to_import.find_use_path_prefixed( + db, + item, + prefix_kind, + ) + } else { + self.module_with_name_to_import.find_use_path(db, item) + } + .map(|path| (path, item)) + }) + .filter(|(use_path, _)| !use_path.segments.is_empty()) + .take(20) + .collect::>(); res.sort_by_key(|(path, _)| path.clone()); res } diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs index 34fc358470d5..272c9a35469c 100644 --- a/crates/completion/src/completions/magic.rs +++ b/crates/completion/src/completions/magic.rs @@ -2,8 +2,8 @@ use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; use either::Either; -use hir::{db::HirDatabase, MacroDef, ModuleDef, Query}; -use itertools::Itertools; +use hir::{db::HirDatabase, MacroDef, ModuleDef}; +use ide_db::imports_locator; use syntax::{algo, AstNode}; use text_edit::TextEdit; @@ -22,42 +22,40 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let potential_import_name = ctx.token.to_string(); - let possible_imports = ctx - .krate? - // TODO kb use imports_locator instead? - .query_external_importables(ctx.db, Query::new(&potential_import_name).limit(40)) - .unique() - .filter_map(|import_candidate| { - let use_path = match import_candidate { - Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), - Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), - }?; - Some((use_path, additional_completion(ctx.db, import_candidate))) - }) - .filter_map(|(mod_path, additional_completion)| { - let mut builder = TextEdit::builder(); - - let correct_qualifier = format!( - "{}{}", - mod_path.segments.last()?, - additional_completion.unwrap_or_default() - ); - builder.replace(anchor.syntax().text_range(), correct_qualifier); - - let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); - let old_ast = rewriter.rewrite_root()?; - algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); - - let completion_item: CompletionItem = CompletionItem::new( - CompletionKind::Magic, - ctx.source_range(), - mod_path.to_string(), - ) - .kind(CompletionItemKind::Struct) - .text_edit(builder.finish()) - .into(); - Some(completion_item) - }); + let possible_imports = + imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name) + .filter_map(|import_candidate| { + let use_path = match import_candidate { + Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), + Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), + }?; + Some((use_path, additional_completion(ctx.db, import_candidate))) + }) + .filter_map(|(mod_path, additional_completion)| { + let mut builder = TextEdit::builder(); + + let correct_qualifier = format!( + "{}{}", + mod_path.segments.last()?, + additional_completion.unwrap_or_default() + ); + builder.replace(anchor.syntax().text_range(), correct_qualifier); + + let rewriter = + insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); + let old_ast = rewriter.rewrite_root()?; + algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); + + let completion_item: CompletionItem = CompletionItem::new( + CompletionKind::Magic, + ctx.source_range(), + mod_path.to_string(), + ) + .kind(CompletionItemKind::Struct) + .text_edit(builder.finish()) + .into(); + Some(completion_item) + }); acc.add_all(possible_imports); Some(()) diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index e920fa6b59d8..8323af8b285e 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs @@ -118,7 +118,7 @@ pub fn completions( completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); completions::trait_impl::complete_trait_impl(&mut acc, &ctx); completions::mod_::complete_mod(&mut acc, &ctx); - completions::complete_magic::complete_magic(&mut acc, &ctx); + completions::magic::complete_magic(&mut acc, &ctx); Some(acc) } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index ad58a7cfe075..4b7ea3aa9f68 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -49,7 +49,7 @@ pub use hir_def::{ builtin_type::BuiltinType, docs::Documentation, find_path::PrefixKind, - import_map::Query, + import_map::Query as ExternalImportablesQuery, item_scope::ItemInNs, nameres::ModuleSource, path::{ModPath, PathKind}, diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs index e4f4b54274fb..71fb7207b661 100644 --- a/crates/ide_db/src/imports_locator.rs +++ b/crates/ide_db/src/imports_locator.rs @@ -1,40 +1,69 @@ //! This module contains an import search funcionality that is provided to the assists module. //! Later, this should be moved away to a separate crate that is accessible from the assists module. -use hir::{Crate, MacroDef, ModuleDef, Query as ImportMapQuery, Semantics}; +use hir::{Crate, ExternalImportablesQuery, MacroDef, ModuleDef, Semantics}; use syntax::{ast, AstNode, SyntaxKind::NAME}; use crate::{ defs::{Definition, NameClass}, - symbol_index::{self, FileSymbol, Query as SymbolQuery}, + symbol_index::{self, FileSymbol, Query as LocalImportablesQuery}, RootDatabase, }; use either::Either; use rustc_hash::FxHashSet; -pub fn find_imports<'a>( +pub fn find_exact_imports<'a>( sema: &Semantics<'a, RootDatabase>, krate: Crate, name_to_import: &str, -) -> Vec> { - let _p = profile::span("search_for_imports"); +) -> impl Iterator> { + let _p = profile::span("find_exact_imports"); + find_imports( + sema, + krate, + { + let mut local_query = LocalImportablesQuery::new(name_to_import.to_string()); + local_query.exact(); + local_query.limit(40); + local_query + }, + ExternalImportablesQuery::new(name_to_import).anchor_end().case_sensitive().limit(40), + ) +} + +pub fn find_similar_imports<'a>( + sema: &Semantics<'a, RootDatabase>, + krate: Crate, + name_to_import: &str, +) -> impl Iterator> { + let _p = profile::span("find_similar_imports"); + find_imports( + sema, + krate, + { + let mut local_query = LocalImportablesQuery::new(name_to_import.to_string()); + local_query.limit(40); + local_query + }, + ExternalImportablesQuery::new(name_to_import).limit(40), + ) +} + +fn find_imports<'a>( + sema: &Semantics<'a, RootDatabase>, + krate: Crate, + local_query: LocalImportablesQuery, + external_query: ExternalImportablesQuery, +) -> impl Iterator> { + let _p = profile::span("find_similar_imports"); let db = sema.db; // Query dependencies first. - let mut candidates: FxHashSet<_> = krate - .query_external_importables( - db, - ImportMapQuery::new(name_to_import).anchor_end().case_sensitive().limit(40), - ) - .collect(); + let mut candidates: FxHashSet<_> = + krate.query_external_importables(db, external_query).collect(); // Query the local crate using the symbol index. - let local_results = { - let mut query = SymbolQuery::new(name_to_import.to_string()); - query.exact(); - query.limit(40); - symbol_index::crate_symbols(db, krate.into(), query) - }; + let local_results = symbol_index::crate_symbols(db, krate.into(), local_query); candidates.extend( local_results @@ -47,7 +76,7 @@ pub fn find_imports<'a>( }), ); - candidates.into_iter().collect() + candidates.into_iter() } fn get_name_definition<'a>( From 1598740292c29613ef2b384a82de3a2735bc5566 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Nov 2020 21:25:45 +0200 Subject: [PATCH 08/18] Reuse existing element rendering --- crates/completion/src/completions/magic.rs | 82 +++++++++------------- crates/completion/src/item.rs | 4 ++ crates/completion/src/render/macro_.rs | 72 +++++++++---------- crates/text_edit/src/lib.rs | 4 ++ 4 files changed, 74 insertions(+), 88 deletions(-) diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs index 272c9a35469c..ef0fc27ba803 100644 --- a/crates/completion/src/completions/magic.rs +++ b/crates/completion/src/completions/magic.rs @@ -2,12 +2,14 @@ use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; use either::Either; -use hir::{db::HirDatabase, MacroDef, ModuleDef}; +use hir::ScopeDef; use ide_db::imports_locator; use syntax::{algo, AstNode}; -use text_edit::TextEdit; -use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, CompletionItemKind}; +use crate::{ + context::CompletionContext, + render::{render_resolution, RenderContext}, +}; use super::Completions; @@ -25,57 +27,41 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let possible_imports = imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name) .filter_map(|import_candidate| { - let use_path = match import_candidate { - Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), - Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), - }?; - Some((use_path, additional_completion(ctx.db, import_candidate))) + Some(match import_candidate { + Either::Left(module_def) => ( + current_module.find_use_path(ctx.db, module_def)?, + ScopeDef::ModuleDef(module_def), + ), + Either::Right(macro_def) => ( + current_module.find_use_path(ctx.db, macro_def)?, + ScopeDef::MacroDef(macro_def), + ), + }) }) - .filter_map(|(mod_path, additional_completion)| { - let mut builder = TextEdit::builder(); + .filter_map(|(mod_path, definition)| { + let mut resolution_with_missing_import = render_resolution( + RenderContext::new(ctx), + mod_path.segments.last()?.to_string(), + &definition, + )?; - let correct_qualifier = format!( - "{}{}", - mod_path.segments.last()?, - additional_completion.unwrap_or_default() - ); - builder.replace(anchor.syntax().text_range(), correct_qualifier); + let mut text_edits = + resolution_with_missing_import.text_edit().to_owned().into_builder(); let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); let old_ast = rewriter.rewrite_root()?; - algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); - - let completion_item: CompletionItem = CompletionItem::new( - CompletionKind::Magic, - ctx.source_range(), - mod_path.to_string(), - ) - .kind(CompletionItemKind::Struct) - .text_edit(builder.finish()) - .into(); - Some(completion_item) + algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); + + resolution_with_missing_import.update_text_edit(text_edits.finish()); + + Some(resolution_with_missing_import) }); - acc.add_all(possible_imports); + acc.add_all(possible_imports); Some(()) } -fn additional_completion( - db: &dyn HirDatabase, - import_candidate: Either, -) -> Option { - match import_candidate { - Either::Left(ModuleDef::Function(_)) => Some("()".to_string()), - Either::Right(macro_def) => { - let (left_brace, right_brace) = - crate::render::macro_::guess_macro_braces(db, macro_def); - Some(format!("!{}{}", left_brace, right_brace)) - } - _ => None, - } -} - #[cfg(test)] mod tests { use crate::test_utils::check_edit; @@ -83,7 +69,7 @@ mod tests { #[test] fn function_magic_completion() { check_edit( - "dep::io::stdin", + "stdin", r#" //- /lib.rs crate:dep pub mod io { @@ -99,7 +85,7 @@ fn main() { use dep::io::stdin; fn main() { - stdin() + stdin()$0 } "#, ); @@ -108,7 +94,7 @@ fn main() { #[test] fn macro_magic_completion() { check_edit( - "dep::macro_with_curlies", + "macro_with_curlies!", r#" //- /lib.rs crate:dep /// Please call me as macro_with_curlies! {} @@ -126,7 +112,7 @@ fn main() { use dep::macro_with_curlies; fn main() { - macro_with_curlies! {} + macro_with_curlies! {$0} } "#, ); @@ -135,7 +121,7 @@ fn main() { #[test] fn case_insensitive_magic_completion_works() { check_edit( - "dep::some_module::ThirdStruct", + "ThirdStruct", r#" //- /lib.rs crate:dep pub struct FirstStruct; diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index f23913935c0e..53a12a763d3e 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs @@ -218,6 +218,10 @@ impl CompletionItem { &self.text_edit } + pub fn update_text_edit(&mut self, new_text_edit: TextEdit) { + self.text_edit = new_text_edit; + } + /// Short one-line additional information, like a type pub fn detail(&self) -> Option<&str> { self.detail.as_deref() diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index b41c00b987d6..96be59cc336a 100644 --- a/crates/completion/src/render/macro_.rs +++ b/crates/completion/src/render/macro_.rs @@ -1,6 +1,6 @@ //! Renderer for macro invocations. -use hir::{db::HirDatabase, Documentation, HasAttrs, HasSource}; +use hir::{Documentation, HasSource}; use syntax::display::macro_label; use test_utils::mark; @@ -27,48 +27,12 @@ struct MacroRender<'a> { ket: &'static str, } -pub fn guess_macro_braces( - db: &dyn HirDatabase, - macro_: hir::MacroDef, -) -> (&'static str, &'static str) { - let macro_name = match macro_.name(db) { - Some(name) => name.to_string(), - None => return ("(", ")"), - }; - let macro_docs = macro_.docs(db); - let macro_docs = macro_docs.as_ref().map(Documentation::as_str).unwrap_or(""); - - let mut votes = [0, 0, 0]; - for (idx, s) in macro_docs.match_indices(¯o_name) { - let (before, after) = (¯o_docs[..idx], ¯o_docs[idx + s.len()..]); - // Ensure to match the full word - if after.starts_with('!') - && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) - { - // It may have spaces before the braces like `foo! {}` - match after[1..].chars().find(|&c| !c.is_whitespace()) { - Some('{') => votes[0] += 1, - Some('[') => votes[1] += 1, - Some('(') => votes[2] += 1, - _ => {} - } - } - } - - // Insert a space before `{}`. - // We prefer the last one when some votes equal. - let (_vote, (bra, ket)) = votes - .iter() - .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) - .max_by_key(|&(&vote, _)| vote) - .unwrap(); - (*bra, *ket) -} - impl<'a> MacroRender<'a> { fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { let docs = ctx.docs(macro_); - let (bra, ket) = guess_macro_braces(ctx.db(), macro_); + let docs_str = docs.as_ref().map_or("", |s| s.as_str()); + let (bra, ket) = guess_macro_braces(&name, docs_str); + MacroRender { ctx, name, macro_, docs, bra, ket } } @@ -133,6 +97,34 @@ impl<'a> MacroRender<'a> { } } +fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { + let mut votes = [0, 0, 0]; + for (idx, s) in docs.match_indices(¯o_name) { + let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); + // Ensure to match the full word + if after.starts_with('!') + && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) + { + // It may have spaces before the braces like `foo! {}` + match after[1..].chars().find(|&c| !c.is_whitespace()) { + Some('{') => votes[0] += 1, + Some('[') => votes[1] += 1, + Some('(') => votes[2] += 1, + _ => {} + } + } + } + + // Insert a space before `{}`. + // We prefer the last one when some votes equal. + let (_vote, (bra, ket)) = votes + .iter() + .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) + .max_by_key(|&(&vote, _)| vote) + .unwrap(); + (*bra, *ket) +} + #[cfg(test)] mod tests { use test_utils::mark; diff --git a/crates/text_edit/src/lib.rs b/crates/text_edit/src/lib.rs index eb3c8caa2a7a..9eef7a890671 100644 --- a/crates/text_edit/src/lib.rs +++ b/crates/text_edit/src/lib.rs @@ -48,6 +48,10 @@ impl TextEdit { TextEditBuilder::default() } + pub fn into_builder(self) -> TextEditBuilder { + TextEditBuilder { indels: self.indels } + } + pub fn insert(offset: TextSize, text: String) -> TextEdit { let mut builder = TextEdit::builder(); builder.insert(offset, text); From 46514448b740b82e4f5f9cf742c6f8c6caec1d38 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Nov 2020 22:31:41 +0200 Subject: [PATCH 09/18] Tweak the search limits a bit --- crates/completion/src/completions/magic.rs | 4 +++- crates/ide_db/src/imports_locator.rs | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs index ef0fc27ba803..2ac6f94a9b5a 100644 --- a/crates/completion/src/completions/magic.rs +++ b/crates/completion/src/completions/magic.rs @@ -13,11 +13,13 @@ use crate::{ use super::Completions; +// TODO kb reuse auto_import assist approach to add trait completion // TODO kb add a setting toggle for this feature? pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { return None; } + let _p = profile::span("complete_magic"); let current_module = ctx.scope.module()?; let anchor = ctx.name_ref_syntax.as_ref()?; let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; @@ -25,7 +27,7 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let potential_import_name = ctx.token.to_string(); let possible_imports = - imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name) + imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name, 400) .filter_map(|import_candidate| { Some(match import_candidate { Either::Left(module_def) => ( diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs index 71fb7207b661..1b21f76ac9b3 100644 --- a/crates/ide_db/src/imports_locator.rs +++ b/crates/ide_db/src/imports_locator.rs @@ -35,6 +35,7 @@ pub fn find_similar_imports<'a>( sema: &Semantics<'a, RootDatabase>, krate: Crate, name_to_import: &str, + limit: usize, ) -> impl Iterator> { let _p = profile::span("find_similar_imports"); find_imports( @@ -42,10 +43,10 @@ pub fn find_similar_imports<'a>( krate, { let mut local_query = LocalImportablesQuery::new(name_to_import.to_string()); - local_query.limit(40); + local_query.limit(limit); local_query }, - ExternalImportablesQuery::new(name_to_import).limit(40), + ExternalImportablesQuery::new(name_to_import).limit(limit), ) } From 3b0fc4d7f2b922a1b7d8d32fc0b065e4023d749b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 14 Nov 2020 01:07:03 +0200 Subject: [PATCH 10/18] Omit modules during autocompletion --- Cargo.lock | 159 ++++++++++++++------- crates/completion/src/completions/magic.rs | 26 ++-- 2 files changed, 118 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb4e43ad951f..d51610e4c1ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "addr2line" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" +checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423" dependencies = [ "gimli", ] @@ -26,9 +26,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c" +checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" [[package]] name = "anymap" @@ -42,9 +42,9 @@ version = "0.0.0" [[package]] name = "arrayvec" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "assists" @@ -81,9 +81,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.53" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e" +checksum = "2baad346b2d4e94a24347adeee9c7a93f412ee94b9cc26e5b59dea23848e9f28" dependencies = [ "addr2line", "cfg-if 1.0.0", @@ -132,7 +132,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5a5f7b42f606b7f23674f6f4d877628350682bc40687d3fae65679a58d55345" dependencies = [ - "semver", + "semver 0.11.0", "serde", "serde_json", ] @@ -271,17 +271,17 @@ dependencies = [ [[package]] name = "const_fn" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2" +checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" [[package]] name = "crc32fast" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", ] [[package]] @@ -418,11 +418,11 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "flate2" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da80be589a72651dcda34d8b35bcdc9b7254ad06325611074d9cc0fbb19f60ee" +checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "crc32fast", "libc", "miniz_oxide", @@ -471,9 +471,9 @@ dependencies = [ [[package]] name = "fst" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7293de202dbfe786c0b3fe6110a027836c5438ed06db7b715c9955ff4bfea51" +checksum = "d79238883cf0307100b90aba4a755d8051a3182305dfe7f649a1e9dc0517006f" [[package]] name = "fuchsia-zircon" @@ -491,11 +491,24 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "generator" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cdc09201b2e8ca1b19290cf7e65de2246b8e91fb6874279722189c4de7b94dc" +dependencies = [ + "cc", + "libc", + "log", + "rustc_version", + "winapi 0.3.9", +] + [[package]] name = "gimli" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" +checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" [[package]] name = "goblin" @@ -723,11 +736,11 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66" +checksum = "cb1fc4429a33e1f80d41dc9fea4d108a88bec1de8053878898ae448a0b52f613" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", ] [[package]] @@ -784,17 +797,17 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" [[package]] name = "libloading" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3557c9384f7f757f6d139cd3a4c62ef4e850696c16bf27924a5538c8a09717a1" +checksum = "1090080fe06ec2648d0da3881d9453d97e71a45f00eb179af7fdd7e3f686fdb0" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "winapi 0.3.9", ] @@ -825,6 +838,19 @@ dependencies = [ "cfg-if 0.1.10", ] +[[package]] +name = "loom" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0e8460f2f2121162705187214720353c517b97bdfb3494c0b1e33d83ebe4bed" +dependencies = [ + "cfg-if 0.1.10", + "generator", + "scoped-tls", + "serde", + "serde_json", +] + [[package]] name = "lsp-server" version = "0.4.1" @@ -887,9 +913,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "memmap" @@ -985,9 +1011,9 @@ dependencies = [ [[package]] name = "notify" -version = "5.0.0-pre.3" +version = "5.0.0-pre.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d03607cf88b4b160ba0e9ed425fff3cee3b55ac813f0c685b3a3772da37d0e" +checksum = "a8b946889dfdad884379cd56367d93b6d0ce8889cc027d26a69a3a31c0a03bb5" dependencies = [ "anymap", "bitflags", @@ -1005,9 +1031,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", "num-traits", @@ -1015,9 +1041,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", ] @@ -1034,9 +1060,9 @@ dependencies = [ [[package]] name = "object" -version = "0.21.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693" +checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" [[package]] name = "once_cell" @@ -1285,9 +1311,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "regex" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b" +checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" dependencies = [ "regex-syntax", ] @@ -1304,9 +1330,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" +checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" [[package]] name = "rowan" @@ -1379,9 +1405,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2610b7f643d18c87dff3b489950269617e6601a51f1f05aa5daefee36f64f0b" +checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" [[package]] name = "rustc-hash" @@ -1389,6 +1415,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "ryu" version = "1.0.5" @@ -1456,25 +1491,40 @@ dependencies = [ [[package]] name = "scroll_derive" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfde5d1531034db129e95c76ac857e2baecea3443579d493d02224950b0fb6d" +checksum = "b12bd20b94c7cdfda8c7ba9b92ad0d9a56e3fa018c25fca83b51aa664c9b4c0d" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser 0.7.0", +] + [[package]] name = "semver" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ - "semver-parser", + "semver-parser 0.10.1", "serde", ] +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "semver-parser" version = "0.10.1" @@ -1528,11 +1578,12 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.0.9" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d5a3f5166fb5b42a5439f2eee8b9de149e235961e3eb21c5808fc3ea17ff3e" +checksum = "7b4921be914e16899a80adefb821f8ddb7974e3f1250223575a44ed994882127" dependencies = [ "lazy_static", + "loom", ] [[package]] @@ -1576,9 +1627,9 @@ version = "0.0.0" [[package]] name = "syn" -version = "1.0.45" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9c5432ff16d6152371f808fb5a871cd67368171b09bb21b43df8e4a47a3556" +checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" dependencies = [ "proc-macro2", "quote", @@ -1755,9 +1806,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef0a5e15477aa303afbfac3a44cba9b6430fdaad52423b1e6c0dbbe28c3eedd" +checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401" dependencies = [ "ansi_term", "chrono", diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs index 2ac6f94a9b5a..0c4db01990c7 100644 --- a/crates/completion/src/completions/magic.rs +++ b/crates/completion/src/completions/magic.rs @@ -2,7 +2,7 @@ use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; use either::Either; -use hir::ScopeDef; +use hir::{ModuleDef, ScopeDef}; use ide_db::imports_locator; use syntax::{algo, AstNode}; @@ -13,7 +13,6 @@ use crate::{ use super::Completions; -// TODO kb reuse auto_import assist approach to add trait completion // TODO kb add a setting toggle for this feature? pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { @@ -28,17 +27,18 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let possible_imports = imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name, 400) - .filter_map(|import_candidate| { - Some(match import_candidate { - Either::Left(module_def) => ( - current_module.find_use_path(ctx.db, module_def)?, - ScopeDef::ModuleDef(module_def), - ), - Either::Right(macro_def) => ( - current_module.find_use_path(ctx.db, macro_def)?, - ScopeDef::MacroDef(macro_def), - ), - }) + .filter_map(|import_candidate| match import_candidate { + // when completing outside the use declaration, modules are pretty useless + // and tend to bloat the completion suggestions a lot + Either::Left(ModuleDef::Module(_)) => None, + Either::Left(module_def) => Some(( + current_module.find_use_path(ctx.db, module_def)?, + ScopeDef::ModuleDef(module_def), + )), + Either::Right(macro_def) => Some(( + current_module.find_use_path(ctx.db, macro_def)?, + ScopeDef::MacroDef(macro_def), + )), }) .filter_map(|(mod_path, definition)| { let mut resolution_with_missing_import = render_resolution( From ee99620754cdcfbab28a2c067dfa31087376d6c3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 14 Nov 2020 01:26:31 +0200 Subject: [PATCH 11/18] Move autoimport completion into the unqialified_path module --- crates/completion/Cargo.toml | 1 - crates/completion/src/completions.rs | 1 - crates/completion/src/completions/magic.rs | 151 ------------------ .../src/completions/unqualified_path.rs | 141 +++++++++++++++- crates/completion/src/lib.rs | 1 - 5 files changed, 139 insertions(+), 156 deletions(-) delete mode 100644 crates/completion/src/completions/magic.rs diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml index 799b4a3d5445..e7df9d955656 100644 --- a/crates/completion/Cargo.toml +++ b/crates/completion/Cargo.toml @@ -22,7 +22,6 @@ text_edit = { path = "../text_edit", version = "0.0.0" } base_db = { path = "../base_db", version = "0.0.0" } ide_db = { path = "../ide_db", version = "0.0.0" } profile = { path = "../profile", version = "0.0.0" } -assists = { path = "../assists", version = "0.0.0" } test_utils = { path = "../test_utils", version = "0.0.0" } # completions crate should depend only on the top-level `hir` package. if you need diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 4abb101564d0..75dbb1a23bb5 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -13,7 +13,6 @@ pub(crate) mod postfix; pub(crate) mod macro_in_item_position; pub(crate) mod trait_impl; pub(crate) mod mod_; -pub(crate) mod magic; use hir::{ModPath, ScopeDef, Type}; diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs deleted file mode 100644 index 0c4db01990c7..000000000000 --- a/crates/completion/src/completions/magic.rs +++ /dev/null @@ -1,151 +0,0 @@ -//! TODO kb move this into the complete_unqualified_path when starts to work properly - -use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; -use either::Either; -use hir::{ModuleDef, ScopeDef}; -use ide_db::imports_locator; -use syntax::{algo, AstNode}; - -use crate::{ - context::CompletionContext, - render::{render_resolution, RenderContext}, -}; - -use super::Completions; - -// TODO kb add a setting toggle for this feature? -pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { - if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { - return None; - } - let _p = profile::span("complete_magic"); - let current_module = ctx.scope.module()?; - let anchor = ctx.name_ref_syntax.as_ref()?; - let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; - - let potential_import_name = ctx.token.to_string(); - - let possible_imports = - imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name, 400) - .filter_map(|import_candidate| match import_candidate { - // when completing outside the use declaration, modules are pretty useless - // and tend to bloat the completion suggestions a lot - Either::Left(ModuleDef::Module(_)) => None, - Either::Left(module_def) => Some(( - current_module.find_use_path(ctx.db, module_def)?, - ScopeDef::ModuleDef(module_def), - )), - Either::Right(macro_def) => Some(( - current_module.find_use_path(ctx.db, macro_def)?, - ScopeDef::MacroDef(macro_def), - )), - }) - .filter_map(|(mod_path, definition)| { - let mut resolution_with_missing_import = render_resolution( - RenderContext::new(ctx), - mod_path.segments.last()?.to_string(), - &definition, - )?; - - let mut text_edits = - resolution_with_missing_import.text_edit().to_owned().into_builder(); - - let rewriter = - insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); - let old_ast = rewriter.rewrite_root()?; - algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); - - resolution_with_missing_import.update_text_edit(text_edits.finish()); - - Some(resolution_with_missing_import) - }); - - acc.add_all(possible_imports); - Some(()) -} - -#[cfg(test)] -mod tests { - use crate::test_utils::check_edit; - - #[test] - fn function_magic_completion() { - check_edit( - "stdin", - r#" -//- /lib.rs crate:dep -pub mod io { - pub fn stdin() {} -}; - -//- /main.rs crate:main deps:dep -fn main() { - stdi<|> -} -"#, - r#" -use dep::io::stdin; - -fn main() { - stdin()$0 -} -"#, - ); - } - - #[test] - fn macro_magic_completion() { - check_edit( - "macro_with_curlies!", - r#" -//- /lib.rs crate:dep -/// Please call me as macro_with_curlies! {} -#[macro_export] -macro_rules! macro_with_curlies { - () => {} -} - -//- /main.rs crate:main deps:dep -fn main() { - curli<|> -} -"#, - r#" -use dep::macro_with_curlies; - -fn main() { - macro_with_curlies! {$0} -} -"#, - ); - } - - #[test] - fn case_insensitive_magic_completion_works() { - check_edit( - "ThirdStruct", - r#" -//- /lib.rs crate:dep -pub struct FirstStruct; -pub mod some_module { - pub struct SecondStruct; - pub struct ThirdStruct; -} - -//- /main.rs crate:main deps:dep -use dep::{FirstStruct, some_module::SecondStruct}; - -fn main() { - this<|> -} -"#, - r#" -use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}}; - -fn main() { - ThirdStruct -} -"#, - ); - } -} diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index 7df58e1da829..ecda37862eb1 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -1,10 +1,16 @@ //! Completion of names from the current scope, e.g. locals and imported items. +use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; +use either::Either; use hir::{Adt, ModuleDef, ScopeDef, Type}; -use syntax::AstNode; +use ide_db::imports_locator; +use syntax::{algo, AstNode}; use test_utils::mark; -use crate::{CompletionContext, Completions}; +use crate::{ + render::{render_resolution, RenderContext}, + CompletionContext, Completions, +}; pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { @@ -37,6 +43,56 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC } acc.add_resolution(ctx, name.to_string(), &res) }); + + fuzzy_completion(acc, ctx).unwrap_or_default() +} + +// TODO kb add a setting toggle for this feature? +fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { + let _p = profile::span("fuzzy_completion®"); + let current_module = ctx.scope.module()?; + let anchor = ctx.name_ref_syntax.as_ref()?; + let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; + + let potential_import_name = ctx.token.to_string(); + + let possible_imports = + imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name, 400) + .filter_map(|import_candidate| match import_candidate { + // when completing outside the use declaration, modules are pretty useless + // and tend to bloat the completion suggestions a lot + Either::Left(ModuleDef::Module(_)) => None, + Either::Left(module_def) => Some(( + current_module.find_use_path(ctx.db, module_def)?, + ScopeDef::ModuleDef(module_def), + )), + Either::Right(macro_def) => Some(( + current_module.find_use_path(ctx.db, macro_def)?, + ScopeDef::MacroDef(macro_def), + )), + }) + .filter_map(|(mod_path, definition)| { + let mut resolution_with_missing_import = render_resolution( + RenderContext::new(ctx), + mod_path.segments.last()?.to_string(), + &definition, + )?; + + let mut text_edits = + resolution_with_missing_import.text_edit().to_owned().into_builder(); + + let rewriter = + insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); + let old_ast = rewriter.rewrite_root()?; + algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); + + resolution_with_missing_import.update_text_edit(text_edits.finish()); + + Some(resolution_with_missing_import) + }); + + acc.add_all(possible_imports); + Some(()) } fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { @@ -676,4 +732,85 @@ impl My<|> "#]], ) } + + #[test] + fn function_magic_completion() { + check_edit( + "stdin", + r#" +//- /lib.rs crate:dep +pub mod io { + pub fn stdin() {} +}; + +//- /main.rs crate:main deps:dep +fn main() { + stdi<|> +} +"#, + r#" +use dep::io::stdin; + +fn main() { + stdin()$0 +} +"#, + ); + } + + #[test] + fn macro_magic_completion() { + check_edit( + "macro_with_curlies!", + r#" +//- /lib.rs crate:dep +/// Please call me as macro_with_curlies! {} +#[macro_export] +macro_rules! macro_with_curlies { + () => {} +} + +//- /main.rs crate:main deps:dep +fn main() { + curli<|> +} +"#, + r#" +use dep::macro_with_curlies; + +fn main() { + macro_with_curlies! {$0} +} +"#, + ); + } + + #[test] + fn case_insensitive_magic_completion_works() { + check_edit( + "ThirdStruct", + r#" +//- /lib.rs crate:dep +pub struct FirstStruct; +pub mod some_module { + pub struct SecondStruct; + pub struct ThirdStruct; +} + +//- /main.rs crate:main deps:dep +use dep::{FirstStruct, some_module::SecondStruct}; + +fn main() { + this<|> +} +"#, + r#" +use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}}; + +fn main() { + ThirdStruct +} +"#, + ); + } } diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index 8323af8b285e..cb6e0554e9a7 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs @@ -118,7 +118,6 @@ pub fn completions( completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); completions::trait_impl::complete_trait_impl(&mut acc, &ctx); completions::mod_::complete_mod(&mut acc, &ctx); - completions::magic::complete_magic(&mut acc, &ctx); Some(acc) } From 38ef1fd4ad7fd26439201a1a4147a7d90a13601f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 14 Nov 2020 11:59:23 +0200 Subject: [PATCH 12/18] Better filter mod paths --- crates/assists/src/utils/import_assets.rs | 2 +- .../src/completions/unqualified_path.rs | 52 ++++++++++--------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs index 89e62ba79c0c..ff5c0e78ee8c 100644 --- a/crates/assists/src/utils/import_assets.rs +++ b/crates/assists/src/utils/import_assets.rs @@ -195,7 +195,7 @@ impl ImportAssets { } .map(|path| (path, item)) }) - .filter(|(use_path, _)| !use_path.segments.is_empty()) + .filter(|(use_path, _)| use_path.len() > 1) .take(20) .collect::>(); res.sort_by_key(|(path, _)| path.clone()); diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index ecda37862eb1..7ce92a07bf23 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -47,6 +47,30 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC fuzzy_completion(acc, ctx).unwrap_or_default() } +fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { + if let Some(Adt::Enum(enum_data)) = ty.as_adt() { + let variants = enum_data.variants(ctx.db); + + let module = if let Some(module) = ctx.scope.module() { + // Compute path from the completion site if available. + module + } else { + // Otherwise fall back to the enum's definition site. + enum_data.module(ctx.db) + }; + + for variant in variants { + if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) { + // Variants with trivial paths are already added by the existing completion logic, + // so we should avoid adding these twice + if path.segments.len() > 1 { + acc.add_qualified_enum_variant(ctx, variant, path); + } + } + } + } +} + // TODO kb add a setting toggle for this feature? fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { let _p = profile::span("fuzzy_completion®"); @@ -71,6 +95,7 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<() ScopeDef::MacroDef(macro_def), )), }) + .filter(|(mod_path, _)| mod_path.len() > 1) .filter_map(|(mod_path, definition)| { let mut resolution_with_missing_import = render_resolution( RenderContext::new(ctx), @@ -89,36 +114,13 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<() resolution_with_missing_import.update_text_edit(text_edits.finish()); Some(resolution_with_missing_import) - }); + }) + .take(20); acc.add_all(possible_imports); Some(()) } -fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { - if let Some(Adt::Enum(enum_data)) = ty.as_adt() { - let variants = enum_data.variants(ctx.db); - - let module = if let Some(module) = ctx.scope.module() { - // Compute path from the completion site if available. - module - } else { - // Otherwise fall back to the enum's definition site. - enum_data.module(ctx.db) - }; - - for variant in variants { - if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) { - // Variants with trivial paths are already added by the existing completion logic, - // so we should avoid adding these twice - if path.segments.len() > 1 { - acc.add_qualified_enum_variant(ctx, variant, path); - } - } - } - } -} - #[cfg(test)] mod tests { use expect_test::{expect, Expect}; From bbe1fbd1786b416908d3c6bc34c8cf805b39b761 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 14 Nov 2020 14:50:57 +0200 Subject: [PATCH 13/18] Qualify autoimport completion suggestions --- .../src/completions/unqualified_path.rs | 35 ++++++++++++------- crates/completion/src/item.rs | 24 ++++++++++--- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index 7ce92a07bf23..fca8d3a72215 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -71,7 +71,6 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T } } -// TODO kb add a setting toggle for this feature? fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { let _p = profile::span("fuzzy_completion®"); let current_module = ctx.scope.module()?; @@ -97,23 +96,35 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<() }) .filter(|(mod_path, _)| mod_path.len() > 1) .filter_map(|(mod_path, definition)| { - let mut resolution_with_missing_import = render_resolution( - RenderContext::new(ctx), - mod_path.segments.last()?.to_string(), - &definition, - )?; + let use_to_insert = mod_path_to_ast(&mod_path); + let mut mod_path_without_last_segment = mod_path; + let name_after_import = mod_path_without_last_segment.segments.pop()?.to_string(); + + let resolution_with_missing_import = + render_resolution(RenderContext::new(ctx), name_after_import, &definition)?; + let lookup_string = resolution_with_missing_import.lookup().to_owned(); let mut text_edits = resolution_with_missing_import.text_edit().to_owned().into_builder(); - - let rewriter = - insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); + let rewriter = insert_use(&import_scope, use_to_insert, ctx.config.merge); let old_ast = rewriter.rewrite_root()?; algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); - resolution_with_missing_import.update_text_edit(text_edits.finish()); - - Some(resolution_with_missing_import) + let qualifier_string = mod_path_without_last_segment.to_string(); + let qualified_label = if qualifier_string.is_empty() { + resolution_with_missing_import.label().to_owned() + } else { + format!("{}::{}", qualifier_string, resolution_with_missing_import.label()) + }; + + Some( + resolution_with_missing_import + .into_builder() + .text_edit(text_edits.finish()) + .label(qualified_label) + .lookup_by(lookup_string) + .build(), + ) }) .take(20); diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index 53a12a763d3e..24b9d036a313 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs @@ -202,6 +202,26 @@ impl CompletionItem { ref_match: None, } } + + pub(crate) fn into_builder(self) -> Builder { + Builder { + source_range: self.source_range, + completion_kind: self.completion_kind, + label: self.label, + insert_text: None, + insert_text_format: self.insert_text_format, + detail: self.detail, + documentation: self.documentation, + lookup: self.lookup, + kind: self.kind, + text_edit: Some(self.text_edit), + deprecated: Some(self.deprecated), + trigger_call_info: Some(self.trigger_call_info), + score: self.score, + ref_match: self.ref_match, + } + } + /// What user sees in pop-up in the UI. pub fn label(&self) -> &str { &self.label @@ -218,10 +238,6 @@ impl CompletionItem { &self.text_edit } - pub fn update_text_edit(&mut self, new_text_edit: TextEdit) { - self.text_edit = new_text_edit; - } - /// Short one-line additional information, like a type pub fn detail(&self) -> Option<&str> { self.detail.as_deref() From 1de7848b5710f21cccf90dabc99a0cf6fcdabad3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 14 Nov 2020 14:59:03 +0200 Subject: [PATCH 14/18] Fix the other test --- .../src/completions/unqualified_path.rs | 6 ++--- crates/completion/src/render.rs | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index fca8d3a72215..362d31f2550b 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -747,7 +747,7 @@ impl My<|> } #[test] - fn function_magic_completion() { + fn function_fuzzy_completion() { check_edit( "stdin", r#" @@ -772,7 +772,7 @@ fn main() { } #[test] - fn macro_magic_completion() { + fn macro_fuzzy_completion() { check_edit( "macro_with_curlies!", r#" @@ -799,7 +799,7 @@ fn main() { } #[test] - fn case_insensitive_magic_completion_works() { + fn struct_fuzzy_completion() { check_edit( "ThirdStruct", r#" diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index 1fa02c37522f..5a4353846161 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -425,6 +425,28 @@ fn main() { let _: m::Spam = S<|> } insert: "m", kind: Module, }, + CompletionItem { + label: "m::Spam", + source_range: 75..76, + text_edit: TextEdit { + indels: [ + Indel { + insert: "use m::Spam;", + delete: 0..0, + }, + Indel { + insert: "\n\n", + delete: 0..0, + }, + Indel { + insert: "Spam", + delete: 75..76, + }, + ], + }, + kind: Enum, + lookup: "Spam", + }, CompletionItem { label: "m::Spam::Foo", source_range: 75..76, From d776c67226fe12e75fc526966cba06358c2b8113 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 14 Nov 2020 23:17:08 +0200 Subject: [PATCH 15/18] Properly fill the completion settings --- crates/rust-analyzer/src/config.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index d16796590087..5fc6800cf2fd 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -294,10 +294,6 @@ impl Config { max_length: data.inlayHints_maxLength, }; - self.completion.enable_postfix_completions = data.completion_postfix_enable; - self.completion.add_call_parenthesis = data.completion_addCallParenthesis; - self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets; - self.assist.insert_use.merge = match data.assist_importMergeBehaviour { MergeBehaviourDef::None => None, MergeBehaviourDef::Full => Some(MergeBehaviour::Full), @@ -309,6 +305,11 @@ impl Config { ImportPrefixDef::BySelf => PrefixKind::BySelf, }; + self.completion.enable_postfix_completions = data.completion_postfix_enable; + self.completion.add_call_parenthesis = data.completion_addCallParenthesis; + self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets; + self.completion.merge = self.assist.insert_use.merge; + self.call_info_full = data.callInfo_full; self.lens = LensConfig { From 410996893489f6c64b472e6128f099f1de229806 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 16 Nov 2020 21:24:54 +0200 Subject: [PATCH 16/18] Remove query aliases --- .../src/completions/unqualified_path.rs | 2 +- crates/hir/src/lib.rs | 2 +- crates/ide_db/src/imports_locator.rs | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index 362d31f2550b..4f8ec1e6756c 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -72,7 +72,7 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T } fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { - let _p = profile::span("fuzzy_completion®"); + let _p = profile::span("fuzzy_completion"); let current_module = ctx.scope.module()?; let anchor = ctx.name_ref_syntax.as_ref()?; let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 4b7ea3aa9f68..5fea25ef1b71 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -49,7 +49,7 @@ pub use hir_def::{ builtin_type::BuiltinType, docs::Documentation, find_path::PrefixKind, - import_map::Query as ExternalImportablesQuery, + import_map, item_scope::ItemInNs, nameres::ModuleSource, path::{ModPath, PathKind}, diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs index 1b21f76ac9b3..9d8ea7368d3a 100644 --- a/crates/ide_db/src/imports_locator.rs +++ b/crates/ide_db/src/imports_locator.rs @@ -1,12 +1,12 @@ //! This module contains an import search funcionality that is provided to the assists module. //! Later, this should be moved away to a separate crate that is accessible from the assists module. -use hir::{Crate, ExternalImportablesQuery, MacroDef, ModuleDef, Semantics}; +use hir::{import_map, Crate, MacroDef, ModuleDef, Semantics}; use syntax::{ast, AstNode, SyntaxKind::NAME}; use crate::{ defs::{Definition, NameClass}, - symbol_index::{self, FileSymbol, Query as LocalImportablesQuery}, + symbol_index::{self, FileSymbol}, RootDatabase, }; use either::Either; @@ -22,12 +22,12 @@ pub fn find_exact_imports<'a>( sema, krate, { - let mut local_query = LocalImportablesQuery::new(name_to_import.to_string()); + let mut local_query = symbol_index::Query::new(name_to_import.to_string()); local_query.exact(); local_query.limit(40); local_query }, - ExternalImportablesQuery::new(name_to_import).anchor_end().case_sensitive().limit(40), + import_map::Query::new(name_to_import).anchor_end().case_sensitive().limit(40), ) } @@ -42,19 +42,19 @@ pub fn find_similar_imports<'a>( sema, krate, { - let mut local_query = LocalImportablesQuery::new(name_to_import.to_string()); + let mut local_query = symbol_index::Query::new(name_to_import.to_string()); local_query.limit(limit); local_query }, - ExternalImportablesQuery::new(name_to_import).limit(limit), + import_map::Query::new(name_to_import).limit(limit), ) } fn find_imports<'a>( sema: &Semantics<'a, RootDatabase>, krate: Crate, - local_query: LocalImportablesQuery, - external_query: ExternalImportablesQuery, + local_query: symbol_index::Query, + external_query: import_map::Query, ) -> impl Iterator> { let _p = profile::span("find_similar_imports"); let db = sema.db; From d4128beb3d8c647674ae43407d0ed6edd71ff420 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 16 Nov 2020 23:16:41 +0200 Subject: [PATCH 17/18] Avoid turning completion objects into builders --- crates/assists/src/utils/insert_use.rs | 2 +- crates/completion/src/completions.rs | 8 +- .../src/completions/unqualified_path.rs | 42 +++------- crates/completion/src/item.rs | 76 ++++++++++++------- crates/completion/src/render.rs | 35 +++++++-- crates/completion/src/render/enum_variant.rs | 10 ++- crates/completion/src/render/function.rs | 12 ++- crates/completion/src/render/macro_.rs | 12 ++- crates/text_edit/src/lib.rs | 4 - 9 files changed, 117 insertions(+), 84 deletions(-) diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs index 1aa727e11fe5..423782a0e0e7 100644 --- a/crates/assists/src/utils/insert_use.rs +++ b/crates/assists/src/utils/insert_use.rs @@ -15,7 +15,7 @@ use syntax::{ }; use test_utils::mark; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ImportScope { File(ast::SourceFile), Module(ast::ItemList), diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 75dbb1a23bb5..9b7d6c580913 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -90,7 +90,7 @@ impl Completions { Some(it) => it, None => return, }; - if let Some(item) = render_macro(RenderContext::new(ctx), name, macro_) { + if let Some(item) = render_macro(RenderContext::new(ctx), None, name, macro_) { self.add(item); } } @@ -101,7 +101,7 @@ impl Completions { func: hir::Function, local_name: Option, ) { - let item = render_fn(RenderContext::new(ctx), local_name, func); + let item = render_fn(RenderContext::new(ctx), None, local_name, func); self.add(item) } @@ -123,7 +123,7 @@ impl Completions { variant: hir::EnumVariant, path: ModPath, ) { - let item = render_enum_variant(RenderContext::new(ctx), None, variant, Some(path)); + let item = render_enum_variant(RenderContext::new(ctx), None, None, variant, Some(path)); self.add(item); } @@ -133,7 +133,7 @@ impl Completions { variant: hir::EnumVariant, local_name: Option, ) { - let item = render_enum_variant(RenderContext::new(ctx), local_name, variant, None); + let item = render_enum_variant(RenderContext::new(ctx), None, local_name, variant, None); self.add(item); } } diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index 4f8ec1e6756c..86c143b637f7 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -1,14 +1,14 @@ //! Completion of names from the current scope, e.g. locals and imported items. -use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; +use assists::utils::ImportScope; use either::Either; use hir::{Adt, ModuleDef, ScopeDef, Type}; use ide_db::imports_locator; -use syntax::{algo, AstNode}; +use syntax::AstNode; use test_utils::mark; use crate::{ - render::{render_resolution, RenderContext}, + render::{render_resolution_with_import, RenderContext}, CompletionContext, Completions, }; @@ -95,35 +95,13 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<() )), }) .filter(|(mod_path, _)| mod_path.len() > 1) - .filter_map(|(mod_path, definition)| { - let use_to_insert = mod_path_to_ast(&mod_path); - let mut mod_path_without_last_segment = mod_path; - let name_after_import = mod_path_without_last_segment.segments.pop()?.to_string(); - - let resolution_with_missing_import = - render_resolution(RenderContext::new(ctx), name_after_import, &definition)?; - let lookup_string = resolution_with_missing_import.lookup().to_owned(); - - let mut text_edits = - resolution_with_missing_import.text_edit().to_owned().into_builder(); - let rewriter = insert_use(&import_scope, use_to_insert, ctx.config.merge); - let old_ast = rewriter.rewrite_root()?; - algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); - - let qualifier_string = mod_path_without_last_segment.to_string(); - let qualified_label = if qualifier_string.is_empty() { - resolution_with_missing_import.label().to_owned() - } else { - format!("{}::{}", qualifier_string, resolution_with_missing_import.label()) - }; - - Some( - resolution_with_missing_import - .into_builder() - .text_edit(text_edits.finish()) - .label(qualified_label) - .lookup_by(lookup_string) - .build(), + .filter_map(|(import_path, definition)| { + render_resolution_with_import( + RenderContext::new(ctx), + import_path.clone(), + import_scope.clone(), + ctx.config.merge, + &definition, ) }) .take(20); diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index 24b9d036a313..b13c3f37628a 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs @@ -2,8 +2,9 @@ use std::fmt; -use hir::{Documentation, Mutability}; -use syntax::TextRange; +use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; +use hir::{Documentation, ModPath, Mutability}; +use syntax::{algo, TextRange}; use text_edit::TextEdit; use crate::config::SnippetCap; @@ -200,25 +201,7 @@ impl CompletionItem { trigger_call_info: None, score: None, ref_match: None, - } - } - - pub(crate) fn into_builder(self) -> Builder { - Builder { - source_range: self.source_range, - completion_kind: self.completion_kind, - label: self.label, - insert_text: None, - insert_text_format: self.insert_text_format, - detail: self.detail, - documentation: self.documentation, - lookup: self.lookup, - kind: self.kind, - text_edit: Some(self.text_edit), - deprecated: Some(self.deprecated), - trigger_call_info: Some(self.trigger_call_info), - score: self.score, - ref_match: self.ref_match, + import_data: None, } } @@ -278,6 +261,7 @@ impl CompletionItem { pub(crate) struct Builder { source_range: TextRange, completion_kind: CompletionKind, + import_data: Option<(ModPath, ImportScope, Option)>, label: String, insert_text: Option, insert_text_format: InsertTextFormat, @@ -294,23 +278,50 @@ pub(crate) struct Builder { impl Builder { pub(crate) fn build(self) -> CompletionItem { - let label = self.label; - let text_edit = match self.text_edit { + let mut label = self.label; + let mut lookup = self.lookup; + let mut insert_text = self.insert_text; + let mut text_edits = TextEdit::builder(); + + if let Some((import_path, import_scope, merge_behaviour)) = self.import_data { + let import = mod_path_to_ast(&import_path); + let mut import_path_without_last_segment = import_path; + let _ = import_path_without_last_segment.segments.pop(); + + if !import_path_without_last_segment.segments.is_empty() { + if lookup.is_none() { + lookup = Some(label.clone()); + } + if insert_text.is_none() { + insert_text = Some(label.clone()); + } + label = format!("{}::{}", import_path_without_last_segment, label); + } + + let rewriter = insert_use(&import_scope, import, merge_behaviour); + if let Some(old_ast) = rewriter.rewrite_root() { + algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); + } + } + + let original_edit = match self.text_edit { Some(it) => it, - None => TextEdit::replace( - self.source_range, - self.insert_text.unwrap_or_else(|| label.clone()), - ), + None => { + TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone())) + } }; + let mut resulting_edit = text_edits.finish(); + resulting_edit.union(original_edit).expect("Failed to unite text edits"); + CompletionItem { source_range: self.source_range, label, insert_text_format: self.insert_text_format, - text_edit, + text_edit: resulting_edit, detail: self.detail, documentation: self.documentation, - lookup: self.lookup, + lookup, kind: self.kind, completion_kind: self.completion_kind, deprecated: self.deprecated.unwrap_or(false), @@ -379,6 +390,13 @@ impl Builder { self.trigger_call_info = Some(true); self } + pub(crate) fn import_data( + mut self, + import_data: Option<(ModPath, ImportScope, Option)>, + ) -> Builder { + self.import_data = import_data; + self + } pub(crate) fn set_ref_match( mut self, ref_match: Option<(Mutability, CompletionScore)>, diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index 5a4353846161..e892d4de8590 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -9,7 +9,8 @@ pub(crate) mod type_alias; mod builder_ext; -use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type}; +use assists::utils::{ImportScope, MergeBehaviour}; +use hir::{Documentation, HasAttrs, HirDisplay, ModPath, Mutability, ScopeDef, Type}; use ide_db::RootDatabase; use syntax::TextRange; use test_utils::mark; @@ -42,7 +43,22 @@ pub(crate) fn render_resolution<'a>( local_name: String, resolution: &ScopeDef, ) -> Option { - Render::new(ctx).render_resolution(local_name, resolution) + Render::new(ctx).render_resolution(local_name, None, resolution) +} + +pub(crate) fn render_resolution_with_import<'a>( + ctx: RenderContext<'a>, + import: ModPath, + import_scope: ImportScope, + merge_behaviour: Option, + resolution: &ScopeDef, +) -> Option { + let local_name = import.segments.last()?.to_string(); + Render::new(ctx).render_resolution( + local_name, + Some((import, import_scope, merge_behaviour)), + resolution, + ) } /// Interface for data and methods required for items rendering. @@ -131,6 +147,7 @@ impl<'a> Render<'a> { fn render_resolution( self, local_name: String, + import_data: Option<(ModPath, ImportScope, Option)>, resolution: &ScopeDef, ) -> Option { use hir::ModuleDef::*; @@ -142,15 +159,15 @@ impl<'a> Render<'a> { let kind = match resolution { ScopeDef::ModuleDef(Function(func)) => { - let item = render_fn(self.ctx, Some(local_name), *func); + let item = render_fn(self.ctx, import_data, Some(local_name), *func); return Some(item); } ScopeDef::ModuleDef(EnumVariant(var)) => { - let item = render_enum_variant(self.ctx, Some(local_name), *var, None); + let item = render_enum_variant(self.ctx, import_data, Some(local_name), *var, None); return Some(item); } ScopeDef::MacroDef(mac) => { - let item = render_macro(self.ctx, local_name, *mac); + let item = render_macro(self.ctx, import_data, local_name, *mac); return item; } @@ -175,6 +192,7 @@ impl<'a> Render<'a> { local_name, ) .kind(CompletionItemKind::UnresolvedReference) + .import_data(import_data) .build(); return Some(item); } @@ -227,7 +245,12 @@ impl<'a> Render<'a> { } } - let item = item.kind(kind).set_documentation(docs).set_ref_match(ref_match).build(); + let item = item + .kind(kind) + .import_data(import_data) + .set_documentation(docs) + .set_ref_match(ref_match) + .build(); Some(item) } diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs index fd412ed0eef8..6070e9b1d793 100644 --- a/crates/completion/src/render/enum_variant.rs +++ b/crates/completion/src/render/enum_variant.rs @@ -1,5 +1,6 @@ //! Renderer for `enum` variants. +use assists::utils::{ImportScope, MergeBehaviour}; use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; use itertools::Itertools; use test_utils::mark; @@ -11,11 +12,12 @@ use crate::{ pub(crate) fn render_enum_variant<'a>( ctx: RenderContext<'a>, + import_data: Option<(ModPath, ImportScope, Option)>, local_name: Option, variant: hir::EnumVariant, path: Option, ) -> CompletionItem { - EnumVariantRender::new(ctx, local_name, variant, path).render() + EnumVariantRender::new(ctx, local_name, variant, path).render(import_data) } #[derive(Debug)] @@ -60,7 +62,10 @@ impl<'a> EnumVariantRender<'a> { } } - fn render(self) -> CompletionItem { + fn render( + self, + import_data: Option<(ModPath, ImportScope, Option)>, + ) -> CompletionItem { let mut builder = CompletionItem::new( CompletionKind::Reference, self.ctx.source_range(), @@ -69,6 +74,7 @@ impl<'a> EnumVariantRender<'a> { .kind(CompletionItemKind::EnumVariant) .set_documentation(self.variant.docs(self.ctx.db())) .set_deprecated(self.ctx.is_deprecated(self.variant)) + .import_data(import_data) .detail(self.detail()); if self.variant_kind == StructKind::Tuple { diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs index 4fa6eafd72f6..9dd5cd18c569 100644 --- a/crates/completion/src/render/function.rs +++ b/crates/completion/src/render/function.rs @@ -1,6 +1,7 @@ //! Renderer for function calls. -use hir::{HasSource, Type}; +use assists::utils::{ImportScope, MergeBehaviour}; +use hir::{HasSource, ModPath, Type}; use syntax::{ast::Fn, display::function_declaration}; use crate::{ @@ -10,10 +11,11 @@ use crate::{ pub(crate) fn render_fn<'a>( ctx: RenderContext<'a>, + import_data: Option<(ModPath, ImportScope, Option)>, local_name: Option, fn_: hir::Function, ) -> CompletionItem { - FunctionRender::new(ctx, local_name, fn_).render() + FunctionRender::new(ctx, local_name, fn_).render(import_data) } #[derive(Debug)] @@ -36,7 +38,10 @@ impl<'a> FunctionRender<'a> { FunctionRender { ctx, name, fn_, ast_node } } - fn render(self) -> CompletionItem { + fn render( + self, + import_data: Option<(ModPath, ImportScope, Option)>, + ) -> CompletionItem { let params = self.params(); CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone()) .kind(self.kind()) @@ -44,6 +49,7 @@ impl<'a> FunctionRender<'a> { .set_deprecated(self.ctx.is_deprecated(self.fn_)) .detail(self.detail()) .add_call_parens(self.ctx.completion, self.name, params) + .import_data(import_data) .build() } diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index 96be59cc336a..fead59e41c09 100644 --- a/crates/completion/src/render/macro_.rs +++ b/crates/completion/src/render/macro_.rs @@ -1,6 +1,7 @@ //! Renderer for macro invocations. -use hir::{Documentation, HasSource}; +use assists::utils::{ImportScope, MergeBehaviour}; +use hir::{Documentation, HasSource, ModPath}; use syntax::display::macro_label; use test_utils::mark; @@ -11,10 +12,11 @@ use crate::{ pub(crate) fn render_macro<'a>( ctx: RenderContext<'a>, + import_data: Option<(ModPath, ImportScope, Option)>, name: String, macro_: hir::MacroDef, ) -> Option { - MacroRender::new(ctx, name, macro_).render() + MacroRender::new(ctx, name, macro_).render(import_data) } #[derive(Debug)] @@ -36,7 +38,10 @@ impl<'a> MacroRender<'a> { MacroRender { ctx, name, macro_, docs, bra, ket } } - fn render(&self) -> Option { + fn render( + &self, + import_data: Option<(ModPath, ImportScope, Option)>, + ) -> Option { // FIXME: Currently proc-macro do not have ast-node, // such that it does not have source if self.macro_.is_proc_macro() { @@ -48,6 +53,7 @@ impl<'a> MacroRender<'a> { .kind(CompletionItemKind::Macro) .set_documentation(self.docs.clone()) .set_deprecated(self.ctx.is_deprecated(self.macro_)) + .import_data(import_data) .detail(self.detail()); let needs_bang = self.needs_bang(); diff --git a/crates/text_edit/src/lib.rs b/crates/text_edit/src/lib.rs index 9eef7a890671..eb3c8caa2a7a 100644 --- a/crates/text_edit/src/lib.rs +++ b/crates/text_edit/src/lib.rs @@ -48,10 +48,6 @@ impl TextEdit { TextEditBuilder::default() } - pub fn into_builder(self) -> TextEditBuilder { - TextEditBuilder { indels: self.indels } - } - pub fn insert(offset: TextSize, text: String) -> TextEdit { let mut builder = TextEdit::builder(); builder.insert(offset, text); From 16f0b2fdde2fba8f45cc18a8de11b45751c0f923 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Nov 2020 13:19:56 +0200 Subject: [PATCH 18/18] Actually enable eager completion --- crates/rust-analyzer/src/handlers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 4d1ebf6bf989..c27fd87d768a 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -573,7 +573,7 @@ pub(crate) fn handle_completion( .flat_map(|item| to_proto::completion_item(&line_index, line_endings, item)) .collect(); - let completion_list = lsp_types::CompletionList { is_incomplete: false, items }; + let completion_list = lsp_types::CompletionList { is_incomplete: true, items }; Ok(Some(completion_list.into())) }