diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 8 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests/generated.rs | 4 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/add_function.rs | 236 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 25 | ||||
-rw-r--r-- | crates/ra_ide/src/assists.rs | 4 | ||||
-rw-r--r-- | crates/ra_ide/src/snapshots/highlight_strings.html | 82 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting.rs | 224 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/tests.rs | 70 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 11 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/tokens.rs | 359 |
10 files changed, 933 insertions, 90 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index c3e653299..279163257 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -10,7 +10,7 @@ use ra_syntax::{ | |||
10 | }; | 10 | }; |
11 | use ra_text_edit::TextEditBuilder; | 11 | use ra_text_edit::TextEditBuilder; |
12 | 12 | ||
13 | use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; | 13 | use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; |
14 | use algo::SyntaxRewriter; | 14 | use algo::SyntaxRewriter; |
15 | 15 | ||
16 | #[derive(Clone, Debug)] | 16 | #[derive(Clone, Debug)] |
@@ -180,6 +180,7 @@ pub(crate) struct ActionBuilder { | |||
180 | edit: TextEditBuilder, | 180 | edit: TextEditBuilder, |
181 | cursor_position: Option<TextUnit>, | 181 | cursor_position: Option<TextUnit>, |
182 | target: Option<TextRange>, | 182 | target: Option<TextRange>, |
183 | file: AssistFile, | ||
183 | } | 184 | } |
184 | 185 | ||
185 | impl ActionBuilder { | 186 | impl ActionBuilder { |
@@ -241,11 +242,16 @@ impl ActionBuilder { | |||
241 | algo::diff(&node, &new).into_text_edit(&mut self.edit) | 242 | algo::diff(&node, &new).into_text_edit(&mut self.edit) |
242 | } | 243 | } |
243 | 244 | ||
245 | pub(crate) fn set_file(&mut self, assist_file: AssistFile) { | ||
246 | self.file = assist_file | ||
247 | } | ||
248 | |||
244 | fn build(self) -> AssistAction { | 249 | fn build(self) -> AssistAction { |
245 | AssistAction { | 250 | AssistAction { |
246 | edit: self.edit.finish(), | 251 | edit: self.edit.finish(), |
247 | cursor_position: self.cursor_position, | 252 | cursor_position: self.cursor_position, |
248 | target: self.target, | 253 | target: self.target, |
254 | file: self.file, | ||
249 | } | 255 | } |
250 | } | 256 | } |
251 | } | 257 | } |
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs index b39e60870..e4fa9ee36 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/doc_tests/generated.rs | |||
@@ -66,7 +66,7 @@ fn doctest_add_function() { | |||
66 | struct Baz; | 66 | struct Baz; |
67 | fn baz() -> Baz { Baz } | 67 | fn baz() -> Baz { Baz } |
68 | fn foo() { | 68 | fn foo() { |
69 | bar<|>("", baz()); | 69 | bar<|>("", baz()); |
70 | } | 70 | } |
71 | 71 | ||
72 | "#####, | 72 | "#####, |
@@ -74,7 +74,7 @@ fn foo() { | |||
74 | struct Baz; | 74 | struct Baz; |
75 | fn baz() -> Baz { Baz } | 75 | fn baz() -> Baz { Baz } |
76 | fn foo() { | 76 | fn foo() { |
77 | bar("", baz()); | 77 | bar("", baz()); |
78 | } | 78 | } |
79 | 79 | ||
80 | fn bar(arg: &str, baz: Baz) { | 80 | fn bar(arg: &str, baz: Baz) { |
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index ad4ab66ed..f185cffdb 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs | |||
@@ -3,8 +3,8 @@ use ra_syntax::{ | |||
3 | SyntaxKind, SyntaxNode, TextUnit, | 3 | SyntaxKind, SyntaxNode, TextUnit, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{Assist, AssistCtx, AssistId}; | 6 | use crate::{Assist, AssistCtx, AssistFile, AssistId}; |
7 | use ast::{edit::IndentLevel, ArgListOwner, CallExpr, Expr}; | 7 | use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner}; |
8 | use hir::HirDisplay; | 8 | use hir::HirDisplay; |
9 | use rustc_hash::{FxHashMap, FxHashSet}; | 9 | use rustc_hash::{FxHashMap, FxHashSet}; |
10 | 10 | ||
@@ -16,7 +16,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; | |||
16 | // struct Baz; | 16 | // struct Baz; |
17 | // fn baz() -> Baz { Baz } | 17 | // fn baz() -> Baz { Baz } |
18 | // fn foo() { | 18 | // fn foo() { |
19 | // bar<|>("", baz()); | 19 | // bar<|>("", baz()); |
20 | // } | 20 | // } |
21 | // | 21 | // |
22 | // ``` | 22 | // ``` |
@@ -25,7 +25,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; | |||
25 | // struct Baz; | 25 | // struct Baz; |
26 | // fn baz() -> Baz { Baz } | 26 | // fn baz() -> Baz { Baz } |
27 | // fn foo() { | 27 | // fn foo() { |
28 | // bar("", baz()); | 28 | // bar("", baz()); |
29 | // } | 29 | // } |
30 | // | 30 | // |
31 | // fn bar(arg: &str, baz: Baz) { | 31 | // fn bar(arg: &str, baz: Baz) { |
@@ -38,21 +38,30 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> { | |||
38 | let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; | 38 | let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; |
39 | let path = path_expr.path()?; | 39 | let path = path_expr.path()?; |
40 | 40 | ||
41 | if path.qualifier().is_some() { | ||
42 | return None; | ||
43 | } | ||
44 | |||
45 | if ctx.sema.resolve_path(&path).is_some() { | 41 | if ctx.sema.resolve_path(&path).is_some() { |
46 | // The function call already resolves, no need to add a function | 42 | // The function call already resolves, no need to add a function |
47 | return None; | 43 | return None; |
48 | } | 44 | } |
49 | 45 | ||
50 | let function_builder = FunctionBuilder::from_call(&ctx, &call)?; | 46 | let target_module = if let Some(qualifier) = path.qualifier() { |
47 | if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) = | ||
48 | ctx.sema.resolve_path(&qualifier) | ||
49 | { | ||
50 | Some(module.definition_source(ctx.sema.db)) | ||
51 | } else { | ||
52 | return None; | ||
53 | } | ||
54 | } else { | ||
55 | None | ||
56 | }; | ||
57 | |||
58 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; | ||
51 | 59 | ||
52 | ctx.add_assist(AssistId("add_function"), "Add function", |edit| { | 60 | ctx.add_assist(AssistId("add_function"), "Add function", |edit| { |
53 | edit.target(call.syntax().text_range()); | 61 | edit.target(call.syntax().text_range()); |
54 | 62 | ||
55 | if let Some(function_template) = function_builder.render() { | 63 | if let Some(function_template) = function_builder.render() { |
64 | edit.set_file(function_template.file); | ||
56 | edit.set_cursor(function_template.cursor_offset); | 65 | edit.set_cursor(function_template.cursor_offset); |
57 | edit.insert(function_template.insert_offset, function_template.fn_def.to_string()); | 66 | edit.insert(function_template.insert_offset, function_template.fn_def.to_string()); |
58 | } | 67 | } |
@@ -63,29 +72,67 @@ struct FunctionTemplate { | |||
63 | insert_offset: TextUnit, | 72 | insert_offset: TextUnit, |
64 | cursor_offset: TextUnit, | 73 | cursor_offset: TextUnit, |
65 | fn_def: ast::SourceFile, | 74 | fn_def: ast::SourceFile, |
75 | file: AssistFile, | ||
66 | } | 76 | } |
67 | 77 | ||
68 | struct FunctionBuilder { | 78 | struct FunctionBuilder { |
69 | append_fn_at: SyntaxNode, | 79 | target: GeneratedFunctionTarget, |
70 | fn_name: ast::Name, | 80 | fn_name: ast::Name, |
71 | type_params: Option<ast::TypeParamList>, | 81 | type_params: Option<ast::TypeParamList>, |
72 | params: ast::ParamList, | 82 | params: ast::ParamList, |
83 | file: AssistFile, | ||
84 | needs_pub: bool, | ||
73 | } | 85 | } |
74 | 86 | ||
75 | impl FunctionBuilder { | 87 | impl FunctionBuilder { |
76 | fn from_call(ctx: &AssistCtx, call: &ast::CallExpr) -> Option<Self> { | 88 | /// Prepares a generated function that matches `call` in `generate_in` |
77 | let append_fn_at = next_space_for_fn(&call)?; | 89 | /// (or as close to `call` as possible, if `generate_in` is `None`) |
78 | let fn_name = fn_name(&call)?; | 90 | fn from_call( |
91 | ctx: &AssistCtx, | ||
92 | call: &ast::CallExpr, | ||
93 | path: &ast::Path, | ||
94 | target_module: Option<hir::InFile<hir::ModuleSource>>, | ||
95 | ) -> Option<Self> { | ||
96 | let needs_pub = target_module.is_some(); | ||
97 | let mut file = AssistFile::default(); | ||
98 | let target = if let Some(target_module) = target_module { | ||
99 | let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?; | ||
100 | file = in_file; | ||
101 | target | ||
102 | } else { | ||
103 | next_space_for_fn_after_call_site(&call)? | ||
104 | }; | ||
105 | let fn_name = fn_name(&path)?; | ||
79 | let (type_params, params) = fn_args(ctx, &call)?; | 106 | let (type_params, params) = fn_args(ctx, &call)?; |
80 | Some(Self { append_fn_at, fn_name, type_params, params }) | 107 | Some(Self { target, fn_name, type_params, params, file, needs_pub }) |
81 | } | 108 | } |
109 | |||
82 | fn render(self) -> Option<FunctionTemplate> { | 110 | fn render(self) -> Option<FunctionTemplate> { |
83 | let placeholder_expr = ast::make::expr_todo(); | 111 | let placeholder_expr = ast::make::expr_todo(); |
84 | let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); | 112 | let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); |
85 | let fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body); | 113 | let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body); |
86 | let fn_def = ast::make::add_newlines(2, fn_def); | 114 | if self.needs_pub { |
87 | let fn_def = IndentLevel::from_node(&self.append_fn_at).increase_indent(fn_def); | 115 | fn_def = ast::make::add_pub_crate_modifier(fn_def); |
88 | let insert_offset = self.append_fn_at.text_range().end(); | 116 | } |
117 | |||
118 | let (fn_def, insert_offset) = match self.target { | ||
119 | GeneratedFunctionTarget::BehindItem(it) => { | ||
120 | let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); | ||
121 | let indented = IndentLevel::from_node(&it).increase_indent(with_leading_blank_line); | ||
122 | (indented, it.text_range().end()) | ||
123 | } | ||
124 | GeneratedFunctionTarget::InEmptyItemList(it) => { | ||
125 | let indent_once = IndentLevel(1); | ||
126 | let indent = IndentLevel::from_node(it.syntax()); | ||
127 | |||
128 | let fn_def = ast::make::add_leading_newlines(1, fn_def); | ||
129 | let fn_def = indent_once.increase_indent(fn_def); | ||
130 | let fn_def = ast::make::add_trailing_newlines(1, fn_def); | ||
131 | let fn_def = indent.increase_indent(fn_def); | ||
132 | (fn_def, it.syntax().text_range().start() + TextUnit::from_usize(1)) | ||
133 | } | ||
134 | }; | ||
135 | |||
89 | let cursor_offset_from_fn_start = fn_def | 136 | let cursor_offset_from_fn_start = fn_def |
90 | .syntax() | 137 | .syntax() |
91 | .descendants() | 138 | .descendants() |
@@ -94,19 +141,24 @@ impl FunctionBuilder { | |||
94 | .text_range() | 141 | .text_range() |
95 | .start(); | 142 | .start(); |
96 | let cursor_offset = insert_offset + cursor_offset_from_fn_start; | 143 | let cursor_offset = insert_offset + cursor_offset_from_fn_start; |
97 | Some(FunctionTemplate { insert_offset, cursor_offset, fn_def }) | 144 | Some(FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file }) |
98 | } | 145 | } |
99 | } | 146 | } |
100 | 147 | ||
101 | fn fn_name(call: &CallExpr) -> Option<ast::Name> { | 148 | enum GeneratedFunctionTarget { |
102 | let name = call.expr()?.syntax().to_string(); | 149 | BehindItem(SyntaxNode), |
150 | InEmptyItemList(ast::ItemList), | ||
151 | } | ||
152 | |||
153 | fn fn_name(call: &ast::Path) -> Option<ast::Name> { | ||
154 | let name = call.segment()?.syntax().to_string(); | ||
103 | Some(ast::make::name(&name)) | 155 | Some(ast::make::name(&name)) |
104 | } | 156 | } |
105 | 157 | ||
106 | /// Computes the type variables and arguments required for the generated function | 158 | /// Computes the type variables and arguments required for the generated function |
107 | fn fn_args( | 159 | fn fn_args( |
108 | ctx: &AssistCtx, | 160 | ctx: &AssistCtx, |
109 | call: &CallExpr, | 161 | call: &ast::CallExpr, |
110 | ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { | 162 | ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { |
111 | let mut arg_names = Vec::new(); | 163 | let mut arg_names = Vec::new(); |
112 | let mut arg_types = Vec::new(); | 164 | let mut arg_types = Vec::new(); |
@@ -158,9 +210,9 @@ fn deduplicate_arg_names(arg_names: &mut Vec<String>) { | |||
158 | } | 210 | } |
159 | } | 211 | } |
160 | 212 | ||
161 | fn fn_arg_name(fn_arg: &Expr) -> Option<String> { | 213 | fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> { |
162 | match fn_arg { | 214 | match fn_arg { |
163 | Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?), | 215 | ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?), |
164 | _ => Some( | 216 | _ => Some( |
165 | fn_arg | 217 | fn_arg |
166 | .syntax() | 218 | .syntax() |
@@ -172,7 +224,7 @@ fn fn_arg_name(fn_arg: &Expr) -> Option<String> { | |||
172 | } | 224 | } |
173 | } | 225 | } |
174 | 226 | ||
175 | fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> { | 227 | fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> { |
176 | let ty = ctx.sema.type_of_expr(fn_arg)?; | 228 | let ty = ctx.sema.type_of_expr(fn_arg)?; |
177 | if ty.is_unknown() { | 229 | if ty.is_unknown() { |
178 | return None; | 230 | return None; |
@@ -184,7 +236,7 @@ fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> { | |||
184 | /// directly after the current block | 236 | /// directly after the current block |
185 | /// We want to write the generated function directly after | 237 | /// We want to write the generated function directly after |
186 | /// fns, impls or macro calls, but inside mods | 238 | /// fns, impls or macro calls, but inside mods |
187 | fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> { | 239 | fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFunctionTarget> { |
188 | let mut ancestors = expr.syntax().ancestors().peekable(); | 240 | let mut ancestors = expr.syntax().ancestors().peekable(); |
189 | let mut last_ancestor: Option<SyntaxNode> = None; | 241 | let mut last_ancestor: Option<SyntaxNode> = None; |
190 | while let Some(next_ancestor) = ancestors.next() { | 242 | while let Some(next_ancestor) = ancestors.next() { |
@@ -201,7 +253,32 @@ fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> { | |||
201 | } | 253 | } |
202 | last_ancestor = Some(next_ancestor); | 254 | last_ancestor = Some(next_ancestor); |
203 | } | 255 | } |
204 | last_ancestor | 256 | last_ancestor.map(GeneratedFunctionTarget::BehindItem) |
257 | } | ||
258 | |||
259 | fn next_space_for_fn_in_module( | ||
260 | db: &dyn hir::db::AstDatabase, | ||
261 | module: hir::InFile<hir::ModuleSource>, | ||
262 | ) -> Option<(AssistFile, GeneratedFunctionTarget)> { | ||
263 | let file = module.file_id.original_file(db); | ||
264 | let assist_file = AssistFile::TargetFile(file); | ||
265 | let assist_item = match module.value { | ||
266 | hir::ModuleSource::SourceFile(it) => { | ||
267 | if let Some(last_item) = it.items().last() { | ||
268 | GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) | ||
269 | } else { | ||
270 | GeneratedFunctionTarget::BehindItem(it.syntax().clone()) | ||
271 | } | ||
272 | } | ||
273 | hir::ModuleSource::Module(it) => { | ||
274 | if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) { | ||
275 | GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) | ||
276 | } else { | ||
277 | GeneratedFunctionTarget::InEmptyItemList(it.item_list()?) | ||
278 | } | ||
279 | } | ||
280 | }; | ||
281 | Some((assist_file, assist_item)) | ||
205 | } | 282 | } |
206 | 283 | ||
207 | #[cfg(test)] | 284 | #[cfg(test)] |
@@ -714,6 +791,111 @@ fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { | |||
714 | } | 791 | } |
715 | 792 | ||
716 | #[test] | 793 | #[test] |
794 | fn add_function_in_module() { | ||
795 | check_assist( | ||
796 | add_function, | ||
797 | r" | ||
798 | mod bar {} | ||
799 | |||
800 | fn foo() { | ||
801 | bar::my_fn<|>() | ||
802 | } | ||
803 | ", | ||
804 | r" | ||
805 | mod bar { | ||
806 | pub(crate) fn my_fn() { | ||
807 | <|>todo!() | ||
808 | } | ||
809 | } | ||
810 | |||
811 | fn foo() { | ||
812 | bar::my_fn() | ||
813 | } | ||
814 | ", | ||
815 | ) | ||
816 | } | ||
817 | |||
818 | #[test] | ||
819 | fn add_function_in_module_containing_other_items() { | ||
820 | check_assist( | ||
821 | add_function, | ||
822 | r" | ||
823 | mod bar { | ||
824 | fn something_else() {} | ||
825 | } | ||
826 | |||
827 | fn foo() { | ||
828 | bar::my_fn<|>() | ||
829 | } | ||
830 | ", | ||
831 | r" | ||
832 | mod bar { | ||
833 | fn something_else() {} | ||
834 | |||
835 | pub(crate) fn my_fn() { | ||
836 | <|>todo!() | ||
837 | } | ||
838 | } | ||
839 | |||
840 | fn foo() { | ||
841 | bar::my_fn() | ||
842 | } | ||
843 | ", | ||
844 | ) | ||
845 | } | ||
846 | |||
847 | #[test] | ||
848 | fn add_function_in_nested_module() { | ||
849 | check_assist( | ||
850 | add_function, | ||
851 | r" | ||
852 | mod bar { | ||
853 | mod baz {} | ||
854 | } | ||
855 | |||
856 | fn foo() { | ||
857 | bar::baz::my_fn<|>() | ||
858 | } | ||
859 | ", | ||
860 | r" | ||
861 | mod bar { | ||
862 | mod baz { | ||
863 | pub(crate) fn my_fn() { | ||
864 | <|>todo!() | ||
865 | } | ||
866 | } | ||
867 | } | ||
868 | |||
869 | fn foo() { | ||
870 | bar::baz::my_fn() | ||
871 | } | ||
872 | ", | ||
873 | ) | ||
874 | } | ||
875 | |||
876 | #[test] | ||
877 | fn add_function_in_another_file() { | ||
878 | check_assist( | ||
879 | add_function, | ||
880 | r" | ||
881 | //- /main.rs | ||
882 | mod foo; | ||
883 | |||
884 | fn main() { | ||
885 | foo::bar<|>() | ||
886 | } | ||
887 | //- /foo.rs | ||
888 | ", | ||
889 | r" | ||
890 | |||
891 | |||
892 | pub(crate) fn bar() { | ||
893 | <|>todo!() | ||
894 | }", | ||
895 | ) | ||
896 | } | ||
897 | |||
898 | #[test] | ||
717 | fn add_function_not_applicable_if_function_already_exists() { | 899 | fn add_function_not_applicable_if_function_already_exists() { |
718 | check_assist_not_applicable( | 900 | check_assist_not_applicable( |
719 | add_function, | 901 | add_function, |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index a00136da1..ccc95735f 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -17,7 +17,7 @@ mod doc_tests; | |||
17 | pub mod utils; | 17 | pub mod utils; |
18 | pub mod ast_transform; | 18 | pub mod ast_transform; |
19 | 19 | ||
20 | use ra_db::FileRange; | 20 | use ra_db::{FileId, FileRange}; |
21 | use ra_ide_db::RootDatabase; | 21 | use ra_ide_db::RootDatabase; |
22 | use ra_syntax::{TextRange, TextUnit}; | 22 | use ra_syntax::{TextRange, TextUnit}; |
23 | use ra_text_edit::TextEdit; | 23 | use ra_text_edit::TextEdit; |
@@ -54,6 +54,7 @@ pub struct AssistAction { | |||
54 | pub cursor_position: Option<TextUnit>, | 54 | pub cursor_position: Option<TextUnit>, |
55 | // FIXME: This belongs to `AssistLabel` | 55 | // FIXME: This belongs to `AssistLabel` |
56 | pub target: Option<TextRange>, | 56 | pub target: Option<TextRange>, |
57 | pub file: AssistFile, | ||
57 | } | 58 | } |
58 | 59 | ||
59 | #[derive(Debug, Clone)] | 60 | #[derive(Debug, Clone)] |
@@ -63,6 +64,18 @@ pub struct ResolvedAssist { | |||
63 | pub action: AssistAction, | 64 | pub action: AssistAction, |
64 | } | 65 | } |
65 | 66 | ||
67 | #[derive(Debug, Clone, Copy)] | ||
68 | pub enum AssistFile { | ||
69 | CurrentFile, | ||
70 | TargetFile(FileId), | ||
71 | } | ||
72 | |||
73 | impl Default for AssistFile { | ||
74 | fn default() -> Self { | ||
75 | Self::CurrentFile | ||
76 | } | ||
77 | } | ||
78 | |||
66 | /// Return all the assists applicable at the given position. | 79 | /// Return all the assists applicable at the given position. |
67 | /// | 80 | /// |
68 | /// Assists are returned in the "unresolved" state, that is only labels are | 81 | /// Assists are returned in the "unresolved" state, that is only labels are |
@@ -184,7 +197,7 @@ mod helpers { | |||
184 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; | 197 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; |
185 | use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset}; | 198 | use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset}; |
186 | 199 | ||
187 | use crate::{AssistCtx, AssistHandler}; | 200 | use crate::{AssistCtx, AssistFile, AssistHandler}; |
188 | use hir::Semantics; | 201 | use hir::Semantics; |
189 | 202 | ||
190 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { | 203 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { |
@@ -246,7 +259,13 @@ mod helpers { | |||
246 | (Some(assist), ExpectedResult::After(after)) => { | 259 | (Some(assist), ExpectedResult::After(after)) => { |
247 | let action = assist.0[0].action.clone().unwrap(); | 260 | let action = assist.0[0].action.clone().unwrap(); |
248 | 261 | ||
249 | let mut actual = action.edit.apply(&text_without_caret); | 262 | let assisted_file_text = if let AssistFile::TargetFile(file_id) = action.file { |
263 | db.file_text(file_id).as_ref().to_owned() | ||
264 | } else { | ||
265 | text_without_caret | ||
266 | }; | ||
267 | |||
268 | let mut actual = action.edit.apply(&assisted_file_text); | ||
250 | match action.cursor_position { | 269 | match action.cursor_position { |
251 | None => { | 270 | None => { |
252 | if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { | 271 | if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { |
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs index 40d56a4f7..2b5d11681 100644 --- a/crates/ra_ide/src/assists.rs +++ b/crates/ra_ide/src/assists.rs | |||
@@ -37,6 +37,10 @@ fn action_to_edit( | |||
37 | file_id: FileId, | 37 | file_id: FileId, |
38 | assist_label: &AssistLabel, | 38 | assist_label: &AssistLabel, |
39 | ) -> SourceChange { | 39 | ) -> SourceChange { |
40 | let file_id = match action.file { | ||
41 | ra_assists::AssistFile::TargetFile(it) => it, | ||
42 | _ => file_id, | ||
43 | }; | ||
40 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; | 44 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; |
41 | SourceChange::source_file_edit(assist_label.label.clone(), file_edit) | 45 | SourceChange::source_file_edit(assist_label.label.clone(), file_edit) |
42 | .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) | 46 | .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) |
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html new file mode 100644 index 000000000..433f2e0c5 --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_strings.html | |||
@@ -0,0 +1,82 @@ | |||
1 | |||
2 | <style> | ||
3 | body { margin: 0; } | ||
4 | pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; } | ||
5 | |||
6 | .lifetime { color: #DFAF8F; font-style: italic; } | ||
7 | .comment { color: #7F9F7F; } | ||
8 | .struct, .enum { color: #7CB8BB; } | ||
9 | .enum_variant { color: #BDE0F3; } | ||
10 | .string_literal { color: #CC9393; } | ||
11 | .field { color: #94BFF3; } | ||
12 | .function { color: #93E0E3; } | ||
13 | .parameter { color: #94BFF3; } | ||
14 | .text { color: #DCDCCC; } | ||
15 | .type { color: #7CB8BB; } | ||
16 | .builtin_type { color: #8CD0D3; } | ||
17 | .type_param { color: #DFAF8F; } | ||
18 | .attribute { color: #94BFF3; } | ||
19 | .numeric_literal { color: #BFEBBF; } | ||
20 | .macro { color: #94BFF3; } | ||
21 | .module { color: #AFD8AF; } | ||
22 | .variable { color: #DCDCCC; } | ||
23 | .mutable { text-decoration: underline; } | ||
24 | |||
25 | .keyword { color: #F0DFAF; font-weight: bold; } | ||
26 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | ||
27 | .control { font-style: italic; } | ||
28 | </style> | ||
29 | <pre><code><span class="macro">macro_rules!</span> println { | ||
30 | ($($arg:tt)*) => ({ | ||
31 | $<span class="keyword">crate</span>::io::_print($<span class="keyword">crate</span>::format_args_nl!($($arg)*)); | ||
32 | }) | ||
33 | } | ||
34 | #[rustc_builtin_macro] | ||
35 | <span class="macro">macro_rules!</span> format_args_nl { | ||
36 | ($fmt:expr) => {{ <span class="comment">/* compiler built-in */</span> }}; | ||
37 | ($fmt:expr, $($args:tt)*) => {{ <span class="comment">/* compiler built-in */</span> }}; | ||
38 | } | ||
39 | |||
40 | <span class="keyword">fn</span> <span class="function declaration">main</span>() { | ||
41 | <span class="comment">// from https://doc.rust-lang.org/std/fmt/index.html</span> | ||
42 | <span class="macro">println!</span>(<span class="string_literal">"Hello"</span>); <span class="comment">// => "Hello"</span> | ||
43 | <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> | ||
44 | <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> | ||
45 | <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> | ||
46 | <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> | ||
47 | <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> | ||
48 | <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> | ||
49 | <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> | ||
50 | <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> | ||
51 | <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> | ||
52 | <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> | ||
53 | <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>); | ||
54 | <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>); | ||
55 | <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>); | ||
56 | <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>); | ||
57 | <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>); | ||
58 | <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>); | ||
59 | <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>); | ||
60 | <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>); | ||
61 | <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>); | ||
62 | <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>); | ||
63 | <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>); | ||
64 | <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>); | ||
65 | <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>); | ||
66 | <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>); | ||
67 | <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>); | ||
68 | <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>); | ||
69 | <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>); | ||
70 | <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>); | ||
71 | <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>); | ||
72 | <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>); | ||
73 | <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>); | ||
74 | <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>); | ||
75 | <span class="macro">println!</span>(<span class="string_literal">"Hello {{}}"</span>); | ||
76 | <span class="macro">println!</span>(<span class="string_literal">"{{ Hello"</span>); | ||
77 | |||
78 | <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>); | ||
79 | |||
80 | <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>); | ||
81 | <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>); | ||
82 | }</code></pre> \ No newline at end of file | ||
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index d9912155b..c0728bfb1 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -12,7 +12,7 @@ use ra_ide_db::{ | |||
12 | }; | 12 | }; |
13 | use ra_prof::profile; | 13 | use ra_prof::profile; |
14 | use ra_syntax::{ | 14 | use ra_syntax::{ |
15 | ast::{self, HasQuotes, HasStringValue}, | 15 | ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue}, |
16 | AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, | 16 | AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, |
17 | SyntaxKind::*, | 17 | SyntaxKind::*, |
18 | SyntaxToken, TextRange, WalkEvent, T, | 18 | SyntaxToken, TextRange, WalkEvent, T, |
@@ -21,6 +21,7 @@ use rustc_hash::FxHashMap; | |||
21 | 21 | ||
22 | use crate::{call_info::ActiveParameter, Analysis, FileId}; | 22 | use crate::{call_info::ActiveParameter, Analysis, FileId}; |
23 | 23 | ||
24 | use ast::FormatSpecifier; | ||
24 | pub(crate) use html::highlight_as_html; | 25 | pub(crate) use html::highlight_as_html; |
25 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; | 26 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; |
26 | 27 | ||
@@ -31,6 +32,81 @@ pub struct HighlightedRange { | |||
31 | pub binding_hash: Option<u64>, | 32 | pub binding_hash: Option<u64>, |
32 | } | 33 | } |
33 | 34 | ||
35 | #[derive(Debug)] | ||
36 | struct HighlightedRangeStack { | ||
37 | stack: Vec<Vec<HighlightedRange>>, | ||
38 | } | ||
39 | |||
40 | /// We use a stack to implement the flattening logic for the highlighted | ||
41 | /// syntax ranges. | ||
42 | impl HighlightedRangeStack { | ||
43 | fn new() -> Self { | ||
44 | Self { stack: vec![Vec::new()] } | ||
45 | } | ||
46 | |||
47 | fn push(&mut self) { | ||
48 | self.stack.push(Vec::new()); | ||
49 | } | ||
50 | |||
51 | /// Flattens the highlighted ranges. | ||
52 | /// | ||
53 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
54 | /// 1) parent-range: Attribute [0, 23) | ||
55 | /// 2) child-range: String [16, 21) | ||
56 | /// | ||
57 | /// The following code implements the flattening, for our example this results to: | ||
58 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
59 | fn pop(&mut self) { | ||
60 | let children = self.stack.pop().unwrap(); | ||
61 | let prev = self.stack.last_mut().unwrap(); | ||
62 | let needs_flattening = !children.is_empty() | ||
63 | && !prev.is_empty() | ||
64 | && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range); | ||
65 | if !needs_flattening { | ||
66 | prev.extend(children); | ||
67 | } else { | ||
68 | let mut parent = prev.pop().unwrap(); | ||
69 | for ele in children { | ||
70 | assert!(ele.range.is_subrange(&parent.range)); | ||
71 | let mut cloned = parent.clone(); | ||
72 | parent.range = TextRange::from_to(parent.range.start(), ele.range.start()); | ||
73 | cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end()); | ||
74 | if !parent.range.is_empty() { | ||
75 | prev.push(parent); | ||
76 | } | ||
77 | prev.push(ele); | ||
78 | parent = cloned; | ||
79 | } | ||
80 | if !parent.range.is_empty() { | ||
81 | prev.push(parent); | ||
82 | } | ||
83 | } | ||
84 | } | ||
85 | |||
86 | fn add(&mut self, range: HighlightedRange) { | ||
87 | self.stack | ||
88 | .last_mut() | ||
89 | .expect("during DFS traversal, the stack must not be empty") | ||
90 | .push(range) | ||
91 | } | ||
92 | |||
93 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
94 | assert_eq!( | ||
95 | self.stack.len(), | ||
96 | 1, | ||
97 | "after DFS traversal, the stack should only contain a single element" | ||
98 | ); | ||
99 | let mut res = self.stack.pop().unwrap(); | ||
100 | res.sort_by_key(|range| range.range.start()); | ||
101 | // Check that ranges are sorted and disjoint | ||
102 | assert!(res | ||
103 | .iter() | ||
104 | .zip(res.iter().skip(1)) | ||
105 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
106 | res | ||
107 | } | ||
108 | } | ||
109 | |||
34 | pub(crate) fn highlight( | 110 | pub(crate) fn highlight( |
35 | db: &RootDatabase, | 111 | db: &RootDatabase, |
36 | file_id: FileId, | 112 | file_id: FileId, |
@@ -57,52 +133,18 @@ pub(crate) fn highlight( | |||
57 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); | 133 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); |
58 | // We use a stack for the DFS traversal below. | 134 | // We use a stack for the DFS traversal below. |
59 | // When we leave a node, the we use it to flatten the highlighted ranges. | 135 | // When we leave a node, the we use it to flatten the highlighted ranges. |
60 | let mut res: Vec<Vec<HighlightedRange>> = vec![Vec::new()]; | 136 | let mut stack = HighlightedRangeStack::new(); |
61 | 137 | ||
62 | let mut current_macro_call: Option<ast::MacroCall> = None; | 138 | let mut current_macro_call: Option<ast::MacroCall> = None; |
139 | let mut format_string: Option<SyntaxElement> = None; | ||
63 | 140 | ||
64 | // Walk all nodes, keeping track of whether we are inside a macro or not. | 141 | // Walk all nodes, keeping track of whether we are inside a macro or not. |
65 | // If in macro, expand it first and highlight the expanded code. | 142 | // If in macro, expand it first and highlight the expanded code. |
66 | for event in root.preorder_with_tokens() { | 143 | for event in root.preorder_with_tokens() { |
67 | match &event { | 144 | match &event { |
68 | WalkEvent::Enter(_) => res.push(Vec::new()), | 145 | WalkEvent::Enter(_) => stack.push(), |
69 | WalkEvent::Leave(_) => { | 146 | WalkEvent::Leave(_) => stack.pop(), |
70 | /* Flattens the highlighted ranges. | ||
71 | * | ||
72 | * For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
73 | * 1) parent-range: Attribute [0, 23) | ||
74 | * 2) child-range: String [16, 21) | ||
75 | * | ||
76 | * The following code implements the flattening, for our example this results to: | ||
77 | * `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
78 | */ | ||
79 | let children = res.pop().unwrap(); | ||
80 | let prev = res.last_mut().unwrap(); | ||
81 | let needs_flattening = !children.is_empty() | ||
82 | && !prev.is_empty() | ||
83 | && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range); | ||
84 | if !needs_flattening { | ||
85 | prev.extend(children); | ||
86 | } else { | ||
87 | let mut parent = prev.pop().unwrap(); | ||
88 | for ele in children { | ||
89 | assert!(ele.range.is_subrange(&parent.range)); | ||
90 | let mut cloned = parent.clone(); | ||
91 | parent.range = TextRange::from_to(parent.range.start(), ele.range.start()); | ||
92 | cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end()); | ||
93 | if !parent.range.is_empty() { | ||
94 | prev.push(parent); | ||
95 | } | ||
96 | prev.push(ele); | ||
97 | parent = cloned; | ||
98 | } | ||
99 | if !parent.range.is_empty() { | ||
100 | prev.push(parent); | ||
101 | } | ||
102 | } | ||
103 | } | ||
104 | }; | 147 | }; |
105 | let current = res.last_mut().expect("during DFS traversal, the stack must not be empty"); | ||
106 | 148 | ||
107 | let event_range = match &event { | 149 | let event_range = match &event { |
108 | WalkEvent::Enter(it) => it.text_range(), | 150 | WalkEvent::Enter(it) => it.text_range(), |
@@ -119,7 +161,7 @@ pub(crate) fn highlight( | |||
119 | WalkEvent::Enter(Some(mc)) => { | 161 | WalkEvent::Enter(Some(mc)) => { |
120 | current_macro_call = Some(mc.clone()); | 162 | current_macro_call = Some(mc.clone()); |
121 | if let Some(range) = macro_call_range(&mc) { | 163 | if let Some(range) = macro_call_range(&mc) { |
122 | current.push(HighlightedRange { | 164 | stack.add(HighlightedRange { |
123 | range, | 165 | range, |
124 | highlight: HighlightTag::Macro.into(), | 166 | highlight: HighlightTag::Macro.into(), |
125 | binding_hash: None, | 167 | binding_hash: None, |
@@ -130,6 +172,7 @@ pub(crate) fn highlight( | |||
130 | WalkEvent::Leave(Some(mc)) => { | 172 | WalkEvent::Leave(Some(mc)) => { |
131 | assert!(current_macro_call == Some(mc)); | 173 | assert!(current_macro_call == Some(mc)); |
132 | current_macro_call = None; | 174 | current_macro_call = None; |
175 | format_string = None; | ||
133 | continue; | 176 | continue; |
134 | } | 177 | } |
135 | _ => (), | 178 | _ => (), |
@@ -150,6 +193,30 @@ pub(crate) fn highlight( | |||
150 | }; | 193 | }; |
151 | let token = sema.descend_into_macros(token.clone()); | 194 | let token = sema.descend_into_macros(token.clone()); |
152 | let parent = token.parent(); | 195 | let parent = token.parent(); |
196 | |||
197 | // Check if macro takes a format string and remember it for highlighting later. | ||
198 | // The macros that accept a format string expand to a compiler builtin macros | ||
199 | // `format_args` and `format_args_nl`. | ||
200 | if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) { | ||
201 | if let Some(name) = | ||
202 | fmt_macro_call.path().and_then(|p| p.segment()).and_then(|s| s.name_ref()) | ||
203 | { | ||
204 | match name.text().as_str() { | ||
205 | "format_args" | "format_args_nl" => { | ||
206 | format_string = parent | ||
207 | .children_with_tokens() | ||
208 | .filter(|t| t.kind() != WHITESPACE) | ||
209 | .nth(1) | ||
210 | .filter(|e| { | ||
211 | ast::String::can_cast(e.kind()) | ||
212 | || ast::RawString::can_cast(e.kind()) | ||
213 | }) | ||
214 | } | ||
215 | _ => {} | ||
216 | } | ||
217 | } | ||
218 | } | ||
219 | |||
153 | // We only care Name and Name_ref | 220 | // We only care Name and Name_ref |
154 | match (token.kind(), parent.kind()) { | 221 | match (token.kind(), parent.kind()) { |
155 | (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), | 222 | (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), |
@@ -161,27 +228,72 @@ pub(crate) fn highlight( | |||
161 | 228 | ||
162 | if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { | 229 | if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { |
163 | let expanded = element_to_highlight.as_token().unwrap().clone(); | 230 | let expanded = element_to_highlight.as_token().unwrap().clone(); |
164 | if highlight_injection(current, &sema, token, expanded).is_some() { | 231 | if highlight_injection(&mut stack, &sema, token, expanded).is_some() { |
165 | continue; | 232 | continue; |
166 | } | 233 | } |
167 | } | 234 | } |
168 | 235 | ||
236 | let is_format_string = format_string.as_ref() == Some(&element_to_highlight); | ||
237 | |||
169 | if let Some((highlight, binding_hash)) = | 238 | if let Some((highlight, binding_hash)) = |
170 | highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight) | 239 | highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone()) |
171 | { | 240 | { |
172 | current.push(HighlightedRange { range, highlight, binding_hash }); | 241 | stack.add(HighlightedRange { range, highlight, binding_hash }); |
242 | if let Some(string) = | ||
243 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | ||
244 | { | ||
245 | stack.push(); | ||
246 | if is_format_string { | ||
247 | string.lex_format_specifier(|piece_range, kind| { | ||
248 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
249 | stack.add(HighlightedRange { | ||
250 | range: piece_range + range.start(), | ||
251 | highlight: highlight.into(), | ||
252 | binding_hash: None, | ||
253 | }); | ||
254 | } | ||
255 | }); | ||
256 | } | ||
257 | stack.pop(); | ||
258 | } else if let Some(string) = | ||
259 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) | ||
260 | { | ||
261 | stack.push(); | ||
262 | if is_format_string { | ||
263 | string.lex_format_specifier(|piece_range, kind| { | ||
264 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
265 | stack.add(HighlightedRange { | ||
266 | range: piece_range + range.start(), | ||
267 | highlight: highlight.into(), | ||
268 | binding_hash: None, | ||
269 | }); | ||
270 | } | ||
271 | }); | ||
272 | } | ||
273 | stack.pop(); | ||
274 | } | ||
173 | } | 275 | } |
174 | } | 276 | } |
175 | 277 | ||
176 | assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element"); | 278 | stack.flattened() |
177 | let mut res = res.pop().unwrap(); | 279 | } |
178 | res.sort_by_key(|range| range.range.start()); | 280 | |
179 | // Check that ranges are sorted and disjoint | 281 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { |
180 | assert!(res | 282 | Some(match kind { |
181 | .iter() | 283 | FormatSpecifier::Open |
182 | .zip(res.iter().skip(1)) | 284 | | FormatSpecifier::Close |
183 | .all(|(left, right)| left.range.end() <= right.range.start())); | 285 | | FormatSpecifier::Colon |
184 | res | 286 | | FormatSpecifier::Fill |
287 | | FormatSpecifier::Align | ||
288 | | FormatSpecifier::Sign | ||
289 | | FormatSpecifier::NumberSign | ||
290 | | FormatSpecifier::DollarSign | ||
291 | | FormatSpecifier::Dot | ||
292 | | FormatSpecifier::Asterisk | ||
293 | | FormatSpecifier::QuestionMark => HighlightTag::Attribute, | ||
294 | FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, | ||
295 | FormatSpecifier::Identifier => HighlightTag::Local, | ||
296 | }) | ||
185 | } | 297 | } |
186 | 298 | ||
187 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 299 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { |
@@ -359,7 +471,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | |||
359 | } | 471 | } |
360 | 472 | ||
361 | fn highlight_injection( | 473 | fn highlight_injection( |
362 | acc: &mut Vec<HighlightedRange>, | 474 | acc: &mut HighlightedRangeStack, |
363 | sema: &Semantics<RootDatabase>, | 475 | sema: &Semantics<RootDatabase>, |
364 | literal: ast::RawString, | 476 | literal: ast::RawString, |
365 | expanded: SyntaxToken, | 477 | expanded: SyntaxToken, |
@@ -372,7 +484,7 @@ fn highlight_injection( | |||
372 | let (analysis, tmp_file_id) = Analysis::from_single_file(value); | 484 | let (analysis, tmp_file_id) = Analysis::from_single_file(value); |
373 | 485 | ||
374 | if let Some(range) = literal.open_quote_text_range() { | 486 | if let Some(range) = literal.open_quote_text_range() { |
375 | acc.push(HighlightedRange { | 487 | acc.add(HighlightedRange { |
376 | range, | 488 | range, |
377 | highlight: HighlightTag::StringLiteral.into(), | 489 | highlight: HighlightTag::StringLiteral.into(), |
378 | binding_hash: None, | 490 | binding_hash: None, |
@@ -382,12 +494,12 @@ fn highlight_injection( | |||
382 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | 494 | for mut h in analysis.highlight(tmp_file_id).unwrap() { |
383 | if let Some(r) = literal.map_range_up(h.range) { | 495 | if let Some(r) = literal.map_range_up(h.range) { |
384 | h.range = r; | 496 | h.range = r; |
385 | acc.push(h) | 497 | acc.add(h) |
386 | } | 498 | } |
387 | } | 499 | } |
388 | 500 | ||
389 | if let Some(range) = literal.close_quote_text_range() { | 501 | if let Some(range) = literal.close_quote_text_range() { |
390 | acc.push(HighlightedRange { | 502 | acc.add(HighlightedRange { |
391 | range, | 503 | range, |
392 | highlight: HighlightTag::StringLiteral.into(), | 504 | highlight: HighlightTag::StringLiteral.into(), |
393 | binding_hash: None, | 505 | binding_hash: None, |
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index 73611e23a..a9aae957f 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -168,3 +168,73 @@ macro_rules! test {} | |||
168 | ); | 168 | ); |
169 | let _ = analysis.highlight(file_id).unwrap(); | 169 | let _ = analysis.highlight(file_id).unwrap(); |
170 | } | 170 | } |
171 | |||
172 | #[test] | ||
173 | fn test_string_highlighting() { | ||
174 | // The format string detection is based on macro-expansion, | ||
175 | // thus, we have to copy the macro definition from `std` | ||
176 | let (analysis, file_id) = single_file( | ||
177 | r#" | ||
178 | macro_rules! println { | ||
179 | ($($arg:tt)*) => ({ | ||
180 | $crate::io::_print($crate::format_args_nl!($($arg)*)); | ||
181 | }) | ||
182 | } | ||
183 | #[rustc_builtin_macro] | ||
184 | macro_rules! format_args_nl { | ||
185 | ($fmt:expr) => {{ /* compiler built-in */ }}; | ||
186 | ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }}; | ||
187 | } | ||
188 | |||
189 | fn main() { | ||
190 | // from https://doc.rust-lang.org/std/fmt/index.html | ||
191 | println!("Hello"); // => "Hello" | ||
192 | println!("Hello, {}!", "world"); // => "Hello, world!" | ||
193 | println!("The number is {}", 1); // => "The number is 1" | ||
194 | println!("{:?}", (3, 4)); // => "(3, 4)" | ||
195 | println!("{value}", value=4); // => "4" | ||
196 | println!("{} {}", 1, 2); // => "1 2" | ||
197 | println!("{:04}", 42); // => "0042" with leading zerosV | ||
198 | println!("{1} {} {0} {}", 1, 2); // => "2 1 1 2" | ||
199 | println!("{argument}", argument = "test"); // => "test" | ||
200 | println!("{name} {}", 1, name = 2); // => "2 1" | ||
201 | println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" | ||
202 | println!("Hello {:5}!", "x"); | ||
203 | println!("Hello {:1$}!", "x", 5); | ||
204 | println!("Hello {1:0$}!", 5, "x"); | ||
205 | println!("Hello {:width$}!", "x", width = 5); | ||
206 | println!("Hello {:<5}!", "x"); | ||
207 | println!("Hello {:-<5}!", "x"); | ||
208 | println!("Hello {:^5}!", "x"); | ||
209 | println!("Hello {:>5}!", "x"); | ||
210 | println!("Hello {:+}!", 5); | ||
211 | println!("{:#x}!", 27); | ||
212 | println!("Hello {:05}!", 5); | ||
213 | println!("Hello {:05}!", -5); | ||
214 | println!("{:#010x}!", 27); | ||
215 | println!("Hello {0} is {1:.5}", "x", 0.01); | ||
216 | println!("Hello {1} is {2:.0$}", 5, "x", 0.01); | ||
217 | println!("Hello {0} is {2:.1$}", "x", 5, 0.01); | ||
218 | println!("Hello {} is {:.*}", "x", 5, 0.01); | ||
219 | println!("Hello {} is {2:.*}", "x", 5, 0.01); | ||
220 | println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01); | ||
221 | println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56); | ||
222 | println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56"); | ||
223 | println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56"); | ||
224 | println!("Hello {{}}"); | ||
225 | println!("{{ Hello"); | ||
226 | |||
227 | println!(r"Hello, {}!", "world"); | ||
228 | |||
229 | println!("{\x41}", A = 92); | ||
230 | println!("{ничоси}", ничоси = 92); | ||
231 | }"# | ||
232 | .trim(), | ||
233 | ); | ||
234 | |||
235 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html"); | ||
236 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | ||
237 | let expected_html = &read_text(&dst_file); | ||
238 | fs::write(dst_file, &actual_html).unwrap(); | ||
239 | assert_eq_text!(expected_html, actual_html); | ||
240 | } | ||
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 0f4a50be4..ee0f5cc40 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -293,11 +293,20 @@ pub fn fn_def( | |||
293 | ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body)) | 293 | ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body)) |
294 | } | 294 | } |
295 | 295 | ||
296 | pub fn add_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { | 296 | pub fn add_leading_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { |
297 | let newlines = "\n".repeat(amount_of_newlines); | 297 | let newlines = "\n".repeat(amount_of_newlines); |
298 | ast_from_text(&format!("{}{}", newlines, t.syntax())) | 298 | ast_from_text(&format!("{}{}", newlines, t.syntax())) |
299 | } | 299 | } |
300 | 300 | ||
301 | pub fn add_trailing_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { | ||
302 | let newlines = "\n".repeat(amount_of_newlines); | ||
303 | ast_from_text(&format!("{}{}", t.syntax(), newlines)) | ||
304 | } | ||
305 | |||
306 | pub fn add_pub_crate_modifier(fn_def: ast::FnDef) -> ast::FnDef { | ||
307 | ast_from_text(&format!("pub(crate) {}", fn_def)) | ||
308 | } | ||
309 | |||
301 | fn ast_from_text<N: AstNode>(text: &str) -> N { | 310 | fn ast_from_text<N: AstNode>(text: &str) -> N { |
302 | let parse = SourceFile::parse(text); | 311 | let parse = SourceFile::parse(text); |
303 | let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); | 312 | let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); |
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs index e8320b57e..aa34b682d 100644 --- a/crates/ra_syntax/src/ast/tokens.rs +++ b/crates/ra_syntax/src/ast/tokens.rs | |||
@@ -172,3 +172,362 @@ impl RawString { | |||
172 | Some(range + contents_range.start()) | 172 | Some(range + contents_range.start()) |
173 | } | 173 | } |
174 | } | 174 | } |
175 | |||
176 | #[derive(Debug)] | ||
177 | pub enum FormatSpecifier { | ||
178 | Open, | ||
179 | Close, | ||
180 | Integer, | ||
181 | Identifier, | ||
182 | Colon, | ||
183 | Fill, | ||
184 | Align, | ||
185 | Sign, | ||
186 | NumberSign, | ||
187 | Zero, | ||
188 | DollarSign, | ||
189 | Dot, | ||
190 | Asterisk, | ||
191 | QuestionMark, | ||
192 | } | ||
193 | |||
194 | pub trait HasFormatSpecifier: AstToken { | ||
195 | fn char_ranges( | ||
196 | &self, | ||
197 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>>; | ||
198 | |||
199 | fn lex_format_specifier<F>(&self, mut callback: F) | ||
200 | where | ||
201 | F: FnMut(TextRange, FormatSpecifier), | ||
202 | { | ||
203 | let char_ranges = if let Some(char_ranges) = self.char_ranges() { | ||
204 | char_ranges | ||
205 | } else { | ||
206 | return; | ||
207 | }; | ||
208 | let mut chars = char_ranges.iter().peekable(); | ||
209 | |||
210 | while let Some((range, first_char)) = chars.next() { | ||
211 | match first_char { | ||
212 | Ok('{') => { | ||
213 | // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax | ||
214 | if let Some((_, Ok('{'))) = chars.peek() { | ||
215 | // Escaped format specifier, `{{` | ||
216 | chars.next(); | ||
217 | continue; | ||
218 | } | ||
219 | |||
220 | callback(*range, FormatSpecifier::Open); | ||
221 | |||
222 | // check for integer/identifier | ||
223 | match chars | ||
224 | .peek() | ||
225 | .and_then(|next| next.1.as_ref().ok()) | ||
226 | .copied() | ||
227 | .unwrap_or_default() | ||
228 | { | ||
229 | '0'..='9' => { | ||
230 | // integer | ||
231 | read_integer(&mut chars, &mut callback); | ||
232 | } | ||
233 | c if c == '_' || c.is_alphabetic() => { | ||
234 | // identifier | ||
235 | read_identifier(&mut chars, &mut callback); | ||
236 | } | ||
237 | _ => {} | ||
238 | } | ||
239 | |||
240 | if let Some((_, Ok(':'))) = chars.peek() { | ||
241 | skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback); | ||
242 | |||
243 | // check for fill/align | ||
244 | let mut cloned = chars.clone().take(2); | ||
245 | let first = cloned | ||
246 | .next() | ||
247 | .and_then(|next| next.1.as_ref().ok()) | ||
248 | .copied() | ||
249 | .unwrap_or_default(); | ||
250 | let second = cloned | ||
251 | .next() | ||
252 | .and_then(|next| next.1.as_ref().ok()) | ||
253 | .copied() | ||
254 | .unwrap_or_default(); | ||
255 | match second { | ||
256 | '<' | '^' | '>' => { | ||
257 | // alignment specifier, first char specifies fillment | ||
258 | skip_char_and_emit( | ||
259 | &mut chars, | ||
260 | FormatSpecifier::Fill, | ||
261 | &mut callback, | ||
262 | ); | ||
263 | skip_char_and_emit( | ||
264 | &mut chars, | ||
265 | FormatSpecifier::Align, | ||
266 | &mut callback, | ||
267 | ); | ||
268 | } | ||
269 | _ => match first { | ||
270 | '<' | '^' | '>' => { | ||
271 | skip_char_and_emit( | ||
272 | &mut chars, | ||
273 | FormatSpecifier::Align, | ||
274 | &mut callback, | ||
275 | ); | ||
276 | } | ||
277 | _ => {} | ||
278 | }, | ||
279 | } | ||
280 | |||
281 | // check for sign | ||
282 | match chars | ||
283 | .peek() | ||
284 | .and_then(|next| next.1.as_ref().ok()) | ||
285 | .copied() | ||
286 | .unwrap_or_default() | ||
287 | { | ||
288 | '+' | '-' => { | ||
289 | skip_char_and_emit( | ||
290 | &mut chars, | ||
291 | FormatSpecifier::Sign, | ||
292 | &mut callback, | ||
293 | ); | ||
294 | } | ||
295 | _ => {} | ||
296 | } | ||
297 | |||
298 | // check for `#` | ||
299 | if let Some((_, Ok('#'))) = chars.peek() { | ||
300 | skip_char_and_emit( | ||
301 | &mut chars, | ||
302 | FormatSpecifier::NumberSign, | ||
303 | &mut callback, | ||
304 | ); | ||
305 | } | ||
306 | |||
307 | // check for `0` | ||
308 | let mut cloned = chars.clone().take(2); | ||
309 | let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
310 | let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
311 | |||
312 | if first == Some('0') && second != Some('$') { | ||
313 | skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback); | ||
314 | } | ||
315 | |||
316 | // width | ||
317 | match chars | ||
318 | .peek() | ||
319 | .and_then(|next| next.1.as_ref().ok()) | ||
320 | .copied() | ||
321 | .unwrap_or_default() | ||
322 | { | ||
323 | '0'..='9' => { | ||
324 | read_integer(&mut chars, &mut callback); | ||
325 | if let Some((_, Ok('$'))) = chars.peek() { | ||
326 | skip_char_and_emit( | ||
327 | &mut chars, | ||
328 | FormatSpecifier::DollarSign, | ||
329 | &mut callback, | ||
330 | ); | ||
331 | } | ||
332 | } | ||
333 | c if c == '_' || c.is_alphabetic() => { | ||
334 | read_identifier(&mut chars, &mut callback); | ||
335 | if chars.peek().and_then(|next| next.1.as_ref().ok()).copied() | ||
336 | != Some('$') | ||
337 | { | ||
338 | continue; | ||
339 | } | ||
340 | skip_char_and_emit( | ||
341 | &mut chars, | ||
342 | FormatSpecifier::DollarSign, | ||
343 | &mut callback, | ||
344 | ); | ||
345 | } | ||
346 | _ => {} | ||
347 | } | ||
348 | |||
349 | // precision | ||
350 | if let Some((_, Ok('.'))) = chars.peek() { | ||
351 | skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback); | ||
352 | |||
353 | match chars | ||
354 | .peek() | ||
355 | .and_then(|next| next.1.as_ref().ok()) | ||
356 | .copied() | ||
357 | .unwrap_or_default() | ||
358 | { | ||
359 | '*' => { | ||
360 | skip_char_and_emit( | ||
361 | &mut chars, | ||
362 | FormatSpecifier::Asterisk, | ||
363 | &mut callback, | ||
364 | ); | ||
365 | } | ||
366 | '0'..='9' => { | ||
367 | read_integer(&mut chars, &mut callback); | ||
368 | if let Some((_, Ok('$'))) = chars.peek() { | ||
369 | skip_char_and_emit( | ||
370 | &mut chars, | ||
371 | FormatSpecifier::DollarSign, | ||
372 | &mut callback, | ||
373 | ); | ||
374 | } | ||
375 | } | ||
376 | c if c == '_' || c.is_alphabetic() => { | ||
377 | read_identifier(&mut chars, &mut callback); | ||
378 | if chars.peek().and_then(|next| next.1.as_ref().ok()).copied() | ||
379 | != Some('$') | ||
380 | { | ||
381 | continue; | ||
382 | } | ||
383 | skip_char_and_emit( | ||
384 | &mut chars, | ||
385 | FormatSpecifier::DollarSign, | ||
386 | &mut callback, | ||
387 | ); | ||
388 | } | ||
389 | _ => { | ||
390 | continue; | ||
391 | } | ||
392 | } | ||
393 | } | ||
394 | |||
395 | // type | ||
396 | match chars | ||
397 | .peek() | ||
398 | .and_then(|next| next.1.as_ref().ok()) | ||
399 | .copied() | ||
400 | .unwrap_or_default() | ||
401 | { | ||
402 | '?' => { | ||
403 | skip_char_and_emit( | ||
404 | &mut chars, | ||
405 | FormatSpecifier::QuestionMark, | ||
406 | &mut callback, | ||
407 | ); | ||
408 | } | ||
409 | c if c == '_' || c.is_alphabetic() => { | ||
410 | read_identifier(&mut chars, &mut callback); | ||
411 | } | ||
412 | _ => {} | ||
413 | } | ||
414 | } | ||
415 | |||
416 | let mut cloned = chars.clone().take(2); | ||
417 | let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
418 | let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
419 | if first != Some('}') { | ||
420 | continue; | ||
421 | } | ||
422 | if second == Some('}') { | ||
423 | // Escaped format end specifier, `}}` | ||
424 | continue; | ||
425 | } | ||
426 | skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback); | ||
427 | } | ||
428 | _ => { | ||
429 | while let Some((_, Ok(next_char))) = chars.peek() { | ||
430 | match next_char { | ||
431 | '{' => break, | ||
432 | _ => {} | ||
433 | } | ||
434 | chars.next(); | ||
435 | } | ||
436 | } | ||
437 | }; | ||
438 | } | ||
439 | |||
440 | fn skip_char_and_emit<'a, I, F>( | ||
441 | chars: &mut std::iter::Peekable<I>, | ||
442 | emit: FormatSpecifier, | ||
443 | callback: &mut F, | ||
444 | ) where | ||
445 | I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>, | ||
446 | F: FnMut(TextRange, FormatSpecifier), | ||
447 | { | ||
448 | let (range, _) = chars.next().unwrap(); | ||
449 | callback(*range, emit); | ||
450 | } | ||
451 | |||
452 | fn read_integer<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F) | ||
453 | where | ||
454 | I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>, | ||
455 | F: FnMut(TextRange, FormatSpecifier), | ||
456 | { | ||
457 | let (mut range, c) = chars.next().unwrap(); | ||
458 | assert!(c.as_ref().unwrap().is_ascii_digit()); | ||
459 | while let Some((r, Ok(next_char))) = chars.peek() { | ||
460 | if next_char.is_ascii_digit() { | ||
461 | chars.next(); | ||
462 | range = range.extend_to(r); | ||
463 | } else { | ||
464 | break; | ||
465 | } | ||
466 | } | ||
467 | callback(range, FormatSpecifier::Integer); | ||
468 | } | ||
469 | |||
470 | fn read_identifier<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F) | ||
471 | where | ||
472 | I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>, | ||
473 | F: FnMut(TextRange, FormatSpecifier), | ||
474 | { | ||
475 | let (mut range, c) = chars.next().unwrap(); | ||
476 | assert!(c.as_ref().unwrap().is_alphabetic() || *c.as_ref().unwrap() == '_'); | ||
477 | while let Some((r, Ok(next_char))) = chars.peek() { | ||
478 | if *next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() { | ||
479 | chars.next(); | ||
480 | range = range.extend_to(r); | ||
481 | } else { | ||
482 | break; | ||
483 | } | ||
484 | } | ||
485 | callback(range, FormatSpecifier::Identifier); | ||
486 | } | ||
487 | } | ||
488 | } | ||
489 | |||
490 | impl HasFormatSpecifier for String { | ||
491 | fn char_ranges( | ||
492 | &self, | ||
493 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> { | ||
494 | let text = self.text().as_str(); | ||
495 | let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; | ||
496 | let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); | ||
497 | |||
498 | let mut res = Vec::with_capacity(text.len()); | ||
499 | rustc_lexer::unescape::unescape_str(text, &mut |range, unescaped_char| { | ||
500 | res.push(( | ||
501 | TextRange::from_to( | ||
502 | TextUnit::from_usize(range.start), | ||
503 | TextUnit::from_usize(range.end), | ||
504 | ) + offset, | ||
505 | unescaped_char, | ||
506 | )) | ||
507 | }); | ||
508 | |||
509 | Some(res) | ||
510 | } | ||
511 | } | ||
512 | |||
513 | impl HasFormatSpecifier for RawString { | ||
514 | fn char_ranges( | ||
515 | &self, | ||
516 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> { | ||
517 | let text = self.text().as_str(); | ||
518 | let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; | ||
519 | let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); | ||
520 | |||
521 | let mut res = Vec::with_capacity(text.len()); | ||
522 | for (idx, c) in text.char_indices() { | ||
523 | res.push(( | ||
524 | TextRange::from_to( | ||
525 | TextUnit::from_usize(idx), | ||
526 | TextUnit::from_usize(idx + c.len_utf8()), | ||
527 | ) + offset, | ||
528 | Ok(c), | ||
529 | )); | ||
530 | } | ||
531 | Some(res) | ||
532 | } | ||
533 | } | ||