2021-10-04 20:13:12 +00:00
//! User (postfix)-snippet definitions.
//!
2021-10-11 19:49:39 +00:00
//! Actual logic is implemented in [`crate::completions::postfix`] and [`crate::completions::snippet`] respectively.
2021-10-05 15:18:40 +00:00
// Feature: User Snippet Completions
//
// rust-analyzer allows the user to define custom (postfix)-snippets that may depend on items to be accessible for the current scope to be applicable.
//
2022-07-11 08:00:40 +00:00
// A custom snippet can be defined by adding it to the `rust-analyzer.completion.snippets.custom` object respectively.
2021-10-05 15:18:40 +00:00
//
// [source,json]
// ----
// {
2022-07-11 08:00:40 +00:00
// "rust-analyzer.completion.snippets.custom": {
2021-10-05 15:18:40 +00:00
// "thread spawn": {
// "prefix": ["spawn", "tspawn"],
// "body": [
// "thread::spawn(move || {",
// "\t$0",
2022-06-27 10:15:59 +00:00
// "});",
2021-10-05 15:18:40 +00:00
// ],
// "description": "Insert a thread::spawn call",
// "requires": "std::thread",
// "scope": "expr",
// }
// }
// }
// ----
//
// In the example above:
//
// * `"thread spawn"` is the name of the snippet.
//
// * `prefix` defines one or more trigger words that will trigger the snippets completion.
// Using `postfix` will instead create a postfix snippet.
//
// * `body` is one or more lines of content joined via newlines for the final output.
//
// * `description` is an optional description of the snippet, if unset the snippet name will be used.
//
// * `requires` is an optional list of item paths that have to be resolvable in the current crate where the completion is rendered.
// On failure of resolution the snippet won't be applicable, otherwise the snippet will insert an import for the items on insertion if
// the items aren't yet in scope.
//
// * `scope` is an optional filter for when the snippet should be applicable. Possible values are:
// ** for Snippet-Scopes: `expr`, `item` (default: `item`)
// ** for Postfix-Snippet-Scopes: `expr`, `type` (default: `expr`)
//
// The `body` field also has access to placeholders as visible in the example as `$0`.
// These placeholders take the form of `$number` or `${number:placeholder_text}` which can be traversed as tabstop in ascending order starting from 1,
// with `$0` being a special case that always comes last.
//
2021-10-12 10:14:24 +00:00
// There is also a special placeholder, `${receiver}`, which will be replaced by the receiver expression for postfix snippets, or a `$0` tabstop in case of normal snippets.
// This replacement for normal snippets allows you to reuse a snippet for both post- and prefix in a single definition.
//
// For the VSCode editor, rust-analyzer also ships with a small set of defaults which can be removed
// by overwriting the settings object mentioned above, the defaults are:
// [source,json]
// ----
// {
// "Arc::new": {
// "postfix": "arc",
// "body": "Arc::new(${receiver})",
// "requires": "std::sync::Arc",
// "description": "Put the expression into an `Arc`",
// "scope": "expr"
// },
// "Rc::new": {
// "postfix": "rc",
// "body": "Rc::new(${receiver})",
// "requires": "std::rc::Rc",
// "description": "Put the expression into an `Rc`",
// "scope": "expr"
// },
// "Box::pin": {
// "postfix": "pinbox",
// "body": "Box::pin(${receiver})",
// "requires": "std::boxed::Box",
// "description": "Put the expression into a pinned `Box`",
// "scope": "expr"
// },
// "Ok": {
// "postfix": "ok",
// "body": "Ok(${receiver})",
// "description": "Wrap the expression in a `Result::Ok`",
// "scope": "expr"
// },
// "Err": {
// "postfix": "err",
// "body": "Err(${receiver})",
// "description": "Wrap the expression in a `Result::Err`",
// "scope": "expr"
// },
// "Some": {
// "postfix": "some",
// "body": "Some(${receiver})",
// "description": "Wrap the expression in an `Option::Some`",
// "scope": "expr"
// }
// }
// ----
2022-04-01 23:42:21 +00:00
use ide_db ::imports ::import_assets ::LocatedImport ;
2021-10-04 17:22:41 +00:00
use itertools ::Itertools ;
2021-10-12 09:47:22 +00:00
use syntax ::{ ast , AstNode , GreenNode , SyntaxNode } ;
2021-10-04 17:22:41 +00:00
2022-04-01 23:42:21 +00:00
use crate ::context ::CompletionContext ;
2021-10-04 17:22:41 +00:00
2021-10-11 19:49:39 +00:00
/// A snippet scope describing where a snippet may apply to.
/// These may differ slightly in meaning depending on the snippet trigger.
2021-10-04 17:22:41 +00:00
#[ derive(Clone, Debug, PartialEq, Eq) ]
pub enum SnippetScope {
Item ,
Expr ,
2021-10-05 15:18:40 +00:00
Type ,
2021-10-04 17:22:41 +00:00
}
2021-10-11 19:49:39 +00:00
/// A user supplied snippet.
2021-10-04 17:22:41 +00:00
#[ derive(Clone, Debug, PartialEq, Eq) ]
pub struct Snippet {
2021-10-11 19:49:39 +00:00
pub postfix_triggers : Box < [ Box < str > ] > ,
pub prefix_triggers : Box < [ Box < str > ] > ,
2021-10-04 17:22:41 +00:00
pub scope : SnippetScope ,
2021-10-11 19:49:39 +00:00
pub description : Option < Box < str > > ,
2021-10-12 09:47:22 +00:00
snippet : String ,
// These are `ast::Path`'s but due to SyntaxNodes not being Send we store these
// and reconstruct them on demand instead. This is cheaper than reparsing them
// from strings
requires : Box < [ GreenNode ] > ,
2021-10-04 17:22:41 +00:00
}
2021-10-05 15:18:40 +00:00
2021-10-04 17:22:41 +00:00
impl Snippet {
pub fn new (
2021-10-05 15:18:40 +00:00
prefix_triggers : & [ String ] ,
postfix_triggers : & [ String ] ,
2021-10-04 17:22:41 +00:00
snippet : & [ String ] ,
2021-10-05 15:18:40 +00:00
description : & str ,
2021-10-04 17:22:41 +00:00
requires : & [ String ] ,
2021-10-04 19:44:33 +00:00
scope : SnippetScope ,
2021-10-04 17:22:41 +00:00
) -> Option < Self > {
2021-10-11 19:49:39 +00:00
if prefix_triggers . is_empty ( ) & & postfix_triggers . is_empty ( ) {
return None ;
}
2021-10-12 09:47:22 +00:00
let ( requires , snippet , description ) = validate_snippet ( snippet , description , requires ) ? ;
2021-10-04 17:22:41 +00:00
Some ( Snippet {
2021-10-05 15:18:40 +00:00
// Box::into doesn't work as that has a Copy bound 😒
2022-06-20 11:17:30 +00:00
postfix_triggers : postfix_triggers . iter ( ) . map ( String ::as_str ) . map ( Into ::into ) . collect ( ) ,
prefix_triggers : prefix_triggers . iter ( ) . map ( String ::as_str ) . map ( Into ::into ) . collect ( ) ,
2021-10-04 19:44:33 +00:00
scope ,
2021-10-04 17:22:41 +00:00
snippet ,
description ,
2021-10-12 09:47:22 +00:00
requires ,
2021-10-04 17:22:41 +00:00
} )
}
2021-10-11 19:49:39 +00:00
/// Returns [`None`] if the required items do not resolve.
2022-07-20 13:02:08 +00:00
pub ( crate ) fn imports ( & self , ctx : & CompletionContext < '_ > ) -> Option < Vec < LocatedImport > > {
2022-04-01 23:42:21 +00:00
import_edits ( ctx , & self . requires )
2021-10-04 17:22:41 +00:00
}
2021-10-05 15:18:40 +00:00
pub fn snippet ( & self ) -> String {
2021-10-12 10:14:24 +00:00
self . snippet . replace ( " ${receiver} " , " $0 " )
2021-10-04 17:22:41 +00:00
}
2021-10-05 15:18:40 +00:00
pub fn postfix_snippet ( & self , receiver : & str ) -> String {
self . snippet . replace ( " ${receiver} " , receiver )
2021-10-04 17:22:41 +00:00
}
}
2022-07-20 13:02:08 +00:00
fn import_edits ( ctx : & CompletionContext < '_ > , requires : & [ GreenNode ] ) -> Option < Vec < LocatedImport > > {
2024-08-10 06:02:03 +00:00
let import_cfg = ctx . config . import_path_config ( ) ;
2024-05-17 19:00:21 +00:00
2021-10-12 09:47:22 +00:00
let resolve = | import : & GreenNode | {
let path = ast ::Path ::cast ( SyntaxNode ::new_root ( import . clone ( ) ) ) ? ;
2021-10-04 20:45:47 +00:00
let item = match ctx . scope . speculative_resolve ( & path ) ? {
hir ::PathResolution ::Def ( def ) = > def . into ( ) ,
_ = > return None ,
} ;
2024-05-22 11:49:56 +00:00
let path = ctx . module . find_use_path (
2022-09-09 18:04:56 +00:00
ctx . db ,
item ,
ctx . config . insert_use . prefix_kind ,
2024-05-17 19:00:21 +00:00
import_cfg ,
2022-09-09 18:04:56 +00:00
) ? ;
2023-12-11 17:37:12 +00:00
Some ( ( path . len ( ) > 1 ) . then ( | | LocatedImport ::new ( path . clone ( ) , item , item ) ) )
2021-10-04 17:22:41 +00:00
} ;
let mut res = Vec ::with_capacity ( requires . len ( ) ) ;
for import in requires {
match resolve ( import ) {
Some ( first ) = > res . extend ( first ) ,
2021-10-04 20:45:47 +00:00
None = > return None ,
2021-10-04 17:22:41 +00:00
}
}
2021-10-04 20:45:47 +00:00
Some ( res )
}
fn validate_snippet (
snippet : & [ String ] ,
2021-10-05 15:18:40 +00:00
description : & str ,
2021-10-04 20:45:47 +00:00
requires : & [ String ] ,
2021-10-12 09:47:22 +00:00
) -> Option < ( Box < [ GreenNode ] > , String , Option < Box < str > > ) > {
let mut imports = Vec ::with_capacity ( requires . len ( ) ) ;
for path in requires . iter ( ) {
2024-07-17 15:35:40 +00:00
let use_path =
ast ::SourceFile ::parse ( & format! ( " use {path} ; " ) , syntax ::Edition ::CURRENT_FIXME )
. syntax_node ( )
. descendants ( )
. find_map ( ast ::Path ::cast ) ? ;
2021-12-28 15:57:13 +00:00
if use_path . syntax ( ) . text ( ) ! = path . as_str ( ) {
2021-10-12 09:47:22 +00:00
return None ;
}
2021-12-28 15:57:13 +00:00
let green = use_path . syntax ( ) . green ( ) . into_owned ( ) ;
2021-10-12 09:47:22 +00:00
imports . push ( green ) ;
2021-10-04 20:45:47 +00:00
}
let snippet = snippet . iter ( ) . join ( " \n " ) ;
2021-11-24 15:01:33 +00:00
let description = ( ! description . is_empty ( ) )
. then ( | | description . split_once ( '\n' ) . map_or ( description , | ( it , _ ) | it ) )
. map ( ToOwned ::to_owned )
. map ( Into ::into ) ;
2021-10-12 09:47:22 +00:00
Some ( ( imports . into_boxed_slice ( ) , snippet , description ) )
2021-10-04 17:22:41 +00:00
}