mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
3998: Make add_function generate functions in other modules via qualified path r=matklad a=TimoFreiberg Additional feature for #3639 - [x] Add tests for paths with more segments - [x] Make generating the function in another file work - [x] Add `pub` or `pub(crate)` to the generated function if it's generated in a different module - [x] Make the assist jump to the edited file - [x] Enable file support in the `check_assist` helper 4006: Syntax highlighting for format strings r=matklad a=ltentrup I have an implementation for syntax highlighting for format string modifiers `{}`. The first commit refactors the changes in #3826 into a separate struct. The second commit implements the highlighting: first we check in a macro call whether the macro is a format macro from `std`. In this case, we remember the format string node. If we encounter this node during syntax highlighting, we check for the format modifiers `{}` using regular expressions. There are a few places which I am not quite sure: - Is the way I extract the macro names correct? - Is the `HighlightTag::Attribute` suitable for highlighting the `{}`? Let me know what you think, any feedback is welcome! Co-authored-by: Timo Freiberg <timo.freiberg@gmail.com> Co-authored-by: Leander Tentrup <leander.tentrup@gmail.com> Co-authored-by: Leander Tentrup <ltentrup@users.noreply.github.com>
This commit is contained in:
commit
51a0058d4c
12 changed files with 940 additions and 95 deletions
|
@ -10,7 +10,7 @@ use ra_syntax::{
|
||||||
};
|
};
|
||||||
use ra_text_edit::TextEditBuilder;
|
use ra_text_edit::TextEditBuilder;
|
||||||
|
|
||||||
use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
|
use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
|
||||||
use algo::SyntaxRewriter;
|
use algo::SyntaxRewriter;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -180,6 +180,7 @@ pub(crate) struct ActionBuilder {
|
||||||
edit: TextEditBuilder,
|
edit: TextEditBuilder,
|
||||||
cursor_position: Option<TextUnit>,
|
cursor_position: Option<TextUnit>,
|
||||||
target: Option<TextRange>,
|
target: Option<TextRange>,
|
||||||
|
file: AssistFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActionBuilder {
|
impl ActionBuilder {
|
||||||
|
@ -241,11 +242,16 @@ impl ActionBuilder {
|
||||||
algo::diff(&node, &new).into_text_edit(&mut self.edit)
|
algo::diff(&node, &new).into_text_edit(&mut self.edit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_file(&mut self, assist_file: AssistFile) {
|
||||||
|
self.file = assist_file
|
||||||
|
}
|
||||||
|
|
||||||
fn build(self) -> AssistAction {
|
fn build(self) -> AssistAction {
|
||||||
AssistAction {
|
AssistAction {
|
||||||
edit: self.edit.finish(),
|
edit: self.edit.finish(),
|
||||||
cursor_position: self.cursor_position,
|
cursor_position: self.cursor_position,
|
||||||
target: self.target,
|
target: self.target,
|
||||||
|
file: self.file,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ fn doctest_add_function() {
|
||||||
struct Baz;
|
struct Baz;
|
||||||
fn baz() -> Baz { Baz }
|
fn baz() -> Baz { Baz }
|
||||||
fn foo() {
|
fn foo() {
|
||||||
bar<|>("", baz());
|
bar<|>("", baz());
|
||||||
}
|
}
|
||||||
|
|
||||||
"#####,
|
"#####,
|
||||||
|
@ -74,7 +74,7 @@ fn foo() {
|
||||||
struct Baz;
|
struct Baz;
|
||||||
fn baz() -> Baz { Baz }
|
fn baz() -> Baz { Baz }
|
||||||
fn foo() {
|
fn foo() {
|
||||||
bar("", baz());
|
bar("", baz());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bar(arg: &str, baz: Baz) {
|
fn bar(arg: &str, baz: Baz) {
|
||||||
|
|
|
@ -3,8 +3,8 @@ use ra_syntax::{
|
||||||
SyntaxKind, SyntaxNode, TextUnit,
|
SyntaxKind, SyntaxNode, TextUnit,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Assist, AssistCtx, AssistId};
|
use crate::{Assist, AssistCtx, AssistFile, AssistId};
|
||||||
use ast::{edit::IndentLevel, ArgListOwner, CallExpr, Expr};
|
use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner};
|
||||||
use hir::HirDisplay;
|
use hir::HirDisplay;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
// struct Baz;
|
// struct Baz;
|
||||||
// fn baz() -> Baz { Baz }
|
// fn baz() -> Baz { Baz }
|
||||||
// fn foo() {
|
// fn foo() {
|
||||||
// bar<|>("", baz());
|
// bar<|>("", baz());
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// ```
|
// ```
|
||||||
|
@ -25,7 +25,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
// struct Baz;
|
// struct Baz;
|
||||||
// fn baz() -> Baz { Baz }
|
// fn baz() -> Baz { Baz }
|
||||||
// fn foo() {
|
// fn foo() {
|
||||||
// bar("", baz());
|
// bar("", baz());
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// fn bar(arg: &str, baz: Baz) {
|
// fn bar(arg: &str, baz: Baz) {
|
||||||
|
@ -38,21 +38,30 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
|
||||||
let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
|
let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
|
||||||
let path = path_expr.path()?;
|
let path = path_expr.path()?;
|
||||||
|
|
||||||
if path.qualifier().is_some() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.sema.resolve_path(&path).is_some() {
|
if ctx.sema.resolve_path(&path).is_some() {
|
||||||
// The function call already resolves, no need to add a function
|
// The function call already resolves, no need to add a function
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let function_builder = FunctionBuilder::from_call(&ctx, &call)?;
|
let target_module = if let Some(qualifier) = path.qualifier() {
|
||||||
|
if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) =
|
||||||
|
ctx.sema.resolve_path(&qualifier)
|
||||||
|
{
|
||||||
|
Some(module.definition_source(ctx.sema.db))
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
|
||||||
|
|
||||||
ctx.add_assist(AssistId("add_function"), "Add function", |edit| {
|
ctx.add_assist(AssistId("add_function"), "Add function", |edit| {
|
||||||
edit.target(call.syntax().text_range());
|
edit.target(call.syntax().text_range());
|
||||||
|
|
||||||
if let Some(function_template) = function_builder.render() {
|
if let Some(function_template) = function_builder.render() {
|
||||||
|
edit.set_file(function_template.file);
|
||||||
edit.set_cursor(function_template.cursor_offset);
|
edit.set_cursor(function_template.cursor_offset);
|
||||||
edit.insert(function_template.insert_offset, function_template.fn_def.to_string());
|
edit.insert(function_template.insert_offset, function_template.fn_def.to_string());
|
||||||
}
|
}
|
||||||
|
@ -63,29 +72,67 @@ struct FunctionTemplate {
|
||||||
insert_offset: TextUnit,
|
insert_offset: TextUnit,
|
||||||
cursor_offset: TextUnit,
|
cursor_offset: TextUnit,
|
||||||
fn_def: ast::SourceFile,
|
fn_def: ast::SourceFile,
|
||||||
|
file: AssistFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FunctionBuilder {
|
struct FunctionBuilder {
|
||||||
append_fn_at: SyntaxNode,
|
target: GeneratedFunctionTarget,
|
||||||
fn_name: ast::Name,
|
fn_name: ast::Name,
|
||||||
type_params: Option<ast::TypeParamList>,
|
type_params: Option<ast::TypeParamList>,
|
||||||
params: ast::ParamList,
|
params: ast::ParamList,
|
||||||
|
file: AssistFile,
|
||||||
|
needs_pub: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FunctionBuilder {
|
impl FunctionBuilder {
|
||||||
fn from_call(ctx: &AssistCtx, call: &ast::CallExpr) -> Option<Self> {
|
/// Prepares a generated function that matches `call` in `generate_in`
|
||||||
let append_fn_at = next_space_for_fn(&call)?;
|
/// (or as close to `call` as possible, if `generate_in` is `None`)
|
||||||
let fn_name = fn_name(&call)?;
|
fn from_call(
|
||||||
|
ctx: &AssistCtx,
|
||||||
|
call: &ast::CallExpr,
|
||||||
|
path: &ast::Path,
|
||||||
|
target_module: Option<hir::InFile<hir::ModuleSource>>,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let needs_pub = target_module.is_some();
|
||||||
|
let mut file = AssistFile::default();
|
||||||
|
let target = if let Some(target_module) = target_module {
|
||||||
|
let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?;
|
||||||
|
file = in_file;
|
||||||
|
target
|
||||||
|
} else {
|
||||||
|
next_space_for_fn_after_call_site(&call)?
|
||||||
|
};
|
||||||
|
let fn_name = fn_name(&path)?;
|
||||||
let (type_params, params) = fn_args(ctx, &call)?;
|
let (type_params, params) = fn_args(ctx, &call)?;
|
||||||
Some(Self { append_fn_at, fn_name, type_params, params })
|
Some(Self { target, fn_name, type_params, params, file, needs_pub })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(self) -> Option<FunctionTemplate> {
|
fn render(self) -> Option<FunctionTemplate> {
|
||||||
let placeholder_expr = ast::make::expr_todo();
|
let placeholder_expr = ast::make::expr_todo();
|
||||||
let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr));
|
let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr));
|
||||||
let fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
|
let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
|
||||||
let fn_def = ast::make::add_newlines(2, fn_def);
|
if self.needs_pub {
|
||||||
let fn_def = IndentLevel::from_node(&self.append_fn_at).increase_indent(fn_def);
|
fn_def = ast::make::add_pub_crate_modifier(fn_def);
|
||||||
let insert_offset = self.append_fn_at.text_range().end();
|
}
|
||||||
|
|
||||||
|
let (fn_def, insert_offset) = match self.target {
|
||||||
|
GeneratedFunctionTarget::BehindItem(it) => {
|
||||||
|
let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def);
|
||||||
|
let indented = IndentLevel::from_node(&it).increase_indent(with_leading_blank_line);
|
||||||
|
(indented, it.text_range().end())
|
||||||
|
}
|
||||||
|
GeneratedFunctionTarget::InEmptyItemList(it) => {
|
||||||
|
let indent_once = IndentLevel(1);
|
||||||
|
let indent = IndentLevel::from_node(it.syntax());
|
||||||
|
|
||||||
|
let fn_def = ast::make::add_leading_newlines(1, fn_def);
|
||||||
|
let fn_def = indent_once.increase_indent(fn_def);
|
||||||
|
let fn_def = ast::make::add_trailing_newlines(1, fn_def);
|
||||||
|
let fn_def = indent.increase_indent(fn_def);
|
||||||
|
(fn_def, it.syntax().text_range().start() + TextUnit::from_usize(1))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let cursor_offset_from_fn_start = fn_def
|
let cursor_offset_from_fn_start = fn_def
|
||||||
.syntax()
|
.syntax()
|
||||||
.descendants()
|
.descendants()
|
||||||
|
@ -94,19 +141,24 @@ impl FunctionBuilder {
|
||||||
.text_range()
|
.text_range()
|
||||||
.start();
|
.start();
|
||||||
let cursor_offset = insert_offset + cursor_offset_from_fn_start;
|
let cursor_offset = insert_offset + cursor_offset_from_fn_start;
|
||||||
Some(FunctionTemplate { insert_offset, cursor_offset, fn_def })
|
Some(FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fn_name(call: &CallExpr) -> Option<ast::Name> {
|
enum GeneratedFunctionTarget {
|
||||||
let name = call.expr()?.syntax().to_string();
|
BehindItem(SyntaxNode),
|
||||||
|
InEmptyItemList(ast::ItemList),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fn_name(call: &ast::Path) -> Option<ast::Name> {
|
||||||
|
let name = call.segment()?.syntax().to_string();
|
||||||
Some(ast::make::name(&name))
|
Some(ast::make::name(&name))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the type variables and arguments required for the generated function
|
/// Computes the type variables and arguments required for the generated function
|
||||||
fn fn_args(
|
fn fn_args(
|
||||||
ctx: &AssistCtx,
|
ctx: &AssistCtx,
|
||||||
call: &CallExpr,
|
call: &ast::CallExpr,
|
||||||
) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
|
) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
|
||||||
let mut arg_names = Vec::new();
|
let mut arg_names = Vec::new();
|
||||||
let mut arg_types = Vec::new();
|
let mut arg_types = Vec::new();
|
||||||
|
@ -158,9 +210,9 @@ fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fn_arg_name(fn_arg: &Expr) -> Option<String> {
|
fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
|
||||||
match fn_arg {
|
match fn_arg {
|
||||||
Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
|
ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
|
||||||
_ => Some(
|
_ => Some(
|
||||||
fn_arg
|
fn_arg
|
||||||
.syntax()
|
.syntax()
|
||||||
|
@ -172,7 +224,7 @@ fn fn_arg_name(fn_arg: &Expr) -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> {
|
fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> {
|
||||||
let ty = ctx.sema.type_of_expr(fn_arg)?;
|
let ty = ctx.sema.type_of_expr(fn_arg)?;
|
||||||
if ty.is_unknown() {
|
if ty.is_unknown() {
|
||||||
return None;
|
return None;
|
||||||
|
@ -184,7 +236,7 @@ fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> {
|
||||||
/// directly after the current block
|
/// directly after the current block
|
||||||
/// We want to write the generated function directly after
|
/// We want to write the generated function directly after
|
||||||
/// fns, impls or macro calls, but inside mods
|
/// fns, impls or macro calls, but inside mods
|
||||||
fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> {
|
fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFunctionTarget> {
|
||||||
let mut ancestors = expr.syntax().ancestors().peekable();
|
let mut ancestors = expr.syntax().ancestors().peekable();
|
||||||
let mut last_ancestor: Option<SyntaxNode> = None;
|
let mut last_ancestor: Option<SyntaxNode> = None;
|
||||||
while let Some(next_ancestor) = ancestors.next() {
|
while let Some(next_ancestor) = ancestors.next() {
|
||||||
|
@ -201,7 +253,32 @@ fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> {
|
||||||
}
|
}
|
||||||
last_ancestor = Some(next_ancestor);
|
last_ancestor = Some(next_ancestor);
|
||||||
}
|
}
|
||||||
last_ancestor
|
last_ancestor.map(GeneratedFunctionTarget::BehindItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_space_for_fn_in_module(
|
||||||
|
db: &dyn hir::db::AstDatabase,
|
||||||
|
module: hir::InFile<hir::ModuleSource>,
|
||||||
|
) -> Option<(AssistFile, GeneratedFunctionTarget)> {
|
||||||
|
let file = module.file_id.original_file(db);
|
||||||
|
let assist_file = AssistFile::TargetFile(file);
|
||||||
|
let assist_item = match module.value {
|
||||||
|
hir::ModuleSource::SourceFile(it) => {
|
||||||
|
if let Some(last_item) = it.items().last() {
|
||||||
|
GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
|
||||||
|
} else {
|
||||||
|
GeneratedFunctionTarget::BehindItem(it.syntax().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hir::ModuleSource::Module(it) => {
|
||||||
|
if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) {
|
||||||
|
GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
|
||||||
|
} else {
|
||||||
|
GeneratedFunctionTarget::InEmptyItemList(it.item_list()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some((assist_file, assist_item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -713,6 +790,111 @@ fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_function_in_module() {
|
||||||
|
check_assist(
|
||||||
|
add_function,
|
||||||
|
r"
|
||||||
|
mod bar {}
|
||||||
|
|
||||||
|
fn foo() {
|
||||||
|
bar::my_fn<|>()
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r"
|
||||||
|
mod bar {
|
||||||
|
pub(crate) fn my_fn() {
|
||||||
|
<|>todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo() {
|
||||||
|
bar::my_fn()
|
||||||
|
}
|
||||||
|
",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_function_in_module_containing_other_items() {
|
||||||
|
check_assist(
|
||||||
|
add_function,
|
||||||
|
r"
|
||||||
|
mod bar {
|
||||||
|
fn something_else() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo() {
|
||||||
|
bar::my_fn<|>()
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r"
|
||||||
|
mod bar {
|
||||||
|
fn something_else() {}
|
||||||
|
|
||||||
|
pub(crate) fn my_fn() {
|
||||||
|
<|>todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo() {
|
||||||
|
bar::my_fn()
|
||||||
|
}
|
||||||
|
",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_function_in_nested_module() {
|
||||||
|
check_assist(
|
||||||
|
add_function,
|
||||||
|
r"
|
||||||
|
mod bar {
|
||||||
|
mod baz {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo() {
|
||||||
|
bar::baz::my_fn<|>()
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r"
|
||||||
|
mod bar {
|
||||||
|
mod baz {
|
||||||
|
pub(crate) fn my_fn() {
|
||||||
|
<|>todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo() {
|
||||||
|
bar::baz::my_fn()
|
||||||
|
}
|
||||||
|
",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_function_in_another_file() {
|
||||||
|
check_assist(
|
||||||
|
add_function,
|
||||||
|
r"
|
||||||
|
//- /main.rs
|
||||||
|
mod foo;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
foo::bar<|>()
|
||||||
|
}
|
||||||
|
//- /foo.rs
|
||||||
|
",
|
||||||
|
r"
|
||||||
|
|
||||||
|
|
||||||
|
pub(crate) fn bar() {
|
||||||
|
<|>todo!()
|
||||||
|
}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_function_not_applicable_if_function_already_exists() {
|
fn add_function_not_applicable_if_function_already_exists() {
|
||||||
check_assist_not_applicable(
|
check_assist_not_applicable(
|
||||||
|
|
|
@ -17,7 +17,7 @@ mod doc_tests;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod ast_transform;
|
pub mod ast_transform;
|
||||||
|
|
||||||
use ra_db::FileRange;
|
use ra_db::{FileId, FileRange};
|
||||||
use ra_ide_db::RootDatabase;
|
use ra_ide_db::RootDatabase;
|
||||||
use ra_syntax::{TextRange, TextUnit};
|
use ra_syntax::{TextRange, TextUnit};
|
||||||
use ra_text_edit::TextEdit;
|
use ra_text_edit::TextEdit;
|
||||||
|
@ -54,6 +54,7 @@ pub struct AssistAction {
|
||||||
pub cursor_position: Option<TextUnit>,
|
pub cursor_position: Option<TextUnit>,
|
||||||
// FIXME: This belongs to `AssistLabel`
|
// FIXME: This belongs to `AssistLabel`
|
||||||
pub target: Option<TextRange>,
|
pub target: Option<TextRange>,
|
||||||
|
pub file: AssistFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -63,6 +64,18 @@ pub struct ResolvedAssist {
|
||||||
pub action: AssistAction,
|
pub action: AssistAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum AssistFile {
|
||||||
|
CurrentFile,
|
||||||
|
TargetFile(FileId),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AssistFile {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::CurrentFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return all the assists applicable at the given position.
|
/// Return all the assists applicable at the given position.
|
||||||
///
|
///
|
||||||
/// Assists are returned in the "unresolved" state, that is only labels are
|
/// Assists are returned in the "unresolved" state, that is only labels are
|
||||||
|
@ -184,7 +197,7 @@ mod helpers {
|
||||||
use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
|
use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
|
||||||
use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
|
use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
|
||||||
|
|
||||||
use crate::{AssistCtx, AssistHandler};
|
use crate::{AssistCtx, AssistFile, AssistHandler};
|
||||||
use hir::Semantics;
|
use hir::Semantics;
|
||||||
|
|
||||||
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
|
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
|
||||||
|
@ -246,7 +259,13 @@ mod helpers {
|
||||||
(Some(assist), ExpectedResult::After(after)) => {
|
(Some(assist), ExpectedResult::After(after)) => {
|
||||||
let action = assist.0[0].action.clone().unwrap();
|
let action = assist.0[0].action.clone().unwrap();
|
||||||
|
|
||||||
let mut actual = action.edit.apply(&text_without_caret);
|
let assisted_file_text = if let AssistFile::TargetFile(file_id) = action.file {
|
||||||
|
db.file_text(file_id).as_ref().to_owned()
|
||||||
|
} else {
|
||||||
|
text_without_caret
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut actual = action.edit.apply(&assisted_file_text);
|
||||||
match action.cursor_position {
|
match action.cursor_position {
|
||||||
None => {
|
None => {
|
||||||
if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
|
if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
|
||||||
|
|
|
@ -37,6 +37,10 @@ fn action_to_edit(
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
assist_label: &AssistLabel,
|
assist_label: &AssistLabel,
|
||||||
) -> SourceChange {
|
) -> SourceChange {
|
||||||
|
let file_id = match action.file {
|
||||||
|
ra_assists::AssistFile::TargetFile(it) => it,
|
||||||
|
_ => file_id,
|
||||||
|
};
|
||||||
let file_edit = SourceFileEdit { file_id, edit: action.edit };
|
let file_edit = SourceFileEdit { file_id, edit: action.edit };
|
||||||
SourceChange::source_file_edit(assist_label.label.clone(), file_edit)
|
SourceChange::source_file_edit(assist_label.label.clone(), file_edit)
|
||||||
.with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id }))
|
.with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id }))
|
||||||
|
|
82
crates/ra_ide/src/snapshots/highlight_strings.html
Normal file
82
crates/ra_ide/src/snapshots/highlight_strings.html
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body { margin: 0; }
|
||||||
|
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
|
||||||
|
|
||||||
|
.lifetime { color: #DFAF8F; font-style: italic; }
|
||||||
|
.comment { color: #7F9F7F; }
|
||||||
|
.struct, .enum { color: #7CB8BB; }
|
||||||
|
.enum_variant { color: #BDE0F3; }
|
||||||
|
.string_literal { color: #CC9393; }
|
||||||
|
.field { color: #94BFF3; }
|
||||||
|
.function { color: #93E0E3; }
|
||||||
|
.parameter { color: #94BFF3; }
|
||||||
|
.text { color: #DCDCCC; }
|
||||||
|
.type { color: #7CB8BB; }
|
||||||
|
.builtin_type { color: #8CD0D3; }
|
||||||
|
.type_param { color: #DFAF8F; }
|
||||||
|
.attribute { color: #94BFF3; }
|
||||||
|
.numeric_literal { color: #BFEBBF; }
|
||||||
|
.macro { color: #94BFF3; }
|
||||||
|
.module { color: #AFD8AF; }
|
||||||
|
.variable { color: #DCDCCC; }
|
||||||
|
.mutable { text-decoration: underline; }
|
||||||
|
|
||||||
|
.keyword { color: #F0DFAF; font-weight: bold; }
|
||||||
|
.keyword.unsafe { color: #BC8383; font-weight: bold; }
|
||||||
|
.control { font-style: italic; }
|
||||||
|
</style>
|
||||||
|
<pre><code><span class="macro">macro_rules!</span> println {
|
||||||
|
($($arg:tt)*) => ({
|
||||||
|
$<span class="keyword">crate</span>::io::_print($<span class="keyword">crate</span>::format_args_nl!($($arg)*));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[rustc_builtin_macro]
|
||||||
|
<span class="macro">macro_rules!</span> format_args_nl {
|
||||||
|
($fmt:expr) => {{ <span class="comment">/* compiler built-in */</span> }};
|
||||||
|
($fmt:expr, $($args:tt)*) => {{ <span class="comment">/* compiler built-in */</span> }};
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="keyword">fn</span> <span class="function declaration">main</span>() {
|
||||||
|
<span class="comment">// from https://doc.rust-lang.org/std/fmt/index.html</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello"</span>); <span class="comment">// => "Hello"</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello, </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>); <span class="comment">// => "Hello, world!"</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"The number is </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>); <span class="comment">// => "The number is 1"</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">?</span><span class="attribute">}</span><span class="string_literal">"</span>, (<span class="numeric_literal">3</span>, <span class="numeric_literal">4</span>)); <span class="comment">// => "(3, 4)"</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">value</span><span class="attribute">}</span><span class="string_literal">"</span>, value=<span class="numeric_literal">4</span>); <span class="comment">// => "4"</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, <span class="numeric_literal">2</span>); <span class="comment">// => "1 2"</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">4</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">42</span>); <span class="comment">// => "0042" with leading zerosV</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, <span class="numeric_literal">2</span>); <span class="comment">// => "2 1 1 2"</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">argument</span><span class="attribute">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// => "test"</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// => "2 1"</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">a</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="variable">c</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="variable">b</span><span class="attribute">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// => "a 3 b"</span>
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">1</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="variable">width</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, width = <span class="numeric_literal">5</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute"><</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">-</span><span class="attribute"><</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">^</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">></span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">+</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">#</span><span class="variable">x</span><span class="string_literal">}!"</span>, <span class="numeric_literal">27</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">0.01</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">0</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">0.01</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">1</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="variable">number</span><span class="attribute">:</span><span class="attribute">.</span><span class="variable">prec</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, prec = <span class="numeric_literal">5</span>, number = <span class="numeric_literal">0.01</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 fractional digits"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="numeric_literal">1234.56</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">></span><span class="numeric_literal">8</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 right-aligned characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"Hello {{}}"</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"{{ Hello"</span>);
|
||||||
|
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>);
|
||||||
|
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">\x41</span><span class="attribute">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>);
|
||||||
|
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">ничоси</span><span class="attribute">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>);
|
||||||
|
}</code></pre>
|
|
@ -12,7 +12,7 @@ use ra_ide_db::{
|
||||||
};
|
};
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
ast::{self, HasQuotes, HasStringValue},
|
ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue},
|
||||||
AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
|
AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
|
||||||
SyntaxKind::*,
|
SyntaxKind::*,
|
||||||
SyntaxToken, TextRange, WalkEvent, T,
|
SyntaxToken, TextRange, WalkEvent, T,
|
||||||
|
@ -21,6 +21,7 @@ use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use crate::{call_info::ActiveParameter, Analysis, FileId};
|
use crate::{call_info::ActiveParameter, Analysis, FileId};
|
||||||
|
|
||||||
|
use ast::FormatSpecifier;
|
||||||
pub(crate) use html::highlight_as_html;
|
pub(crate) use html::highlight_as_html;
|
||||||
pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
|
pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
|
||||||
|
|
||||||
|
@ -31,6 +32,81 @@ pub struct HighlightedRange {
|
||||||
pub binding_hash: Option<u64>,
|
pub binding_hash: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct HighlightedRangeStack {
|
||||||
|
stack: Vec<Vec<HighlightedRange>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We use a stack to implement the flattening logic for the highlighted
|
||||||
|
/// syntax ranges.
|
||||||
|
impl HighlightedRangeStack {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { stack: vec![Vec::new()] }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self) {
|
||||||
|
self.stack.push(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flattens the highlighted ranges.
|
||||||
|
///
|
||||||
|
/// For example `#[cfg(feature = "foo")]` contains the nested ranges:
|
||||||
|
/// 1) parent-range: Attribute [0, 23)
|
||||||
|
/// 2) child-range: String [16, 21)
|
||||||
|
///
|
||||||
|
/// The following code implements the flattening, for our example this results to:
|
||||||
|
/// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
|
||||||
|
fn pop(&mut self) {
|
||||||
|
let children = self.stack.pop().unwrap();
|
||||||
|
let prev = self.stack.last_mut().unwrap();
|
||||||
|
let needs_flattening = !children.is_empty()
|
||||||
|
&& !prev.is_empty()
|
||||||
|
&& children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
|
||||||
|
if !needs_flattening {
|
||||||
|
prev.extend(children);
|
||||||
|
} else {
|
||||||
|
let mut parent = prev.pop().unwrap();
|
||||||
|
for ele in children {
|
||||||
|
assert!(ele.range.is_subrange(&parent.range));
|
||||||
|
let mut cloned = parent.clone();
|
||||||
|
parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
|
||||||
|
cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
|
||||||
|
if !parent.range.is_empty() {
|
||||||
|
prev.push(parent);
|
||||||
|
}
|
||||||
|
prev.push(ele);
|
||||||
|
parent = cloned;
|
||||||
|
}
|
||||||
|
if !parent.range.is_empty() {
|
||||||
|
prev.push(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, range: HighlightedRange) {
|
||||||
|
self.stack
|
||||||
|
.last_mut()
|
||||||
|
.expect("during DFS traversal, the stack must not be empty")
|
||||||
|
.push(range)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flattened(mut self) -> Vec<HighlightedRange> {
|
||||||
|
assert_eq!(
|
||||||
|
self.stack.len(),
|
||||||
|
1,
|
||||||
|
"after DFS traversal, the stack should only contain a single element"
|
||||||
|
);
|
||||||
|
let mut res = self.stack.pop().unwrap();
|
||||||
|
res.sort_by_key(|range| range.range.start());
|
||||||
|
// Check that ranges are sorted and disjoint
|
||||||
|
assert!(res
|
||||||
|
.iter()
|
||||||
|
.zip(res.iter().skip(1))
|
||||||
|
.all(|(left, right)| left.range.end() <= right.range.start()));
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn highlight(
|
pub(crate) fn highlight(
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
|
@ -57,52 +133,18 @@ pub(crate) fn highlight(
|
||||||
let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
|
let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
|
||||||
// We use a stack for the DFS traversal below.
|
// We use a stack for the DFS traversal below.
|
||||||
// When we leave a node, the we use it to flatten the highlighted ranges.
|
// When we leave a node, the we use it to flatten the highlighted ranges.
|
||||||
let mut res: Vec<Vec<HighlightedRange>> = vec![Vec::new()];
|
let mut stack = HighlightedRangeStack::new();
|
||||||
|
|
||||||
let mut current_macro_call: Option<ast::MacroCall> = None;
|
let mut current_macro_call: Option<ast::MacroCall> = None;
|
||||||
|
let mut format_string: Option<SyntaxElement> = None;
|
||||||
|
|
||||||
// Walk all nodes, keeping track of whether we are inside a macro or not.
|
// Walk all nodes, keeping track of whether we are inside a macro or not.
|
||||||
// If in macro, expand it first and highlight the expanded code.
|
// If in macro, expand it first and highlight the expanded code.
|
||||||
for event in root.preorder_with_tokens() {
|
for event in root.preorder_with_tokens() {
|
||||||
match &event {
|
match &event {
|
||||||
WalkEvent::Enter(_) => res.push(Vec::new()),
|
WalkEvent::Enter(_) => stack.push(),
|
||||||
WalkEvent::Leave(_) => {
|
WalkEvent::Leave(_) => stack.pop(),
|
||||||
/* Flattens the highlighted ranges.
|
|
||||||
*
|
|
||||||
* For example `#[cfg(feature = "foo")]` contains the nested ranges:
|
|
||||||
* 1) parent-range: Attribute [0, 23)
|
|
||||||
* 2) child-range: String [16, 21)
|
|
||||||
*
|
|
||||||
* The following code implements the flattening, for our example this results to:
|
|
||||||
* `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
|
|
||||||
*/
|
|
||||||
let children = res.pop().unwrap();
|
|
||||||
let prev = res.last_mut().unwrap();
|
|
||||||
let needs_flattening = !children.is_empty()
|
|
||||||
&& !prev.is_empty()
|
|
||||||
&& children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
|
|
||||||
if !needs_flattening {
|
|
||||||
prev.extend(children);
|
|
||||||
} else {
|
|
||||||
let mut parent = prev.pop().unwrap();
|
|
||||||
for ele in children {
|
|
||||||
assert!(ele.range.is_subrange(&parent.range));
|
|
||||||
let mut cloned = parent.clone();
|
|
||||||
parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
|
|
||||||
cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
|
|
||||||
if !parent.range.is_empty() {
|
|
||||||
prev.push(parent);
|
|
||||||
}
|
|
||||||
prev.push(ele);
|
|
||||||
parent = cloned;
|
|
||||||
}
|
|
||||||
if !parent.range.is_empty() {
|
|
||||||
prev.push(parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let current = res.last_mut().expect("during DFS traversal, the stack must not be empty");
|
|
||||||
|
|
||||||
let event_range = match &event {
|
let event_range = match &event {
|
||||||
WalkEvent::Enter(it) => it.text_range(),
|
WalkEvent::Enter(it) => it.text_range(),
|
||||||
|
@ -119,7 +161,7 @@ pub(crate) fn highlight(
|
||||||
WalkEvent::Enter(Some(mc)) => {
|
WalkEvent::Enter(Some(mc)) => {
|
||||||
current_macro_call = Some(mc.clone());
|
current_macro_call = Some(mc.clone());
|
||||||
if let Some(range) = macro_call_range(&mc) {
|
if let Some(range) = macro_call_range(&mc) {
|
||||||
current.push(HighlightedRange {
|
stack.add(HighlightedRange {
|
||||||
range,
|
range,
|
||||||
highlight: HighlightTag::Macro.into(),
|
highlight: HighlightTag::Macro.into(),
|
||||||
binding_hash: None,
|
binding_hash: None,
|
||||||
|
@ -130,6 +172,7 @@ pub(crate) fn highlight(
|
||||||
WalkEvent::Leave(Some(mc)) => {
|
WalkEvent::Leave(Some(mc)) => {
|
||||||
assert!(current_macro_call == Some(mc));
|
assert!(current_macro_call == Some(mc));
|
||||||
current_macro_call = None;
|
current_macro_call = None;
|
||||||
|
format_string = None;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -150,6 +193,30 @@ pub(crate) fn highlight(
|
||||||
};
|
};
|
||||||
let token = sema.descend_into_macros(token.clone());
|
let token = sema.descend_into_macros(token.clone());
|
||||||
let parent = token.parent();
|
let parent = token.parent();
|
||||||
|
|
||||||
|
// Check if macro takes a format string and remember it for highlighting later.
|
||||||
|
// The macros that accept a format string expand to a compiler builtin macros
|
||||||
|
// `format_args` and `format_args_nl`.
|
||||||
|
if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) {
|
||||||
|
if let Some(name) =
|
||||||
|
fmt_macro_call.path().and_then(|p| p.segment()).and_then(|s| s.name_ref())
|
||||||
|
{
|
||||||
|
match name.text().as_str() {
|
||||||
|
"format_args" | "format_args_nl" => {
|
||||||
|
format_string = parent
|
||||||
|
.children_with_tokens()
|
||||||
|
.filter(|t| t.kind() != WHITESPACE)
|
||||||
|
.nth(1)
|
||||||
|
.filter(|e| {
|
||||||
|
ast::String::can_cast(e.kind())
|
||||||
|
|| ast::RawString::can_cast(e.kind())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We only care Name and Name_ref
|
// We only care Name and Name_ref
|
||||||
match (token.kind(), parent.kind()) {
|
match (token.kind(), parent.kind()) {
|
||||||
(IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
|
(IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
|
||||||
|
@ -161,27 +228,72 @@ pub(crate) fn highlight(
|
||||||
|
|
||||||
if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
|
if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
|
||||||
let expanded = element_to_highlight.as_token().unwrap().clone();
|
let expanded = element_to_highlight.as_token().unwrap().clone();
|
||||||
if highlight_injection(current, &sema, token, expanded).is_some() {
|
if highlight_injection(&mut stack, &sema, token, expanded).is_some() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let is_format_string = format_string.as_ref() == Some(&element_to_highlight);
|
||||||
|
|
||||||
if let Some((highlight, binding_hash)) =
|
if let Some((highlight, binding_hash)) =
|
||||||
highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight)
|
highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone())
|
||||||
{
|
{
|
||||||
current.push(HighlightedRange { range, highlight, binding_hash });
|
stack.add(HighlightedRange { range, highlight, binding_hash });
|
||||||
|
if let Some(string) =
|
||||||
|
element_to_highlight.as_token().cloned().and_then(ast::String::cast)
|
||||||
|
{
|
||||||
|
stack.push();
|
||||||
|
if is_format_string {
|
||||||
|
string.lex_format_specifier(|piece_range, kind| {
|
||||||
|
if let Some(highlight) = highlight_format_specifier(kind) {
|
||||||
|
stack.add(HighlightedRange {
|
||||||
|
range: piece_range + range.start(),
|
||||||
|
highlight: highlight.into(),
|
||||||
|
binding_hash: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stack.pop();
|
||||||
|
} else if let Some(string) =
|
||||||
|
element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
|
||||||
|
{
|
||||||
|
stack.push();
|
||||||
|
if is_format_string {
|
||||||
|
string.lex_format_specifier(|piece_range, kind| {
|
||||||
|
if let Some(highlight) = highlight_format_specifier(kind) {
|
||||||
|
stack.add(HighlightedRange {
|
||||||
|
range: piece_range + range.start(),
|
||||||
|
highlight: highlight.into(),
|
||||||
|
binding_hash: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element");
|
stack.flattened()
|
||||||
let mut res = res.pop().unwrap();
|
}
|
||||||
res.sort_by_key(|range| range.range.start());
|
|
||||||
// Check that ranges are sorted and disjoint
|
fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
|
||||||
assert!(res
|
Some(match kind {
|
||||||
.iter()
|
FormatSpecifier::Open
|
||||||
.zip(res.iter().skip(1))
|
| FormatSpecifier::Close
|
||||||
.all(|(left, right)| left.range.end() <= right.range.start()));
|
| FormatSpecifier::Colon
|
||||||
res
|
| FormatSpecifier::Fill
|
||||||
|
| FormatSpecifier::Align
|
||||||
|
| FormatSpecifier::Sign
|
||||||
|
| FormatSpecifier::NumberSign
|
||||||
|
| FormatSpecifier::DollarSign
|
||||||
|
| FormatSpecifier::Dot
|
||||||
|
| FormatSpecifier::Asterisk
|
||||||
|
| FormatSpecifier::QuestionMark => HighlightTag::Attribute,
|
||||||
|
FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral,
|
||||||
|
FormatSpecifier::Identifier => HighlightTag::Local,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
|
fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
|
||||||
|
@ -359,7 +471,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn highlight_injection(
|
fn highlight_injection(
|
||||||
acc: &mut Vec<HighlightedRange>,
|
acc: &mut HighlightedRangeStack,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
literal: ast::RawString,
|
literal: ast::RawString,
|
||||||
expanded: SyntaxToken,
|
expanded: SyntaxToken,
|
||||||
|
@ -372,7 +484,7 @@ fn highlight_injection(
|
||||||
let (analysis, tmp_file_id) = Analysis::from_single_file(value);
|
let (analysis, tmp_file_id) = Analysis::from_single_file(value);
|
||||||
|
|
||||||
if let Some(range) = literal.open_quote_text_range() {
|
if let Some(range) = literal.open_quote_text_range() {
|
||||||
acc.push(HighlightedRange {
|
acc.add(HighlightedRange {
|
||||||
range,
|
range,
|
||||||
highlight: HighlightTag::StringLiteral.into(),
|
highlight: HighlightTag::StringLiteral.into(),
|
||||||
binding_hash: None,
|
binding_hash: None,
|
||||||
|
@ -382,12 +494,12 @@ fn highlight_injection(
|
||||||
for mut h in analysis.highlight(tmp_file_id).unwrap() {
|
for mut h in analysis.highlight(tmp_file_id).unwrap() {
|
||||||
if let Some(r) = literal.map_range_up(h.range) {
|
if let Some(r) = literal.map_range_up(h.range) {
|
||||||
h.range = r;
|
h.range = r;
|
||||||
acc.push(h)
|
acc.add(h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(range) = literal.close_quote_text_range() {
|
if let Some(range) = literal.close_quote_text_range() {
|
||||||
acc.push(HighlightedRange {
|
acc.add(HighlightedRange {
|
||||||
range,
|
range,
|
||||||
highlight: HighlightTag::StringLiteral.into(),
|
highlight: HighlightTag::StringLiteral.into(),
|
||||||
binding_hash: None,
|
binding_hash: None,
|
||||||
|
|
|
@ -168,3 +168,73 @@ macro_rules! test {}
|
||||||
);
|
);
|
||||||
let _ = analysis.highlight(file_id).unwrap();
|
let _ = analysis.highlight(file_id).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_string_highlighting() {
|
||||||
|
// The format string detection is based on macro-expansion,
|
||||||
|
// thus, we have to copy the macro definition from `std`
|
||||||
|
let (analysis, file_id) = single_file(
|
||||||
|
r#"
|
||||||
|
macro_rules! println {
|
||||||
|
($($arg:tt)*) => ({
|
||||||
|
$crate::io::_print($crate::format_args_nl!($($arg)*));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[rustc_builtin_macro]
|
||||||
|
macro_rules! format_args_nl {
|
||||||
|
($fmt:expr) => {{ /* compiler built-in */ }};
|
||||||
|
($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// from https://doc.rust-lang.org/std/fmt/index.html
|
||||||
|
println!("Hello"); // => "Hello"
|
||||||
|
println!("Hello, {}!", "world"); // => "Hello, world!"
|
||||||
|
println!("The number is {}", 1); // => "The number is 1"
|
||||||
|
println!("{:?}", (3, 4)); // => "(3, 4)"
|
||||||
|
println!("{value}", value=4); // => "4"
|
||||||
|
println!("{} {}", 1, 2); // => "1 2"
|
||||||
|
println!("{:04}", 42); // => "0042" with leading zerosV
|
||||||
|
println!("{1} {} {0} {}", 1, 2); // => "2 1 1 2"
|
||||||
|
println!("{argument}", argument = "test"); // => "test"
|
||||||
|
println!("{name} {}", 1, name = 2); // => "2 1"
|
||||||
|
println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b"
|
||||||
|
println!("Hello {:5}!", "x");
|
||||||
|
println!("Hello {:1$}!", "x", 5);
|
||||||
|
println!("Hello {1:0$}!", 5, "x");
|
||||||
|
println!("Hello {:width$}!", "x", width = 5);
|
||||||
|
println!("Hello {:<5}!", "x");
|
||||||
|
println!("Hello {:-<5}!", "x");
|
||||||
|
println!("Hello {:^5}!", "x");
|
||||||
|
println!("Hello {:>5}!", "x");
|
||||||
|
println!("Hello {:+}!", 5);
|
||||||
|
println!("{:#x}!", 27);
|
||||||
|
println!("Hello {:05}!", 5);
|
||||||
|
println!("Hello {:05}!", -5);
|
||||||
|
println!("{:#010x}!", 27);
|
||||||
|
println!("Hello {0} is {1:.5}", "x", 0.01);
|
||||||
|
println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
|
||||||
|
println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
|
||||||
|
println!("Hello {} is {:.*}", "x", 5, 0.01);
|
||||||
|
println!("Hello {} is {2:.*}", "x", 5, 0.01);
|
||||||
|
println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
|
||||||
|
println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
|
||||||
|
println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
|
||||||
|
println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
|
||||||
|
println!("Hello {{}}");
|
||||||
|
println!("{{ Hello");
|
||||||
|
|
||||||
|
println!(r"Hello, {}!", "world");
|
||||||
|
|
||||||
|
println!("{\x41}", A = 92);
|
||||||
|
println!("{ничоси}", ничоси = 92);
|
||||||
|
}"#
|
||||||
|
.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html");
|
||||||
|
let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
|
||||||
|
let expected_html = &read_text(&dst_file);
|
||||||
|
fs::write(dst_file, &actual_html).unwrap();
|
||||||
|
assert_eq_text!(expected_html, actual_html);
|
||||||
|
}
|
||||||
|
|
|
@ -293,11 +293,20 @@ pub fn fn_def(
|
||||||
ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body))
|
ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
|
pub fn add_leading_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
|
||||||
let newlines = "\n".repeat(amount_of_newlines);
|
let newlines = "\n".repeat(amount_of_newlines);
|
||||||
ast_from_text(&format!("{}{}", newlines, t.syntax()))
|
ast_from_text(&format!("{}{}", newlines, t.syntax()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_trailing_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
|
||||||
|
let newlines = "\n".repeat(amount_of_newlines);
|
||||||
|
ast_from_text(&format!("{}{}", t.syntax(), newlines))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_pub_crate_modifier(fn_def: ast::FnDef) -> ast::FnDef {
|
||||||
|
ast_from_text(&format!("pub(crate) {}", fn_def))
|
||||||
|
}
|
||||||
|
|
||||||
fn ast_from_text<N: AstNode>(text: &str) -> N {
|
fn ast_from_text<N: AstNode>(text: &str) -> N {
|
||||||
let parse = SourceFile::parse(text);
|
let parse = SourceFile::parse(text);
|
||||||
let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap();
|
let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap();
|
||||||
|
|
|
@ -172,3 +172,362 @@ impl RawString {
|
||||||
Some(range + contents_range.start())
|
Some(range + contents_range.start())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FormatSpecifier {
|
||||||
|
Open,
|
||||||
|
Close,
|
||||||
|
Integer,
|
||||||
|
Identifier,
|
||||||
|
Colon,
|
||||||
|
Fill,
|
||||||
|
Align,
|
||||||
|
Sign,
|
||||||
|
NumberSign,
|
||||||
|
Zero,
|
||||||
|
DollarSign,
|
||||||
|
Dot,
|
||||||
|
Asterisk,
|
||||||
|
QuestionMark,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasFormatSpecifier: AstToken {
|
||||||
|
fn char_ranges(
|
||||||
|
&self,
|
||||||
|
) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>>;
|
||||||
|
|
||||||
|
fn lex_format_specifier<F>(&self, mut callback: F)
|
||||||
|
where
|
||||||
|
F: FnMut(TextRange, FormatSpecifier),
|
||||||
|
{
|
||||||
|
let char_ranges = if let Some(char_ranges) = self.char_ranges() {
|
||||||
|
char_ranges
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut chars = char_ranges.iter().peekable();
|
||||||
|
|
||||||
|
while let Some((range, first_char)) = chars.next() {
|
||||||
|
match first_char {
|
||||||
|
Ok('{') => {
|
||||||
|
// Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax
|
||||||
|
if let Some((_, Ok('{'))) = chars.peek() {
|
||||||
|
// Escaped format specifier, `{{`
|
||||||
|
chars.next();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(*range, FormatSpecifier::Open);
|
||||||
|
|
||||||
|
// check for integer/identifier
|
||||||
|
match chars
|
||||||
|
.peek()
|
||||||
|
.and_then(|next| next.1.as_ref().ok())
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
'0'..='9' => {
|
||||||
|
// integer
|
||||||
|
read_integer(&mut chars, &mut callback);
|
||||||
|
}
|
||||||
|
c if c == '_' || c.is_alphabetic() => {
|
||||||
|
// identifier
|
||||||
|
read_identifier(&mut chars, &mut callback);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((_, Ok(':'))) = chars.peek() {
|
||||||
|
skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback);
|
||||||
|
|
||||||
|
// check for fill/align
|
||||||
|
let mut cloned = chars.clone().take(2);
|
||||||
|
let first = cloned
|
||||||
|
.next()
|
||||||
|
.and_then(|next| next.1.as_ref().ok())
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let second = cloned
|
||||||
|
.next()
|
||||||
|
.and_then(|next| next.1.as_ref().ok())
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default();
|
||||||
|
match second {
|
||||||
|
'<' | '^' | '>' => {
|
||||||
|
// alignment specifier, first char specifies fillment
|
||||||
|
skip_char_and_emit(
|
||||||
|
&mut chars,
|
||||||
|
FormatSpecifier::Fill,
|
||||||
|
&mut callback,
|
||||||
|
);
|
||||||
|
skip_char_and_emit(
|
||||||
|
&mut chars,
|
||||||
|
FormatSpecifier::Align,
|
||||||
|
&mut callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => match first {
|
||||||
|
'<' | '^' | '>' => {
|
||||||
|
skip_char_and_emit(
|
||||||
|
&mut chars,
|
||||||
|
FormatSpecifier::Align,
|
||||||
|
&mut callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for sign
|
||||||
|
match chars
|
||||||
|
.peek()
|
||||||
|
.and_then(|next| next.1.as_ref().ok())
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
'+' | '-' => {
|
||||||
|
skip_char_and_emit(
|
||||||
|
&mut chars,
|
||||||
|
FormatSpecifier::Sign,
|
||||||
|
&mut callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for `#`
|
||||||
|
if let Some((_, Ok('#'))) = chars.peek() {
|
||||||
|
skip_char_and_emit(
|
||||||
|
&mut chars,
|
||||||
|
FormatSpecifier::NumberSign,
|
||||||
|
&mut callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for `0`
|
||||||
|
let mut cloned = chars.clone().take(2);
|
||||||
|
let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
|
||||||
|
let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
|
||||||
|
|
||||||
|
if first == Some('0') && second != Some('$') {
|
||||||
|
skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// width
|
||||||
|
match chars
|
||||||
|
.peek()
|
||||||
|
.and_then(|next| next.1.as_ref().ok())
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
'0'..='9' => {
|
||||||
|
read_integer(&mut chars, &mut callback);
|
||||||
|
if let Some((_, Ok('$'))) = chars.peek() {
|
||||||
|
skip_char_and_emit(
|
||||||
|
&mut chars,
|
||||||
|
FormatSpecifier::DollarSign,
|
||||||
|
&mut callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c if c == '_' || c.is_alphabetic() => {
|
||||||
|
read_identifier(&mut chars, &mut callback);
|
||||||
|
if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
|
||||||
|
!= Some('$')
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
skip_char_and_emit(
|
||||||
|
&mut chars,
|
||||||
|
FormatSpecifier::DollarSign,
|
||||||
|
&mut callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// precision
|
||||||
|
if let Some((_, Ok('.'))) = chars.peek() {
|
||||||
|
skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback);
|
||||||
|
|
||||||
|
match chars
|
||||||
|
.peek()
|
||||||
|
.and_then(|next| next.1.as_ref().ok())
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
'*' => {
|
||||||
|
skip_char_and_emit(
|
||||||
|
&mut chars,
|
||||||
|
FormatSpecifier::Asterisk,
|
||||||
|
&mut callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
'0'..='9' => {
|
||||||
|
read_integer(&mut chars, &mut callback);
|
||||||
|
if let Some((_, Ok('$'))) = chars.peek() {
|
||||||
|
skip_char_and_emit(
|
||||||
|
&mut chars,
|
||||||
|
FormatSpecifier::DollarSign,
|
||||||
|
&mut callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c if c == '_' || c.is_alphabetic() => {
|
||||||
|
read_identifier(&mut chars, &mut callback);
|
||||||
|
if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
|
||||||
|
!= Some('$')
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
skip_char_and_emit(
|
||||||
|
&mut chars,
|
||||||
|
FormatSpecifier::DollarSign,
|
||||||
|
&mut callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// type
|
||||||
|
match chars
|
||||||
|
.peek()
|
||||||
|
.and_then(|next| next.1.as_ref().ok())
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
'?' => {
|
||||||
|
skip_char_and_emit(
|
||||||
|
&mut chars,
|
||||||
|
FormatSpecifier::QuestionMark,
|
||||||
|
&mut callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
c if c == '_' || c.is_alphabetic() => {
|
||||||
|
read_identifier(&mut chars, &mut callback);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cloned = chars.clone().take(2);
|
||||||
|
let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
|
||||||
|
let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
|
||||||
|
if first != Some('}') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if second == Some('}') {
|
||||||
|
// Escaped format end specifier, `}}`
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
while let Some((_, Ok(next_char))) = chars.peek() {
|
||||||
|
match next_char {
|
||||||
|
'{' => break,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
chars.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_char_and_emit<'a, I, F>(
|
||||||
|
chars: &mut std::iter::Peekable<I>,
|
||||||
|
emit: FormatSpecifier,
|
||||||
|
callback: &mut F,
|
||||||
|
) where
|
||||||
|
I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
|
||||||
|
F: FnMut(TextRange, FormatSpecifier),
|
||||||
|
{
|
||||||
|
let (range, _) = chars.next().unwrap();
|
||||||
|
callback(*range, emit);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_integer<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
|
||||||
|
F: FnMut(TextRange, FormatSpecifier),
|
||||||
|
{
|
||||||
|
let (mut range, c) = chars.next().unwrap();
|
||||||
|
assert!(c.as_ref().unwrap().is_ascii_digit());
|
||||||
|
while let Some((r, Ok(next_char))) = chars.peek() {
|
||||||
|
if next_char.is_ascii_digit() {
|
||||||
|
chars.next();
|
||||||
|
range = range.extend_to(r);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(range, FormatSpecifier::Integer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_identifier<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
|
||||||
|
F: FnMut(TextRange, FormatSpecifier),
|
||||||
|
{
|
||||||
|
let (mut range, c) = chars.next().unwrap();
|
||||||
|
assert!(c.as_ref().unwrap().is_alphabetic() || *c.as_ref().unwrap() == '_');
|
||||||
|
while let Some((r, Ok(next_char))) = chars.peek() {
|
||||||
|
if *next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() {
|
||||||
|
chars.next();
|
||||||
|
range = range.extend_to(r);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(range, FormatSpecifier::Identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasFormatSpecifier for String {
|
||||||
|
fn char_ranges(
|
||||||
|
&self,
|
||||||
|
) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
|
||||||
|
let text = self.text().as_str();
|
||||||
|
let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
|
||||||
|
let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
|
||||||
|
|
||||||
|
let mut res = Vec::with_capacity(text.len());
|
||||||
|
rustc_lexer::unescape::unescape_str(text, &mut |range, unescaped_char| {
|
||||||
|
res.push((
|
||||||
|
TextRange::from_to(
|
||||||
|
TextUnit::from_usize(range.start),
|
||||||
|
TextUnit::from_usize(range.end),
|
||||||
|
) + offset,
|
||||||
|
unescaped_char,
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasFormatSpecifier for RawString {
|
||||||
|
fn char_ranges(
|
||||||
|
&self,
|
||||||
|
) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
|
||||||
|
let text = self.text().as_str();
|
||||||
|
let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
|
||||||
|
let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
|
||||||
|
|
||||||
|
let mut res = Vec::with_capacity(text.len());
|
||||||
|
for (idx, c) in text.char_indices() {
|
||||||
|
res.push((
|
||||||
|
TextRange::from_to(
|
||||||
|
TextUnit::from_usize(idx),
|
||||||
|
TextUnit::from_usize(idx + c.len_utf8()),
|
||||||
|
) + offset,
|
||||||
|
Ok(c),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ Adds a stub function with a signature matching the function under the cursor.
|
||||||
struct Baz;
|
struct Baz;
|
||||||
fn baz() -> Baz { Baz }
|
fn baz() -> Baz { Baz }
|
||||||
fn foo() {
|
fn foo() {
|
||||||
bar┃("", baz());
|
bar┃("", baz());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ fn foo() {
|
||||||
struct Baz;
|
struct Baz;
|
||||||
fn baz() -> Baz { Baz }
|
fn baz() -> Baz { Baz }
|
||||||
fn foo() {
|
fn foo() {
|
||||||
bar("", baz());
|
bar("", baz());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bar(arg: &str, baz: Baz) {
|
fn bar(arg: &str, baz: Baz) {
|
||||||
|
|
|
@ -37,11 +37,13 @@ export async function applySourceChange(ctx: Ctx, change: ra.SourceChange) {
|
||||||
toReveal.position,
|
toReveal.position,
|
||||||
);
|
);
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
if (!editor || editor.document.uri.toString() !== uri.toString()) {
|
if (!editor || !editor.selection.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!editor.selection.isEmpty) {
|
|
||||||
return;
|
if (editor.document.uri !== uri) {
|
||||||
|
const doc = await vscode.workspace.openTextDocument(uri);
|
||||||
|
await vscode.window.showTextDocument(doc);
|
||||||
}
|
}
|
||||||
editor.selection = new vscode.Selection(position, position);
|
editor.selection = new vscode.Selection(position, position);
|
||||||
editor.revealRange(
|
editor.revealRange(
|
||||||
|
|
Loading…
Reference in a new issue