aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/expect/Cargo.toml14
-rw-r--r--crates/expect/src/lib.rs356
-rw-r--r--crates/flycheck/Cargo.toml (renamed from crates/ra_flycheck/Cargo.toml)3
-rw-r--r--crates/flycheck/src/lib.rs317
-rw-r--r--crates/paths/Cargo.toml9
-rw-r--r--crates/paths/src/lib.rs217
-rw-r--r--crates/ra_arena/Cargo.toml1
-rw-r--r--crates/ra_arena/src/lib.rs3
-rw-r--r--crates/ra_assists/Cargo.toml1
-rw-r--r--crates/ra_assists/src/assist_config.rs5
-rw-r--r--crates/ra_assists/src/assist_context.rs87
-rw-r--r--crates/ra_assists/src/ast_transform.rs18
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs4
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs10
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs98
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs35
-rw-r--r--crates/ra_assists/src/handlers/add_turbo_fish.rs13
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs17
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs279
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs37
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs48
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs156
-rw-r--r--crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs321
-rw-r--r--crates/ra_assists/src/handlers/extract_variable.rs (renamed from crates/ra_assists/src/handlers/introduce_variable.rs)270
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs257
-rw-r--r--crates/ra_assists/src/handlers/fix_visibility.rs208
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs21
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs15
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs15
-rw-r--r--crates/ra_assists/src/handlers/generate_derive.rs (renamed from crates/ra_assists/src/handlers/add_derive.rs)63
-rw-r--r--crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs (renamed from crates/ra_assists/src/handlers/add_from_impl_for_enum.rs)24
-rw-r--r--crates/ra_assists/src/handlers/generate_function.rs (renamed from crates/ra_assists/src/handlers/add_function.rs)91
-rw-r--r--crates/ra_assists/src/handlers/generate_impl.rs109
-rw-r--r--crates/ra_assists/src/handlers/generate_new.rs (renamed from crates/ra_assists/src/handlers/add_new.rs)46
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs24
-rw-r--r--crates/ra_assists/src/handlers/introduce_named_lifetime.rs (renamed from crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs)85
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs4
-rw-r--r--crates/ra_assists/src/handlers/merge_imports.rs25
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs50
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs55
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs29
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs135
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs4
-rw-r--r--crates/ra_assists/src/handlers/remove_mut.rs13
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs27
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs81
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs41
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs282
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs60
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs14
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs168
-rw-r--r--crates/ra_assists/src/lib.rs62
-rw-r--r--crates/ra_assists/src/tests.rs88
-rw-r--r--crates/ra_assists/src/tests/generated.rs297
-rw-r--r--crates/ra_assists/src/utils.rs16
-rw-r--r--crates/ra_assists/src/utils/insert_use.rs24
-rw-r--r--crates/ra_cfg/Cargo.toml1
-rw-r--r--crates/ra_cfg/src/lib.rs10
-rw-r--r--crates/ra_db/Cargo.toml6
-rw-r--r--crates/ra_db/src/fixture.rs188
-rw-r--r--crates/ra_db/src/input.rs161
-rw-r--r--crates/ra_db/src/lib.rs73
-rw-r--r--crates/ra_flycheck/src/lib.rs302
-rw-r--r--crates/ra_fmt/Cargo.toml1
-rw-r--r--crates/ra_hir/Cargo.toml2
-rw-r--r--crates/ra_hir/src/code_model.rs231
-rw-r--r--crates/ra_hir/src/db.rs18
-rw-r--r--crates/ra_hir/src/diagnostics.rs4
-rw-r--r--crates/ra_hir/src/has_source.rs10
-rw-r--r--crates/ra_hir/src/lib.rs21
-rw-r--r--crates/ra_hir/src/semantics.rs329
-rw-r--r--crates/ra_hir/src/semantics/source_to_def.rs12
-rw-r--r--crates/ra_hir/src/source_analyzer.rs60
-rw-r--r--crates/ra_hir_def/Cargo.toml6
-rw-r--r--crates/ra_hir_def/src/adt.rs94
-rw-r--r--crates/ra_hir_def/src/attr.rs57
-rw-r--r--crates/ra_hir_def/src/body.rs67
-rw-r--r--crates/ra_hir_def/src/body/lower.rs159
-rw-r--r--crates/ra_hir_def/src/body/scope.rs140
-rw-r--r--crates/ra_hir_def/src/data.rs384
-rw-r--r--crates/ra_hir_def/src/db.rs25
-rw-r--r--crates/ra_hir_def/src/diagnostics.rs3
-rw-r--r--crates/ra_hir_def/src/docs.rs50
-rw-r--r--crates/ra_hir_def/src/expr.rs5
-rw-r--r--crates/ra_hir_def/src/find_path.rs247
-rw-r--r--crates/ra_hir_def/src/generics.rs62
-rw-r--r--crates/ra_hir_def/src/import_map.rs737
-rw-r--r--crates/ra_hir_def/src/item_scope.rs174
-rw-r--r--crates/ra_hir_def/src/item_tree.rs754
-rw-r--r--crates/ra_hir_def/src/item_tree/lower.rs708
-rw-r--r--crates/ra_hir_def/src/item_tree/tests.rs439
-rw-r--r--crates/ra_hir_def/src/lib.rs166
-rw-r--r--crates/ra_hir_def/src/nameres.rs12
-rw-r--r--crates/ra_hir_def/src/nameres/collector.rs514
-rw-r--r--crates/ra_hir_def/src/nameres/mod_resolution.rs121
-rw-r--r--crates/ra_hir_def/src/nameres/raw.rs482
-rw-r--r--crates/ra_hir_def/src/nameres/tests.rs25
-rw-r--r--crates/ra_hir_def/src/nameres/tests/globs.rs137
-rw-r--r--crates/ra_hir_def/src/nameres/tests/incremental.rs38
-rw-r--r--crates/ra_hir_def/src/nameres/tests/macros.rs24
-rw-r--r--crates/ra_hir_def/src/nameres/tests/mod_resolution.rs28
-rw-r--r--crates/ra_hir_def/src/path.rs37
-rw-r--r--crates/ra_hir_def/src/per_ns.rs10
-rw-r--r--crates/ra_hir_def/src/resolver.rs8
-rw-r--r--crates/ra_hir_def/src/src.rs31
-rw-r--r--crates/ra_hir_def/src/test_db.rs51
-rw-r--r--crates/ra_hir_def/src/type_ref.rs13
-rw-r--r--crates/ra_hir_def/src/visibility.rs21
-rw-r--r--crates/ra_hir_expand/Cargo.toml2
-rw-r--r--crates/ra_hir_expand/src/ast_id_map.rs13
-rw-r--r--crates/ra_hir_expand/src/builtin_derive.rs31
-rw-r--r--crates/ra_hir_expand/src/builtin_macro.rs111
-rw-r--r--crates/ra_hir_expand/src/diagnostics.rs2
-rw-r--r--crates/ra_hir_expand/src/eager.rs32
-rw-r--r--crates/ra_hir_expand/src/lib.rs30
-rw-r--r--crates/ra_hir_expand/src/name.rs5
-rw-r--r--crates/ra_hir_expand/src/test_db.rs44
-rw-r--r--crates/ra_hir_ty/Cargo.toml11
-rw-r--r--crates/ra_hir_ty/src/_match.rs2085
-rw-r--r--crates/ra_hir_ty/src/autoderef.rs15
-rw-r--r--crates/ra_hir_ty/src/db.rs61
-rw-r--r--crates/ra_hir_ty/src/diagnostics.rs327
-rw-r--r--crates/ra_hir_ty/src/diagnostics/expr.rs (renamed from crates/ra_hir_ty/src/expr.rs)277
-rw-r--r--crates/ra_hir_ty/src/diagnostics/match_check.rs1421
-rw-r--r--crates/ra_hir_ty/src/diagnostics/unsafe_check.rs173
-rw-r--r--crates/ra_hir_ty/src/display.rs78
-rw-r--r--crates/ra_hir_ty/src/infer.rs89
-rw-r--r--crates/ra_hir_ty/src/infer/expr.rs133
-rw-r--r--crates/ra_hir_ty/src/infer/pat.rs42
-rw-r--r--crates/ra_hir_ty/src/infer/path.rs5
-rw-r--r--crates/ra_hir_ty/src/infer/unify.rs55
-rw-r--r--crates/ra_hir_ty/src/lib.rs268
-rw-r--r--crates/ra_hir_ty/src/lower.rs329
-rw-r--r--crates/ra_hir_ty/src/method_resolution.rs393
-rw-r--r--crates/ra_hir_ty/src/primitive.rs54
-rw-r--r--crates/ra_hir_ty/src/test_db.rs129
-rw-r--r--crates/ra_hir_ty/src/tests.rs311
-rw-r--r--crates/ra_hir_ty/src/tests/coercion.rs690
-rw-r--r--crates/ra_hir_ty/src/tests/display_source_code.rs49
-rw-r--r--crates/ra_hir_ty/src/tests/macros.rs236
-rw-r--r--crates/ra_hir_ty/src/tests/method_resolution.rs590
-rw-r--r--crates/ra_hir_ty/src/tests/never_type.rs308
-rw-r--r--crates/ra_hir_ty/src/tests/patterns.rs652
-rw-r--r--crates/ra_hir_ty/src/tests/regression.rs527
-rw-r--r--crates/ra_hir_ty/src/tests/simple.rs1818
-rw-r--r--crates/ra_hir_ty/src/tests/traits.rs2041
-rw-r--r--crates/ra_hir_ty/src/traits.rs113
-rw-r--r--crates/ra_hir_ty/src/traits/builtin.rs377
-rw-r--r--crates/ra_hir_ty/src/traits/chalk.rs294
-rw-r--r--crates/ra_hir_ty/src/traits/chalk/interner.rs32
-rw-r--r--crates/ra_hir_ty/src/traits/chalk/mapping.rs286
-rw-r--r--crates/ra_hir_ty/src/traits/chalk/tls.rs33
-rw-r--r--crates/ra_hir_ty/src/utils.rs46
-rw-r--r--crates/ra_ide/Cargo.toml4
-rw-r--r--crates/ra_ide/src/call_hierarchy.rs214
-rw-r--r--crates/ra_ide/src/call_info.rs549
-rw-r--r--crates/ra_ide/src/completion.rs91
-rw-r--r--crates/ra_ide/src/completion/complete_attribute.rs861
-rw-r--r--crates/ra_ide/src/completion/complete_dot.rs994
-rw-r--r--crates/ra_ide/src/completion/complete_fn_param.rs131
-rw-r--r--crates/ra_ide/src/completion/complete_keyword.rs1102
-rw-r--r--crates/ra_ide/src/completion/complete_macro_in_item_position.rs137
-rw-r--r--crates/ra_ide/src/completion/complete_pattern.rs126
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs639
-rw-r--r--crates/ra_ide/src/completion/complete_qualified_path.rs1506
-rw-r--r--crates/ra_ide/src/completion/complete_record.rs546
-rw-r--r--crates/ra_ide/src/completion/complete_snippet.rs106
-rw-r--r--crates/ra_ide/src/completion/complete_trait_impl.rs464
-rw-r--r--crates/ra_ide/src/completion/complete_unqualified_path.rs1615
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs83
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs32
-rw-r--r--crates/ra_ide/src/completion/patterns.rs194
-rw-r--r--crates/ra_ide/src/completion/presentation.rs1639
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs111
-rw-r--r--crates/ra_ide/src/diagnostics.rs880
-rw-r--r--crates/ra_ide/src/display.rs29
-rw-r--r--crates/ra_ide/src/display/function_signature.rs120
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs134
-rw-r--r--crates/ra_ide/src/display/structure.rs429
-rw-r--r--crates/ra_ide/src/expand_macro.rs241
-rw-r--r--crates/ra_ide/src/extend_selection.rs12
-rw-r--r--crates/ra_ide/src/folding_ranges.rs174
-rw-r--r--crates/ra_ide/src/goto_definition.rs1164
-rw-r--r--crates/ra_ide/src/goto_implementation.rs197
-rw-r--r--crates/ra_ide/src/goto_type_definition.rs132
-rw-r--r--crates/ra_ide/src/hover.rs2625
-rw-r--r--crates/ra_ide/src/inlay_hints.rs992
-rw-r--r--crates/ra_ide/src/join_lines.rs5
-rw-r--r--crates/ra_ide/src/lib.rs87
-rw-r--r--crates/ra_ide/src/markup.rs38
-rw-r--r--crates/ra_ide/src/matching_brace.rs33
-rw-r--r--crates/ra_ide/src/mock_analysis.rs214
-rw-r--r--crates/ra_ide/src/parent_module.rs13
-rw-r--r--crates/ra_ide/src/prime_caches.rs2
-rw-r--r--crates/ra_ide/src/references.rs515
-rw-r--r--crates/ra_ide/src/references/rename.rs1210
-rw-r--r--crates/ra_ide/src/runnables.rs898
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html41
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html84
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html101
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html42
-rw-r--r--crates/ra_ide/src/ssr.rs618
-rw-r--r--crates/ra_ide/src/status.rs28
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs333
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs11
-rw-r--r--crates/ra_ide/src/syntax_highlighting/injection.rs188
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs18
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs201
-rw-r--r--crates/ra_ide/src/syntax_tree.rs12
-rw-r--r--crates/ra_ide/src/typing.rs44
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs15
-rw-r--r--crates/ra_ide/test_data/highlight_doctest.html102
-rw-r--r--crates/ra_ide/test_data/highlight_injection.html48
-rw-r--r--crates/ra_ide/test_data/highlight_strings.html96
-rw-r--r--crates/ra_ide/test_data/highlight_unsafe.html54
-rw-r--r--crates/ra_ide/test_data/highlighting.html128
-rw-r--r--crates/ra_ide/test_data/rainbow_highlighting.html49
-rw-r--r--crates/ra_ide_db/Cargo.toml1
-rw-r--r--crates/ra_ide_db/src/change.rs254
-rw-r--r--crates/ra_ide_db/src/defs.rs69
-rw-r--r--crates/ra_ide_db/src/imports_locator.rs84
-rw-r--r--crates/ra_ide_db/src/lib.rs80
-rw-r--r--crates/ra_ide_db/src/search.rs16
-rw-r--r--crates/ra_ide_db/src/source_change.rs31
-rw-r--r--crates/ra_ide_db/src/symbol_index.rs138
-rw-r--r--crates/ra_mbe/Cargo.toml1
-rw-r--r--crates/ra_mbe/src/lib.rs6
-rw-r--r--crates/ra_mbe/src/mbe_expander/matcher.rs2
-rw-r--r--crates/ra_mbe/src/parser.rs5
-rw-r--r--crates/ra_parser/Cargo.toml1
-rw-r--r--crates/ra_parser/src/grammar.rs12
-rw-r--r--crates/ra_parser/src/grammar/expressions.rs6
-rw-r--r--crates/ra_parser/src/grammar/items.rs20
-rw-r--r--crates/ra_parser/src/grammar/paths.rs15
-rw-r--r--crates/ra_parser/src/grammar/patterns.rs8
-rw-r--r--crates/ra_parser/src/grammar/type_params.rs11
-rw-r--r--crates/ra_parser/src/grammar/types.rs16
-rw-r--r--crates/ra_parser/src/parser.rs15
-rw-r--r--crates/ra_parser/src/syntax_kind.rs5
-rw-r--r--crates/ra_proc_macro/Cargo.toml1
-rw-r--r--crates/ra_proc_macro_srv/Cargo.toml2
-rw-r--r--crates/ra_proc_macro_srv/src/dylib.rs2
-rw-r--r--crates/ra_proc_macro_srv/src/tests/fixtures/test_serialize_proc_macro.txt2
-rw-r--r--crates/ra_proc_macro_srv/src/tests/mod.rs6
-rw-r--r--crates/ra_proc_macro_srv/src/tests/utils.rs7
-rw-r--r--crates/ra_prof/Cargo.toml8
-rw-r--r--crates/ra_prof/src/lib.rs13
-rw-r--r--crates/ra_project_model/Cargo.toml3
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs79
-rw-r--r--crates/ra_project_model/src/json_project.rs56
-rw-r--r--crates/ra_project_model/src/lib.rs220
-rw-r--r--crates/ra_project_model/src/project_json.rs129
-rw-r--r--crates/ra_project_model/src/sysroot.rs28
-rw-r--r--crates/ra_ssr/Cargo.toml20
-rw-r--r--crates/ra_ssr/src/errors.rs29
-rw-r--r--crates/ra_ssr/src/lib.rs285
-rw-r--r--crates/ra_ssr/src/matching.rs623
-rw-r--r--crates/ra_ssr/src/parsing.rs343
-rw-r--r--crates/ra_ssr/src/replacing.rs66
-rw-r--r--crates/ra_ssr/src/tests.rs582
-rw-r--r--crates/ra_syntax/Cargo.toml4
-rw-r--r--crates/ra_syntax/src/algo.rs12
-rw-r--r--crates/ra_syntax/src/ast.rs8
-rw-r--r--crates/ra_syntax/src/ast/edit.rs36
-rw-r--r--crates/ra_syntax/src/ast/expr_extensions.rs5
-rw-r--r--crates/ra_syntax/src/ast/extensions.rs16
-rw-r--r--crates/ra_syntax/src/ast/generated/nodes.rs276
-rw-r--r--crates/ra_syntax/src/ast/generated/tokens.rs8
-rw-r--r--crates/ra_syntax/src/ast/make.rs4
-rw-r--r--crates/ra_syntax/src/ast/tokens.rs69
-rw-r--r--crates/ra_syntax/src/ast/traits.rs13
-rw-r--r--crates/ra_syntax/src/lib.rs38
-rw-r--r--crates/ra_syntax/src/parsing.rs32
-rw-r--r--crates/ra_syntax/src/parsing/lexer.rs24
-rw-r--r--crates/ra_syntax/src/parsing/reparsing.rs5
-rw-r--r--crates/ra_syntax/src/parsing/text_token_source.rs89
-rw-r--r--crates/ra_syntax/src/parsing/text_tree_sink.rs5
-rw-r--r--crates/ra_syntax/src/syntax_node.rs4
-rw-r--r--crates/ra_syntax/src/tests.rs182
-rw-r--r--crates/ra_syntax/test_data/parser/err/0004_use_path_bad_segment.rast5
-rw-r--r--crates/ra_syntax/test_data/parser/err/0024_many_type_parens.rast74
-rw-r--r--crates/ra_syntax/test_data/parser/err/0027_incomplere_where_for.rast15
-rw-r--r--crates/ra_syntax/test_data/parser/err/0043_default_const.rast40
-rw-r--r--crates/ra_syntax/test_data/parser/err/0043_default_const.rs3
-rw-r--r--crates/ra_syntax/test_data/parser/err/0044_unexpected_for_type.rast240
-rw-r--r--crates/ra_syntax/test_data/parser/err/0044_unexpected_for_type.rs9
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/expr/err/0000_truncated_add.rast1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/expr/err/0000_truncated_add.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/expr/ok/0000_add.rast8
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/expr/ok/0000_add.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/item/err/0000_extra_keyword.rast1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/item/err/0000_extra_keyword.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/item/ok/0000_fn.rast12
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/item/ok/0000_fn.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/path/err/0000_reserved_word.rast1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/path/err/0000_reserved_word.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/path/err/0001_expression.rast1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/path/err/0001_expression.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/path/ok/0000_single_ident.rast4
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/path/ok/0000_single_ident.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/path/ok/0001_multipart.rast14
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/path/ok/0001_multipart.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/pattern/err/0000_reserved_word.rast1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/pattern/err/0000_reserved_word.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/pattern/err/0001_missing_paren.rast1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/pattern/err/0001_missing_paren.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/pattern/ok/0000_enum.rast10
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/pattern/ok/0000_enum.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/type/err/0000_missing_close.rast1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/type/err/0000_missing_close.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/type/ok/0000_result.rast22
-rw-r--r--crates/ra_syntax/test_data/parser/fragments/type/ok/0000_result.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rast1
-rw-r--r--crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rast34
-rw-r--r--crates/ra_syntax/test_data/parser/inline/err/0015_empty_segment.rast15
-rw-r--r--crates/ra_syntax/test_data/parser/inline/err/0015_empty_segment.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0003_where_pred_for.rast117
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0003_where_pred_for.rs2
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0081_for_type.rast292
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0081_for_type.rs5
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_fn.rast40
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_fn.rs3
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rast18
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0164_type_path_in_pattern.rast38
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0164_type_path_in_pattern.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0066_default_const.rast44
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0066_default_const.rs3
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0067_where_for_pred.rast392
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0067_where_for_pred.rs30
-rw-r--r--crates/ra_text_edit/Cargo.toml1
-rw-r--r--crates/ra_toolchain/Cargo.toml4
-rw-r--r--crates/ra_toolchain/src/lib.rs25
-rw-r--r--crates/ra_tt/Cargo.toml2
-rw-r--r--crates/ra_tt/src/buffer.rs5
-rw-r--r--crates/ra_tt/src/lib.rs19
-rw-r--r--crates/rust-analyzer/Cargo.toml22
-rw-r--r--crates/rust-analyzer/build.rs5
-rw-r--r--crates/rust-analyzer/src/bin/args.rs95
-rw-r--r--crates/rust-analyzer/src/bin/main.rs84
-rw-r--r--crates/rust-analyzer/src/caps.rs25
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs133
-rw-r--r--crates/rust-analyzer/src/cli.rs12
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs70
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs80
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs80
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs187
-rw-r--r--crates/rust-analyzer/src/cli/ssr.rs71
-rw-r--r--crates/rust-analyzer/src/config.rs348
-rw-r--r--crates/rust-analyzer/src/diagnostics.rs72
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs499
-rw-r--r--crates/rust-analyzer/src/dispatch.rs170
-rw-r--r--crates/rust-analyzer/src/from_proto.rs42
-rw-r--r--crates/rust-analyzer/src/global_state.rs292
-rw-r--r--crates/rust-analyzer/src/handlers.rs (renamed from crates/rust-analyzer/src/main_loop/handlers.rs)988
-rw-r--r--crates/rust-analyzer/src/lib.rs66
-rw-r--r--crates/rust-analyzer/src/line_endings.rs52
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs123
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs195
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1444
-rw-r--r--crates/rust-analyzer/src/main_loop/pending_requests.rs75
-rw-r--r--crates/rust-analyzer/src/main_loop/subscriptions.rs22
-rw-r--r--crates/rust-analyzer/src/reload.rs324
-rw-r--r--crates/rust-analyzer/src/request_metrics.rs37
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs6
-rw-r--r--crates/rust-analyzer/src/thread_pool.rs35
-rw-r--r--crates/rust-analyzer/src/to_proto.rs276
-rw-r--r--crates/rust-analyzer/src/vfs_glob.rs98
-rw-r--r--crates/rust-analyzer/src/world.rs343
-rw-r--r--crates/rust-analyzer/test_data/clippy_pass_by_ref.txt (renamed from crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_clippy_pass_by_ref.snap)18
-rw-r--r--crates/rust-analyzer/test_data/handles_macro_location.txt (renamed from crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_handles_macro_location.snap)18
-rw-r--r--crates/rust-analyzer/test_data/macro_compiler_error.txt (renamed from crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_macro_compiler_error.snap)18
-rw-r--r--crates/rust-analyzer/test_data/rustc_incompatible_type_for_trait.txt (renamed from crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_incompatible_type_for_trait.snap)18
-rw-r--r--crates/rust-analyzer/test_data/rustc_mismatched_type.txt (renamed from crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_mismatched_type.snap)18
-rw-r--r--crates/rust-analyzer/test_data/rustc_unused_variable.txt (renamed from crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap)24
-rw-r--r--crates/rust-analyzer/test_data/rustc_unused_variable_as_hint.txt71
-rw-r--r--crates/rust-analyzer/test_data/rustc_unused_variable_as_info.txt71
-rw-r--r--crates/rust-analyzer/test_data/rustc_wrong_number_of_parameters.txt (renamed from crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_wrong_number_of_parameters.snap)18
-rw-r--r--crates/rust-analyzer/test_data/snap_multi_line_fix.txt (renamed from crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap)24
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs285
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs91
-rw-r--r--crates/stdx/Cargo.toml1
-rw-r--r--crates/stdx/src/lib.rs111
-rw-r--r--crates/stdx/src/macros.rs40
-rw-r--r--crates/test_utils/Cargo.toml6
-rw-r--r--crates/test_utils/src/fixture.rs142
-rw-r--r--crates/test_utils/src/lib.rs501
-rw-r--r--crates/test_utils/src/mark.rs4
-rw-r--r--crates/vfs-notify/Cargo.toml21
-rw-r--r--crates/vfs-notify/src/include.rs42
-rw-r--r--crates/vfs-notify/src/lib.rs230
-rw-r--r--crates/vfs/Cargo.toml15
-rw-r--r--crates/vfs/src/file_set.rs172
-rw-r--r--crates/vfs/src/lib.rs140
-rw-r--r--crates/vfs/src/loader.rs71
-rw-r--r--crates/vfs/src/path_interner.rs31
-rw-r--r--crates/vfs/src/vfs_path.rs146
397 files changed, 36615 insertions, 27931 deletions
diff --git a/crates/expect/Cargo.toml b/crates/expect/Cargo.toml
new file mode 100644
index 000000000..77775630d
--- /dev/null
+++ b/crates/expect/Cargo.toml
@@ -0,0 +1,14 @@
1[package]
2name = "expect"
3version = "0.1.0"
4authors = ["rust-analyzer developers"]
5edition = "2018"
6license = "MIT OR Apache-2.0"
7
8[lib]
9doctest = false
10
11[dependencies]
12once_cell = "1"
13difference = "2"
14stdx = { path = "../stdx" }
diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs
new file mode 100644
index 000000000..21a458d47
--- /dev/null
+++ b/crates/expect/src/lib.rs
@@ -0,0 +1,356 @@
1//! Snapshot testing library, see
2//! https://github.com/rust-analyzer/rust-analyzer/pull/5101
3use std::{
4 collections::HashMap,
5 env, fmt, fs, mem,
6 ops::Range,
7 panic,
8 path::{Path, PathBuf},
9 sync::Mutex,
10};
11
12use difference::Changeset;
13use once_cell::sync::Lazy;
14use stdx::{lines_with_ends, trim_indent};
15
16const HELP: &str = "
17You can update all `expect![[]]` tests by running:
18
19 env UPDATE_EXPECT=1 cargo test
20
21To update a single test, place the cursor on `expect` token and use `run` feature of rust-analyzer.
22";
23
24fn update_expect() -> bool {
25 env::var("UPDATE_EXPECT").is_ok()
26}
27
28/// expect![[r#"inline snapshot"#]]
29#[macro_export]
30macro_rules! expect {
31 [[$data:literal]] => {$crate::Expect {
32 position: $crate::Position {
33 file: file!(),
34 line: line!(),
35 column: column!(),
36 },
37 data: $data,
38 }};
39 [[]] => { $crate::expect![[""]] };
40}
41
42/// expect_file!["/crates/foo/test_data/bar.html"]
43#[macro_export]
44macro_rules! expect_file {
45 [$path:expr] => {$crate::ExpectFile {
46 path: std::path::PathBuf::from($path)
47 }};
48}
49
50#[derive(Debug)]
51pub struct Expect {
52 pub position: Position,
53 pub data: &'static str,
54}
55
56#[derive(Debug)]
57pub struct ExpectFile {
58 pub path: PathBuf,
59}
60
61#[derive(Debug)]
62pub struct Position {
63 pub file: &'static str,
64 pub line: u32,
65 pub column: u32,
66}
67
68impl fmt::Display for Position {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 write!(f, "{}:{}:{}", self.file, self.line, self.column)
71 }
72}
73
74impl Expect {
75 pub fn assert_eq(&self, actual: &str) {
76 let trimmed = self.trimmed();
77 if &trimmed == actual {
78 return;
79 }
80 Runtime::fail_expect(self, &trimmed, actual);
81 }
82 pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) {
83 let actual = format!("{:#?}\n", actual);
84 self.assert_eq(&actual)
85 }
86
87 fn trimmed(&self) -> String {
88 if !self.data.contains('\n') {
89 return self.data.to_string();
90 }
91 trim_indent(self.data)
92 }
93
94 fn locate(&self, file: &str) -> Location {
95 let mut target_line = None;
96 let mut line_start = 0;
97 for (i, line) in lines_with_ends(file).enumerate() {
98 if i == self.position.line as usize - 1 {
99 let pat = "expect![[";
100 let offset = line.find(pat).unwrap();
101 let literal_start = line_start + offset + pat.len();
102 let indent = line.chars().take_while(|&it| it == ' ').count();
103 target_line = Some((literal_start, indent));
104 break;
105 }
106 line_start += line.len();
107 }
108 let (literal_start, line_indent) = target_line.unwrap();
109 let literal_length =
110 file[literal_start..].find("]]").expect("Couldn't find matching `]]` for `expect![[`.");
111 let literal_range = literal_start..literal_start + literal_length;
112 Location { line_indent, literal_range }
113 }
114}
115
116impl ExpectFile {
117 pub fn assert_eq(&self, actual: &str) {
118 let expected = self.read();
119 if actual == expected {
120 return;
121 }
122 Runtime::fail_file(self, &expected, actual);
123 }
124 pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) {
125 let actual = format!("{:#?}\n", actual);
126 self.assert_eq(&actual)
127 }
128 fn read(&self) -> String {
129 fs::read_to_string(self.abs_path()).unwrap_or_default().replace("\r\n", "\n")
130 }
131 fn write(&self, contents: &str) {
132 fs::write(self.abs_path(), contents).unwrap()
133 }
134 fn abs_path(&self) -> PathBuf {
135 WORKSPACE_ROOT.join(&self.path)
136 }
137}
138
139#[derive(Default)]
140struct Runtime {
141 help_printed: bool,
142 per_file: HashMap<&'static str, FileRuntime>,
143}
144static RT: Lazy<Mutex<Runtime>> = Lazy::new(Default::default);
145
146impl Runtime {
147 fn fail_expect(expect: &Expect, expected: &str, actual: &str) {
148 let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
149 if update_expect() {
150 println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.position);
151 rt.per_file
152 .entry(expect.position.file)
153 .or_insert_with(|| FileRuntime::new(expect))
154 .update(expect, actual);
155 return;
156 }
157 rt.panic(expect.position.to_string(), expected, actual);
158 }
159
160 fn fail_file(expect: &ExpectFile, expected: &str, actual: &str) {
161 let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
162 if update_expect() {
163 println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path.display());
164 expect.write(actual);
165 return;
166 }
167 rt.panic(expect.path.display().to_string(), expected, actual);
168 }
169
170 fn panic(&mut self, position: String, expected: &str, actual: &str) {
171 let print_help = !mem::replace(&mut self.help_printed, true);
172 let help = if print_help { HELP } else { "" };
173
174 let diff = Changeset::new(actual, expected, "\n");
175
176 println!(
177 "\n
178\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m
179 \x1b[1m\x1b[34m-->\x1b[0m {}
180{}
181\x1b[1mExpect\x1b[0m:
182----
183{}
184----
185
186\x1b[1mActual\x1b[0m:
187----
188{}
189----
190
191\x1b[1mDiff\x1b[0m:
192----
193{}
194----
195",
196 position, help, expected, actual, diff
197 );
198 // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise.
199 panic::resume_unwind(Box::new(()));
200 }
201}
202
203struct FileRuntime {
204 path: PathBuf,
205 original_text: String,
206 patchwork: Patchwork,
207}
208
209impl FileRuntime {
210 fn new(expect: &Expect) -> FileRuntime {
211 let path = WORKSPACE_ROOT.join(expect.position.file);
212 let original_text = fs::read_to_string(&path).unwrap();
213 let patchwork = Patchwork::new(original_text.clone());
214 FileRuntime { path, original_text, patchwork }
215 }
216 fn update(&mut self, expect: &Expect, actual: &str) {
217 let loc = expect.locate(&self.original_text);
218 let patch = format_patch(loc.line_indent.clone(), actual);
219 self.patchwork.patch(loc.literal_range, &patch);
220 fs::write(&self.path, &self.patchwork.text).unwrap()
221 }
222}
223
224#[derive(Debug)]
225struct Location {
226 line_indent: usize,
227 literal_range: Range<usize>,
228}
229
230#[derive(Debug)]
231struct Patchwork {
232 text: String,
233 indels: Vec<(Range<usize>, usize)>,
234}
235
236impl Patchwork {
237 fn new(text: String) -> Patchwork {
238 Patchwork { text, indels: Vec::new() }
239 }
240 fn patch(&mut self, mut range: Range<usize>, patch: &str) {
241 self.indels.push((range.clone(), patch.len()));
242 self.indels.sort_by_key(|(delete, _insert)| delete.start);
243
244 let (delete, insert) = self
245 .indels
246 .iter()
247 .take_while(|(delete, _)| delete.start < range.start)
248 .map(|(delete, insert)| (delete.end - delete.start, insert))
249 .fold((0usize, 0usize), |(x1, y1), (x2, y2)| (x1 + x2, y1 + y2));
250
251 for pos in &mut [&mut range.start, &mut range.end] {
252 **pos -= delete;
253 **pos += insert;
254 }
255
256 self.text.replace_range(range, &patch);
257 }
258}
259
260fn format_patch(line_indent: usize, patch: &str) -> String {
261 let mut max_hashes = 0;
262 let mut cur_hashes = 0;
263 for byte in patch.bytes() {
264 if byte != b'#' {
265 cur_hashes = 0;
266 continue;
267 }
268 cur_hashes += 1;
269 max_hashes = max_hashes.max(cur_hashes);
270 }
271 let hashes = &"#".repeat(max_hashes + 1);
272 let indent = &" ".repeat(line_indent);
273 let is_multiline = patch.contains('\n');
274
275 let mut buf = String::new();
276 buf.push('r');
277 buf.push_str(hashes);
278 buf.push('"');
279 if is_multiline {
280 buf.push('\n');
281 }
282 let mut final_newline = false;
283 for line in lines_with_ends(patch) {
284 if is_multiline && !line.trim().is_empty() {
285 buf.push_str(indent);
286 buf.push_str(" ");
287 }
288 buf.push_str(line);
289 final_newline = line.ends_with('\n');
290 }
291 if final_newline {
292 buf.push_str(indent);
293 }
294 buf.push('"');
295 buf.push_str(hashes);
296 buf
297}
298
299static WORKSPACE_ROOT: Lazy<PathBuf> = Lazy::new(|| {
300 let my_manifest =
301 env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned());
302 // Heuristic, see https://github.com/rust-lang/cargo/issues/3946
303 Path::new(&my_manifest)
304 .ancestors()
305 .filter(|it| it.join("Cargo.toml").exists())
306 .last()
307 .unwrap()
308 .to_path_buf()
309});
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn test_format_patch() {
317 let patch = format_patch(0, "hello\nworld\n");
318 expect![[r##"
319 r#"
320 hello
321 world
322 "#"##]]
323 .assert_eq(&patch);
324
325 let patch = format_patch(4, "single line");
326 expect![[r##"r#"single line"#"##]].assert_eq(&patch);
327 }
328
329 #[test]
330 fn test_patchwork() {
331 let mut patchwork = Patchwork::new("one two three".to_string());
332 patchwork.patch(4..7, "zwei");
333 patchwork.patch(0..3, "один");
334 patchwork.patch(8..13, "3");
335 expect![[r#"
336 Patchwork {
337 text: "один zwei 3",
338 indels: [
339 (
340 0..3,
341 8,
342 ),
343 (
344 4..7,
345 4,
346 ),
347 (
348 8..13,
349 1,
350 ),
351 ],
352 }
353 "#]]
354 .assert_debug_eq(&patchwork);
355 }
356}
diff --git a/crates/ra_flycheck/Cargo.toml b/crates/flycheck/Cargo.toml
index 1aa39bade..bea485694 100644
--- a/crates/ra_flycheck/Cargo.toml
+++ b/crates/flycheck/Cargo.toml
@@ -1,8 +1,9 @@
1[package] 1[package]
2edition = "2018" 2edition = "2018"
3name = "ra_flycheck" 3name = "flycheck"
4version = "0.1.0" 4version = "0.1.0"
5authors = ["rust-analyzer developers"] 5authors = ["rust-analyzer developers"]
6license = "MIT OR Apache-2.0"
6 7
7[lib] 8[lib]
8doctest = false 9doctest = false
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
new file mode 100644
index 000000000..6804d9bda
--- /dev/null
+++ b/crates/flycheck/src/lib.rs
@@ -0,0 +1,317 @@
1//! cargo_check provides the functionality needed to run `cargo check` or
2//! another compatible command (f.x. clippy) in a background thread and provide
3//! LSP diagnostics based on the output of the command.
4
5use std::{
6 fmt,
7 io::{self, BufReader},
8 ops,
9 path::PathBuf,
10 process::{self, Command, Stdio},
11 time::Duration,
12};
13
14use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
15
16pub use cargo_metadata::diagnostic::{
17 Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
18 DiagnosticSpanMacroExpansion,
19};
20
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub enum FlycheckConfig {
23 CargoCommand {
24 command: String,
25 all_targets: bool,
26 all_features: bool,
27 features: Vec<String>,
28 extra_args: Vec<String>,
29 },
30 CustomCommand {
31 command: String,
32 args: Vec<String>,
33 },
34}
35
36impl fmt::Display for FlycheckConfig {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 match self {
39 FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {}", command),
40 FlycheckConfig::CustomCommand { command, args } => {
41 write!(f, "{} {}", command, args.join(" "))
42 }
43 }
44 }
45}
46
47/// Flycheck wraps the shared state and communication machinery used for
48/// running `cargo check` (or other compatible command) and providing
49/// diagnostics based on the output.
50/// The spawned thread is shut down when this struct is dropped.
51#[derive(Debug)]
52pub struct FlycheckHandle {
53 // XXX: drop order is significant
54 sender: Sender<Restart>,
55 thread: jod_thread::JoinHandle,
56}
57
58impl FlycheckHandle {
59 pub fn spawn(
60 sender: Box<dyn Fn(Message) + Send>,
61 config: FlycheckConfig,
62 workspace_root: PathBuf,
63 ) -> FlycheckHandle {
64 let actor = FlycheckActor::new(sender, config, workspace_root);
65 let (sender, receiver) = unbounded::<Restart>();
66 let thread = jod_thread::spawn(move || actor.run(receiver));
67 FlycheckHandle { sender, thread }
68 }
69
70 /// Schedule a re-start of the cargo check worker.
71 pub fn update(&self) {
72 self.sender.send(Restart).unwrap();
73 }
74}
75
76#[derive(Debug)]
77pub enum Message {
78 /// Request adding a diagnostic with fixes included to a file
79 AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic },
80
81 /// Request check progress notification to client
82 Progress(Progress),
83}
84
85#[derive(Debug)]
86pub enum Progress {
87 DidStart,
88 DidCheckCrate(String),
89 DidFinish(io::Result<()>),
90 DidCancel,
91}
92
93struct Restart;
94
95struct FlycheckActor {
96 sender: Box<dyn Fn(Message) + Send>,
97 config: FlycheckConfig,
98 workspace_root: PathBuf,
99 /// WatchThread exists to wrap around the communication needed to be able to
100 /// run `cargo check` without blocking. Currently the Rust standard library
101 /// doesn't provide a way to read sub-process output without blocking, so we
102 /// have to wrap sub-processes output handling in a thread and pass messages
103 /// back over a channel.
104 cargo_handle: Option<CargoHandle>,
105}
106
107enum Event {
108 Restart(Restart),
109 CheckEvent(Option<cargo_metadata::Message>),
110}
111
112impl FlycheckActor {
113 fn new(
114 sender: Box<dyn Fn(Message) + Send>,
115 config: FlycheckConfig,
116 workspace_root: PathBuf,
117 ) -> FlycheckActor {
118 FlycheckActor { sender, config, workspace_root, cargo_handle: None }
119 }
120 fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
121 let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver);
122 select! {
123 recv(inbox) -> msg => msg.ok().map(Event::Restart),
124 recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
125 }
126 }
127 fn run(mut self, inbox: Receiver<Restart>) {
128 while let Some(event) = self.next_event(&inbox) {
129 match event {
130 Event::Restart(Restart) => {
131 while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {}
132
133 self.cancel_check_process();
134
135 let mut command = self.check_command();
136 log::info!("restart flycheck {:?}", command);
137 command.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null());
138 if let Ok(child) = command.spawn().map(JodChild) {
139 self.cargo_handle = Some(CargoHandle::spawn(child));
140 self.send(Message::Progress(Progress::DidStart));
141 }
142 }
143 Event::CheckEvent(None) => {
144 // Watcher finished, replace it with a never channel to
145 // avoid busy-waiting.
146 let cargo_handle = self.cargo_handle.take().unwrap();
147 let res = cargo_handle.join();
148 self.send(Message::Progress(Progress::DidFinish(res)));
149 }
150 Event::CheckEvent(Some(message)) => match message {
151 cargo_metadata::Message::CompilerArtifact(msg) => {
152 self.send(Message::Progress(Progress::DidCheckCrate(msg.target.name)));
153 }
154
155 cargo_metadata::Message::CompilerMessage(msg) => {
156 self.send(Message::AddDiagnostic {
157 workspace_root: self.workspace_root.clone(),
158 diagnostic: msg.message,
159 });
160 }
161
162 cargo_metadata::Message::BuildScriptExecuted(_)
163 | cargo_metadata::Message::BuildFinished(_)
164 | cargo_metadata::Message::TextLine(_)
165 | cargo_metadata::Message::Unknown => {}
166 },
167 }
168 }
169 // If we rerun the thread, we need to discard the previous check results first
170 self.cancel_check_process();
171 }
172 fn cancel_check_process(&mut self) {
173 if self.cargo_handle.take().is_some() {
174 self.send(Message::Progress(Progress::DidCancel));
175 }
176 }
177 fn check_command(&self) -> Command {
178 let mut cmd = match &self.config {
179 FlycheckConfig::CargoCommand {
180 command,
181 all_targets,
182 all_features,
183 extra_args,
184 features,
185 } => {
186 let mut cmd = Command::new(ra_toolchain::cargo());
187 cmd.arg(command);
188 cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
189 .arg(self.workspace_root.join("Cargo.toml"));
190 if *all_targets {
191 cmd.arg("--all-targets");
192 }
193 if *all_features {
194 cmd.arg("--all-features");
195 } else if !features.is_empty() {
196 cmd.arg("--features");
197 cmd.arg(features.join(" "));
198 }
199 cmd.args(extra_args);
200 cmd
201 }
202 FlycheckConfig::CustomCommand { command, args } => {
203 let mut cmd = Command::new(command);
204 cmd.args(args);
205 cmd
206 }
207 };
208 cmd.current_dir(&self.workspace_root);
209 cmd
210 }
211
212 fn send(&self, check_task: Message) {
213 (self.sender)(check_task)
214 }
215}
216
217struct CargoHandle {
218 child: JodChild,
219 #[allow(unused)]
220 thread: jod_thread::JoinHandle<io::Result<bool>>,
221 receiver: Receiver<cargo_metadata::Message>,
222}
223
224impl CargoHandle {
225 fn spawn(mut child: JodChild) -> CargoHandle {
226 let child_stdout = child.stdout.take().unwrap();
227 let (sender, receiver) = unbounded();
228 let actor = CargoActor::new(child_stdout, sender);
229 let thread = jod_thread::spawn(move || actor.run());
230 CargoHandle { child, thread, receiver }
231 }
232 fn join(mut self) -> io::Result<()> {
233 // It is okay to ignore the result, as it only errors if the process is already dead
234 let _ = self.child.kill();
235 let exit_status = self.child.wait()?;
236 let read_at_least_one_message = self.thread.join()?;
237 if !exit_status.success() && !read_at_least_one_message {
238 // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment:
239 // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298
240 return Err(io::Error::new(
241 io::ErrorKind::Other,
242 format!(
243 "Cargo watcher failed,the command produced no valid metadata (exit code: {:?})",
244 exit_status
245 ),
246 ));
247 }
248 Ok(())
249 }
250}
251
252struct CargoActor {
253 child_stdout: process::ChildStdout,
254 sender: Sender<cargo_metadata::Message>,
255}
256
257impl CargoActor {
258 fn new(
259 child_stdout: process::ChildStdout,
260 sender: Sender<cargo_metadata::Message>,
261 ) -> CargoActor {
262 CargoActor { child_stdout, sender }
263 }
264 fn run(self) -> io::Result<bool> {
265 // We manually read a line at a time, instead of using serde's
266 // stream deserializers, because the deserializer cannot recover
267 // from an error, resulting in it getting stuck, because we try to
268 // be resilient against failures.
269 //
270 // Because cargo only outputs one JSON object per line, we can
271 // simply skip a line if it doesn't parse, which just ignores any
272 // erroneus output.
273 let stdout = BufReader::new(self.child_stdout);
274 let mut read_at_least_one_message = false;
275 for message in cargo_metadata::Message::parse_stream(stdout) {
276 let message = match message {
277 Ok(message) => message,
278 Err(err) => {
279 log::error!("Invalid json from cargo check, ignoring ({})", err);
280 continue;
281 }
282 };
283
284 read_at_least_one_message = true;
285
286 // Skip certain kinds of messages to only spend time on what's useful
287 match &message {
288 cargo_metadata::Message::CompilerArtifact(artifact) if artifact.fresh => (),
289 cargo_metadata::Message::BuildScriptExecuted(_)
290 | cargo_metadata::Message::Unknown => (),
291 _ => self.sender.send(message).unwrap(),
292 }
293 }
294 Ok(read_at_least_one_message)
295 }
296}
297
298struct JodChild(process::Child);
299
300impl ops::Deref for JodChild {
301 type Target = process::Child;
302 fn deref(&self) -> &process::Child {
303 &self.0
304 }
305}
306
307impl ops::DerefMut for JodChild {
308 fn deref_mut(&mut self) -> &mut process::Child {
309 &mut self.0
310 }
311}
312
313impl Drop for JodChild {
314 fn drop(&mut self) {
315 let _ = self.0.kill();
316 }
317}
diff --git a/crates/paths/Cargo.toml b/crates/paths/Cargo.toml
new file mode 100644
index 000000000..cbe2c26e2
--- /dev/null
+++ b/crates/paths/Cargo.toml
@@ -0,0 +1,9 @@
1[package]
2name = "paths"
3version = "0.1.0"
4authors = ["rust-analyzer developers"]
5edition = "2018"
6license = "MIT OR Apache-2.0"
7
8[lib]
9doctest = false
diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs
new file mode 100644
index 000000000..1b259682d
--- /dev/null
+++ b/crates/paths/src/lib.rs
@@ -0,0 +1,217 @@
1//! Thin wrappers around `std::path`, distinguishing between absolute and
2//! relative paths.
3use std::{
4 convert::{TryFrom, TryInto},
5 ops,
6 path::{Component, Path, PathBuf},
7};
8
9#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
10pub struct AbsPathBuf(PathBuf);
11
12impl From<AbsPathBuf> for PathBuf {
13 fn from(AbsPathBuf(path_buf): AbsPathBuf) -> PathBuf {
14 path_buf
15 }
16}
17
18impl ops::Deref for AbsPathBuf {
19 type Target = AbsPath;
20 fn deref(&self) -> &AbsPath {
21 self.as_path()
22 }
23}
24
25impl AsRef<Path> for AbsPathBuf {
26 fn as_ref(&self) -> &Path {
27 self.0.as_path()
28 }
29}
30
31impl AsRef<AbsPath> for AbsPathBuf {
32 fn as_ref(&self) -> &AbsPath {
33 self.as_path()
34 }
35}
36
37impl TryFrom<PathBuf> for AbsPathBuf {
38 type Error = PathBuf;
39 fn try_from(path_buf: PathBuf) -> Result<AbsPathBuf, PathBuf> {
40 if !path_buf.is_absolute() {
41 return Err(path_buf);
42 }
43 Ok(AbsPathBuf(path_buf))
44 }
45}
46
47impl TryFrom<&str> for AbsPathBuf {
48 type Error = PathBuf;
49 fn try_from(path: &str) -> Result<AbsPathBuf, PathBuf> {
50 AbsPathBuf::try_from(PathBuf::from(path))
51 }
52}
53
54impl PartialEq<AbsPath> for AbsPathBuf {
55 fn eq(&self, other: &AbsPath) -> bool {
56 self.as_path() == other
57 }
58}
59
60impl AbsPathBuf {
61 pub fn assert(path: PathBuf) -> AbsPathBuf {
62 AbsPathBuf::try_from(path)
63 .unwrap_or_else(|path| panic!("expected absolute path, got {}", path.display()))
64 }
65 pub fn as_path(&self) -> &AbsPath {
66 AbsPath::assert(self.0.as_path())
67 }
68 pub fn pop(&mut self) -> bool {
69 self.0.pop()
70 }
71}
72
73#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
74#[repr(transparent)]
75pub struct AbsPath(Path);
76
77impl ops::Deref for AbsPath {
78 type Target = Path;
79 fn deref(&self) -> &Path {
80 &self.0
81 }
82}
83
84impl AsRef<Path> for AbsPath {
85 fn as_ref(&self) -> &Path {
86 &self.0
87 }
88}
89
90impl<'a> TryFrom<&'a Path> for &'a AbsPath {
91 type Error = &'a Path;
92 fn try_from(path: &'a Path) -> Result<&'a AbsPath, &'a Path> {
93 if !path.is_absolute() {
94 return Err(path);
95 }
96 Ok(AbsPath::assert(path))
97 }
98}
99
100impl AbsPath {
101 pub fn assert(path: &Path) -> &AbsPath {
102 assert!(path.is_absolute());
103 unsafe { &*(path as *const Path as *const AbsPath) }
104 }
105
106 pub fn parent(&self) -> Option<&AbsPath> {
107 self.0.parent().map(AbsPath::assert)
108 }
109 pub fn join(&self, path: impl AsRef<Path>) -> AbsPathBuf {
110 self.as_ref().join(path).try_into().unwrap()
111 }
112 pub fn normalize(&self) -> AbsPathBuf {
113 AbsPathBuf(normalize_path(&self.0))
114 }
115 pub fn to_path_buf(&self) -> AbsPathBuf {
116 AbsPathBuf::try_from(self.0.to_path_buf()).unwrap()
117 }
118 pub fn strip_prefix(&self, base: &AbsPath) -> Option<&RelPath> {
119 self.0.strip_prefix(base).ok().map(RelPath::new_unchecked)
120 }
121}
122
123#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
124pub struct RelPathBuf(PathBuf);
125
126impl From<RelPathBuf> for PathBuf {
127 fn from(RelPathBuf(path_buf): RelPathBuf) -> PathBuf {
128 path_buf
129 }
130}
131
132impl ops::Deref for RelPathBuf {
133 type Target = RelPath;
134 fn deref(&self) -> &RelPath {
135 self.as_path()
136 }
137}
138
139impl AsRef<Path> for RelPathBuf {
140 fn as_ref(&self) -> &Path {
141 self.0.as_path()
142 }
143}
144
145impl TryFrom<PathBuf> for RelPathBuf {
146 type Error = PathBuf;
147 fn try_from(path_buf: PathBuf) -> Result<RelPathBuf, PathBuf> {
148 if !path_buf.is_relative() {
149 return Err(path_buf);
150 }
151 Ok(RelPathBuf(path_buf))
152 }
153}
154
155impl TryFrom<&str> for RelPathBuf {
156 type Error = PathBuf;
157 fn try_from(path: &str) -> Result<RelPathBuf, PathBuf> {
158 RelPathBuf::try_from(PathBuf::from(path))
159 }
160}
161
162impl RelPathBuf {
163 pub fn as_path(&self) -> &RelPath {
164 RelPath::new_unchecked(self.0.as_path())
165 }
166}
167
168#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
169#[repr(transparent)]
170pub struct RelPath(Path);
171
172impl ops::Deref for RelPath {
173 type Target = Path;
174 fn deref(&self) -> &Path {
175 &self.0
176 }
177}
178
179impl AsRef<Path> for RelPath {
180 fn as_ref(&self) -> &Path {
181 &self.0
182 }
183}
184
185impl RelPath {
186 pub fn new_unchecked(path: &Path) -> &RelPath {
187 unsafe { &*(path as *const Path as *const RelPath) }
188 }
189}
190
191// https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85
192fn normalize_path(path: &Path) -> PathBuf {
193 let mut components = path.components().peekable();
194 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
195 components.next();
196 PathBuf::from(c.as_os_str())
197 } else {
198 PathBuf::new()
199 };
200
201 for component in components {
202 match component {
203 Component::Prefix(..) => unreachable!(),
204 Component::RootDir => {
205 ret.push(component.as_os_str());
206 }
207 Component::CurDir => {}
208 Component::ParentDir => {
209 ret.pop();
210 }
211 Component::Normal(c) => {
212 ret.push(c);
213 }
214 }
215 }
216 ret
217}
diff --git a/crates/ra_arena/Cargo.toml b/crates/ra_arena/Cargo.toml
index d287dbb73..66c3738f4 100644
--- a/crates/ra_arena/Cargo.toml
+++ b/crates/ra_arena/Cargo.toml
@@ -3,6 +3,7 @@ edition = "2018"
3name = "ra_arena" 3name = "ra_arena"
4version = "0.1.0" 4version = "0.1.0"
5authors = ["rust-analyzer developers"] 5authors = ["rust-analyzer developers"]
6license = "MIT OR Apache-2.0"
6 7
7[lib] 8[lib]
8doctest = false 9doctest = false
diff --git a/crates/ra_arena/src/lib.rs b/crates/ra_arena/src/lib.rs
index 441fbb3cb..3169aa5b8 100644
--- a/crates/ra_arena/src/lib.rs
+++ b/crates/ra_arena/src/lib.rs
@@ -116,6 +116,9 @@ impl<T> Arena<T> {
116 ) -> impl Iterator<Item = (Idx<T>, &T)> + ExactSizeIterator + DoubleEndedIterator { 116 ) -> impl Iterator<Item = (Idx<T>, &T)> + ExactSizeIterator + DoubleEndedIterator {
117 self.data.iter().enumerate().map(|(idx, value)| (Idx::from_raw(RawId(idx as u32)), value)) 117 self.data.iter().enumerate().map(|(idx, value)| (Idx::from_raw(RawId(idx as u32)), value))
118 } 118 }
119 pub fn shrink_to_fit(&mut self) {
120 self.data.shrink_to_fit();
121 }
119} 122}
120 123
121impl<T> Default for Arena<T> { 124impl<T> Default for Arena<T> {
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml
index 3bcf58ba4..bd2905f08 100644
--- a/crates/ra_assists/Cargo.toml
+++ b/crates/ra_assists/Cargo.toml
@@ -3,6 +3,7 @@ edition = "2018"
3name = "ra_assists" 3name = "ra_assists"
4version = "0.1.0" 4version = "0.1.0"
5authors = ["rust-analyzer developers"] 5authors = ["rust-analyzer developers"]
6license = "MIT OR Apache-2.0"
6 7
7[lib] 8[lib]
8doctest = false 9doctest = false
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs
index c0a0226fb..cda2abfb9 100644
--- a/crates/ra_assists/src/assist_config.rs
+++ b/crates/ra_assists/src/assist_config.rs
@@ -4,9 +4,12 @@
4//! module, and we use to statically check that we only produce snippet 4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to. 5//! assists if we are allowed to.
6 6
7use crate::AssistKind;
8
7#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct AssistConfig { 10pub struct AssistConfig {
9 pub snippet_cap: Option<SnippetCap>, 11 pub snippet_cap: Option<SnippetCap>,
12 pub allowed: Option<Vec<AssistKind>>,
10} 13}
11 14
12impl AssistConfig { 15impl AssistConfig {
@@ -22,6 +25,6 @@ pub struct SnippetCap {
22 25
23impl Default for AssistConfig { 26impl Default for AssistConfig {
24 fn default() -> Self { 27 fn default() -> Self {
25 AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) } 28 AssistConfig { snippet_cap: Some(SnippetCap { _private: () }), allowed: None }
26 } 29 }
27} 30}
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs
index 5b1a4680b..3407df856 100644
--- a/crates/ra_assists/src/assist_context.rs
+++ b/crates/ra_assists/src/assist_context.rs
@@ -1,5 +1,7 @@
1//! See `AssistContext` 1//! See `AssistContext`
2 2
3use std::mem;
4
3use algo::find_covering_element; 5use algo::find_covering_element;
4use hir::Semantics; 6use hir::Semantics;
5use ra_db::{FileId, FileRange}; 7use ra_db::{FileId, FileRange};
@@ -17,7 +19,7 @@ use ra_text_edit::TextEditBuilder;
17 19
18use crate::{ 20use crate::{
19 assist_config::{AssistConfig, SnippetCap}, 21 assist_config::{AssistConfig, SnippetCap},
20 Assist, AssistId, GroupLabel, ResolvedAssist, 22 Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist,
21}; 23};
22 24
23/// `AssistContext` allows to apply an assist or check if it could be applied. 25/// `AssistContext` allows to apply an assist or check if it could be applied.
@@ -53,7 +55,6 @@ use crate::{
53pub(crate) struct AssistContext<'a> { 55pub(crate) struct AssistContext<'a> {
54 pub(crate) config: &'a AssistConfig, 56 pub(crate) config: &'a AssistConfig,
55 pub(crate) sema: Semantics<'a, RootDatabase>, 57 pub(crate) sema: Semantics<'a, RootDatabase>,
56 pub(crate) db: &'a RootDatabase,
57 pub(crate) frange: FileRange, 58 pub(crate) frange: FileRange,
58 source_file: SourceFile, 59 source_file: SourceFile,
59} 60}
@@ -65,8 +66,11 @@ impl<'a> AssistContext<'a> {
65 frange: FileRange, 66 frange: FileRange,
66 ) -> AssistContext<'a> { 67 ) -> AssistContext<'a> {
67 let source_file = sema.parse(frange.file_id); 68 let source_file = sema.parse(frange.file_id);
68 let db = sema.db; 69 AssistContext { config, sema, frange, source_file }
69 AssistContext { config, sema, db, frange, source_file } 70 }
71
72 pub(crate) fn db(&self) -> &RootDatabase {
73 self.sema.db
70 } 74 }
71 75
72 // NB, this ignores active selection. 76 // NB, this ignores active selection.
@@ -99,14 +103,26 @@ pub(crate) struct Assists {
99 resolve: bool, 103 resolve: bool,
100 file: FileId, 104 file: FileId,
101 buf: Vec<(Assist, Option<SourceChange>)>, 105 buf: Vec<(Assist, Option<SourceChange>)>,
106 allowed: Option<Vec<AssistKind>>,
102} 107}
103 108
104impl Assists { 109impl Assists {
105 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { 110 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists {
106 Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } 111 Assists {
112 resolve: true,
113 file: ctx.frange.file_id,
114 buf: Vec::new(),
115 allowed: ctx.config.allowed.clone(),
116 }
107 } 117 }
118
108 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { 119 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists {
109 Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } 120 Assists {
121 resolve: false,
122 file: ctx.frange.file_id,
123 buf: Vec::new(),
124 allowed: ctx.config.allowed.clone(),
125 }
110 } 126 }
111 127
112 pub(crate) fn finish_unresolved(self) -> Vec<Assist> { 128 pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
@@ -135,9 +151,13 @@ impl Assists {
135 target: TextRange, 151 target: TextRange,
136 f: impl FnOnce(&mut AssistBuilder), 152 f: impl FnOnce(&mut AssistBuilder),
137 ) -> Option<()> { 153 ) -> Option<()> {
154 if !self.is_allowed(&id) {
155 return None;
156 }
138 let label = Assist::new(id, label.into(), None, target); 157 let label = Assist::new(id, label.into(), None, target);
139 self.add_impl(label, f) 158 self.add_impl(label, f)
140 } 159 }
160
141 pub(crate) fn add_group( 161 pub(crate) fn add_group(
142 &mut self, 162 &mut self,
143 group: &GroupLabel, 163 group: &GroupLabel,
@@ -146,9 +166,14 @@ impl Assists {
146 target: TextRange, 166 target: TextRange,
147 f: impl FnOnce(&mut AssistBuilder), 167 f: impl FnOnce(&mut AssistBuilder),
148 ) -> Option<()> { 168 ) -> Option<()> {
169 if !self.is_allowed(&id) {
170 return None;
171 }
172
149 let label = Assist::new(id, label.into(), Some(group.clone()), target); 173 let label = Assist::new(id, label.into(), Some(group.clone()), target);
150 self.add_impl(label, f) 174 self.add_impl(label, f)
151 } 175 }
176
152 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { 177 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
153 let source_change = if self.resolve { 178 let source_change = if self.resolve {
154 let mut builder = AssistBuilder::new(self.file); 179 let mut builder = AssistBuilder::new(self.file);
@@ -166,17 +191,43 @@ impl Assists {
166 self.buf.sort_by_key(|(label, _edit)| label.target.len()); 191 self.buf.sort_by_key(|(label, _edit)| label.target.len());
167 self.buf 192 self.buf
168 } 193 }
194
195 fn is_allowed(&self, id: &AssistId) -> bool {
196 match &self.allowed {
197 Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
198 None => true,
199 }
200 }
169} 201}
170 202
171pub(crate) struct AssistBuilder { 203pub(crate) struct AssistBuilder {
172 edit: TextEditBuilder, 204 edit: TextEditBuilder,
173 file: FileId, 205 file_id: FileId,
174 is_snippet: bool, 206 is_snippet: bool,
207 change: SourceChange,
175} 208}
176 209
177impl AssistBuilder { 210impl AssistBuilder {
178 pub(crate) fn new(file: FileId) -> AssistBuilder { 211 pub(crate) fn new(file_id: FileId) -> AssistBuilder {
179 AssistBuilder { edit: TextEditBuilder::default(), file, is_snippet: false } 212 AssistBuilder {
213 edit: TextEditBuilder::default(),
214 file_id,
215 is_snippet: false,
216 change: SourceChange::default(),
217 }
218 }
219
220 pub(crate) fn edit_file(&mut self, file_id: FileId) {
221 self.file_id = file_id;
222 }
223
224 fn commit(&mut self) {
225 let edit = mem::take(&mut self.edit).finish();
226 if !edit.is_empty() {
227 let new_edit = SourceFileEdit { file_id: self.file_id, edit };
228 assert!(!self.change.source_file_edits.iter().any(|it| it.file_id == new_edit.file_id));
229 self.change.source_file_edits.push(new_edit);
230 }
180 } 231 }
181 232
182 /// Remove specified `range` of text. 233 /// Remove specified `range` of text.
@@ -231,12 +282,7 @@ impl AssistBuilder {
231 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { 282 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
232 let node = rewriter.rewrite_root().unwrap(); 283 let node = rewriter.rewrite_root().unwrap();
233 let new = rewriter.rewrite(&node); 284 let new = rewriter.rewrite(&node);
234 algo::diff(&node, &new).into_text_edit(&mut self.edit) 285 algo::diff(&node, &new).into_text_edit(&mut self.edit);
235 }
236
237 // FIXME: better API
238 pub(crate) fn set_file(&mut self, assist_file: FileId) {
239 self.file = assist_file;
240 } 286 }
241 287
242 // FIXME: kill this API 288 // FIXME: kill this API
@@ -245,13 +291,12 @@ impl AssistBuilder {
245 &mut self.edit 291 &mut self.edit
246 } 292 }
247 293
248 fn finish(self) -> SourceChange { 294 fn finish(mut self) -> SourceChange {
249 let edit = self.edit.finish(); 295 self.commit();
250 let source_file_edit = SourceFileEdit { file_id: self.file, edit }; 296 let mut change = mem::take(&mut self.change);
251 let mut res: SourceChange = source_file_edit.into();
252 if self.is_snippet { 297 if self.is_snippet {
253 res.is_snippet = true; 298 change.is_snippet = true;
254 } 299 }
255 res 300 change
256 } 301 }
257} 302}
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs
index 3079a02a2..01adb834c 100644
--- a/crates/ra_assists/src/ast_transform.rs
+++ b/crates/ra_assists/src/ast_transform.rs
@@ -2,7 +2,6 @@
2use rustc_hash::FxHashMap; 2use rustc_hash::FxHashMap;
3 3
4use hir::{HirDisplay, PathResolution, SemanticsScope}; 4use hir::{HirDisplay, PathResolution, SemanticsScope};
5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 5use ra_syntax::{
7 algo::SyntaxRewriter, 6 algo::SyntaxRewriter,
8 ast::{self, AstNode}, 7 ast::{self, AstNode},
@@ -32,14 +31,14 @@ impl<'a> AstTransform<'a> for NullTransformer {
32} 31}
33 32
34pub struct SubstituteTypeParams<'a> { 33pub struct SubstituteTypeParams<'a> {
35 source_scope: &'a SemanticsScope<'a, RootDatabase>, 34 source_scope: &'a SemanticsScope<'a>,
36 substs: FxHashMap<hir::TypeParam, ast::TypeRef>, 35 substs: FxHashMap<hir::TypeParam, ast::TypeRef>,
37 previous: Box<dyn AstTransform<'a> + 'a>, 36 previous: Box<dyn AstTransform<'a> + 'a>,
38} 37}
39 38
40impl<'a> SubstituteTypeParams<'a> { 39impl<'a> SubstituteTypeParams<'a> {
41 pub fn for_trait_impl( 40 pub fn for_trait_impl(
42 source_scope: &'a SemanticsScope<'a, RootDatabase>, 41 source_scope: &'a SemanticsScope<'a>,
43 // FIXME: there's implicit invariant that `trait_` and `source_scope` match... 42 // FIXME: there's implicit invariant that `trait_` and `source_scope` match...
44 trait_: hir::Trait, 43 trait_: hir::Trait,
45 impl_def: ast::ImplDef, 44 impl_def: ast::ImplDef,
@@ -106,6 +105,7 @@ impl<'a> SubstituteTypeParams<'a> {
106 _ => return None, 105 _ => return None,
107 }; 106 };
108 // FIXME: use `hir::Path::from_src` instead. 107 // FIXME: use `hir::Path::from_src` instead.
108 #[allow(deprecated)]
109 let path = hir::Path::from_ast(path)?; 109 let path = hir::Path::from_ast(path)?;
110 let resolution = self.source_scope.resolve_hir_path(&path)?; 110 let resolution = self.source_scope.resolve_hir_path(&path)?;
111 match resolution { 111 match resolution {
@@ -125,16 +125,13 @@ impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
125} 125}
126 126
127pub struct QualifyPaths<'a> { 127pub struct QualifyPaths<'a> {
128 target_scope: &'a SemanticsScope<'a, RootDatabase>, 128 target_scope: &'a SemanticsScope<'a>,
129 source_scope: &'a SemanticsScope<'a, RootDatabase>, 129 source_scope: &'a SemanticsScope<'a>,
130 previous: Box<dyn AstTransform<'a> + 'a>, 130 previous: Box<dyn AstTransform<'a> + 'a>,
131} 131}
132 132
133impl<'a> QualifyPaths<'a> { 133impl<'a> QualifyPaths<'a> {
134 pub fn new( 134 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
135 target_scope: &'a SemanticsScope<'a, RootDatabase>,
136 source_scope: &'a SemanticsScope<'a, RootDatabase>,
137 ) -> Self {
138 Self { target_scope, source_scope, previous: Box::new(NullTransformer) } 135 Self { target_scope, source_scope, previous: Box::new(NullTransformer) }
139 } 136 }
140 137
@@ -150,11 +147,12 @@ impl<'a> QualifyPaths<'a> {
150 return None; 147 return None;
151 } 148 }
152 // FIXME: use `hir::Path::from_src` instead. 149 // FIXME: use `hir::Path::from_src` instead.
150 #[allow(deprecated)]
153 let hir_path = hir::Path::from_ast(p.clone()); 151 let hir_path = hir::Path::from_ast(p.clone());
154 let resolution = self.source_scope.resolve_hir_path(&hir_path?)?; 152 let resolution = self.source_scope.resolve_hir_path(&hir_path?)?;
155 match resolution { 153 match resolution {
156 PathResolution::Def(def) => { 154 PathResolution::Def(def) => {
157 let found_path = from.find_use_path(self.source_scope.db, def)?; 155 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
158 let mut path = path_to_ast(found_path); 156 let mut path = path_to_ast(found_path);
159 157
160 let type_args = p 158 let type_args = p
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
index fa70c8496..acb07e36a 100644
--- a/crates/ra_assists/src/handlers/add_custom_impl.rs
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -8,7 +8,7 @@ use stdx::SepBy;
8 8
9use crate::{ 9use crate::{
10 assist_context::{AssistContext, Assists}, 10 assist_context::{AssistContext, Assists},
11 AssistId, 11 AssistId, AssistKind,
12}; 12};
13 13
14// Assist: add_custom_impl 14// Assist: add_custom_impl
@@ -52,7 +52,7 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); 52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
53 53
54 let target = attr.syntax().text_range(); 54 let target = attr.syntax().text_range();
55 acc.add(AssistId("add_custom_impl"), label, target, |builder| { 55 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
56 let new_attr_input = input 56 let new_attr_input = input
57 .syntax() 57 .syntax()
58 .descendants_with_tokens() 58 .descendants_with_tokens()
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
index ab20c6649..39a5321d1 100644
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 TextRange, 4 TextRange,
5}; 5};
6 6
7use crate::{AssistContext, AssistId, Assists}; 7use crate::{AssistContext, AssistId, AssistKind, Assists};
8 8
9// Assist: add_explicit_type 9// Assist: add_explicit_type
10// 10//
@@ -57,9 +57,9 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
57 return None; 57 return None;
58 } 58 }
59 59
60 let inferred_type = ty.display_source_code(ctx.db, module.into()).ok()?; 60 let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
61 acc.add( 61 acc.add(
62 AssistId("add_explicit_type"), 62 AssistId("add_explicit_type", AssistKind::RefactorRewrite),
63 format!("Insert explicit type `{}`", inferred_type), 63 format!("Insert explicit type `{}`", inferred_type),
64 pat_range, 64 pat_range,
65 |builder| match ascribed_ty { 65 |builder| match ascribed_ty {
@@ -195,7 +195,7 @@ struct Test<K, T = u8> {
195} 195}
196 196
197fn main() { 197fn main() {
198 let test<|> = Test { t: 23, k: 33 }; 198 let test<|> = Test { t: 23u8, k: 33 };
199}"#, 199}"#,
200 r#" 200 r#"
201struct Test<K, T = u8> { 201struct Test<K, T = u8> {
@@ -204,7 +204,7 @@ struct Test<K, T = u8> {
204} 204}
205 205
206fn main() { 206fn main() {
207 let test: Test<i32> = Test { t: 23, k: 33 }; 207 let test: Test<i32> = Test { t: 23u8, k: 33 };
208}"#, 208}"#,
209 ); 209 );
210 } 210 }
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs
deleted file mode 100644
index eceba7d0a..000000000
--- a/crates/ra_assists/src/handlers/add_impl.rs
+++ /dev/null
@@ -1,98 +0,0 @@
1use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner};
2use stdx::{format_to, SepBy};
3
4use crate::{AssistContext, AssistId, Assists};
5
6// Assist: add_impl
7//
8// Adds a new inherent impl for a type.
9//
10// ```
11// struct Ctx<T: Clone> {
12// data: T,<|>
13// }
14// ```
15// ->
16// ```
17// struct Ctx<T: Clone> {
18// data: T,
19// }
20//
21// impl<T: Clone> Ctx<T> {
22// $0
23// }
24// ```
25pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
27 let name = nominal.name()?;
28 let target = nominal.syntax().text_range();
29 acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| {
30 let type_params = nominal.type_param_list();
31 let start_offset = nominal.syntax().text_range().end();
32 let mut buf = String::new();
33 buf.push_str("\n\nimpl");
34 if let Some(type_params) = &type_params {
35 format_to!(buf, "{}", type_params.syntax());
36 }
37 buf.push_str(" ");
38 buf.push_str(name.text().as_str());
39 if let Some(type_params) = type_params {
40 let lifetime_params = type_params
41 .lifetime_params()
42 .filter_map(|it| it.lifetime_token())
43 .map(|it| it.text().clone());
44 let type_params =
45 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
46
47 let generic_params = lifetime_params.chain(type_params).sep_by(", ");
48 format_to!(buf, "<{}>", generic_params)
49 }
50 match ctx.config.snippet_cap {
51 Some(cap) => {
52 buf.push_str(" {\n $0\n}");
53 edit.insert_snippet(cap, start_offset, buf);
54 }
55 None => {
56 buf.push_str(" {\n}");
57 edit.insert(start_offset, buf);
58 }
59 }
60 })
61}
62
63#[cfg(test)]
64mod tests {
65 use crate::tests::{check_assist, check_assist_target};
66
67 use super::*;
68
69 #[test]
70 fn test_add_impl() {
71 check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n");
72 check_assist(
73 add_impl,
74 "struct Foo<T: Clone> {<|>}",
75 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
76 );
77 check_assist(
78 add_impl,
79 "struct Foo<'a, T: Foo<'a>> {<|>}",
80 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
81 );
82 }
83
84 #[test]
85 fn add_impl_target() {
86 check_assist_target(
87 add_impl,
88 "
89struct SomeThingIrrelevant;
90/// Has a lifetime parameter
91struct Foo<'a, T: Foo<'a>> {<|>}
92struct EvenMoreIrrelevant;
93",
94 "/// Has a lifetime parameter
95struct Foo<'a, T: Foo<'a>> {}",
96 );
97 }
98}
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
index abacd4065..f185e61e5 100644
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -12,7 +12,7 @@ use crate::{
12 assist_context::{AssistContext, Assists}, 12 assist_context::{AssistContext, Assists},
13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, 13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
14 utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor}, 14 utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor},
15 AssistId, 15 AssistId, AssistKind,
16}; 16};
17 17
18#[derive(PartialEq)] 18#[derive(PartialEq)]
@@ -128,9 +128,9 @@ fn add_missing_impl_members_inner(
128 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def) 128 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
129 .iter() 129 .iter()
130 .map(|i| match i { 130 .map(|i| match i {
131 hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), 131 hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db()).value),
132 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db).value), 132 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db()).value),
133 hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db).value), 133 hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db()).value),
134 }) 134 })
135 .filter(|t| def_name(&t).is_some()) 135 .filter(|t| def_name(&t).is_some())
136 .filter(|t| match t { 136 .filter(|t| match t {
@@ -147,7 +147,7 @@ fn add_missing_impl_members_inner(
147 } 147 }
148 148
149 let target = impl_def.syntax().text_range(); 149 let target = impl_def.syntax().text_range();
150 acc.add(AssistId(assist_id), label, target, |builder| { 150 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
151 let n_existing_items = impl_item_list.assoc_items().count(); 151 let n_existing_items = impl_item_list.assoc_items().count();
152 let source_scope = ctx.sema.scope_for_def(trait_); 152 let source_scope = ctx.sema.scope_for_def(trait_);
153 let target_scope = ctx.sema.scope(impl_item_list.syntax()); 153 let target_scope = ctx.sema.scope(impl_item_list.syntax());
@@ -158,6 +158,9 @@ fn add_missing_impl_members_inner(
158 .map(|it| ast_transform::apply(&*ast_transform, it)) 158 .map(|it| ast_transform::apply(&*ast_transform, it))
159 .map(|it| match it { 159 .map(|it| match it {
160 ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)), 160 ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)),
161 ast::AssocItem::TypeAliasDef(def) => {
162 ast::AssocItem::TypeAliasDef(def.remove_bounds())
163 }
161 _ => it, 164 _ => it,
162 }) 165 })
163 .map(|it| edit::remove_attrs_and_docs(&it)); 166 .map(|it| edit::remove_attrs_and_docs(&it));
@@ -684,4 +687,26 @@ impl Foo<T> for S<T> {
684}"#, 687}"#,
685 ) 688 )
686 } 689 }
690
691 #[test]
692 fn test_assoc_type_bounds_are_removed() {
693 check_assist(
694 add_missing_impl_members,
695 r#"
696trait Tr {
697 type Ty: Copy + 'static;
698}
699
700impl Tr for ()<|> {
701}"#,
702 r#"
703trait Tr {
704 type Ty: Copy + 'static;
705}
706
707impl Tr for () {
708 $0type Ty;
709}"#,
710 )
711 }
687} 712}
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs
index 26acf81f2..f7e1a7b05 100644
--- a/crates/ra_assists/src/handlers/add_turbo_fish.rs
+++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs
@@ -4,7 +4,7 @@ use test_utils::mark;
4 4
5use crate::{ 5use crate::{
6 assist_context::{AssistContext, Assists}, 6 assist_context::{AssistContext, Assists},
7 AssistId, 7 AssistId, AssistKind,
8}; 8};
9 9
10// Assist: add_turbo_fish 10// Assist: add_turbo_fish
@@ -45,12 +45,15 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<(
45 mark::hit!(add_turbo_fish_non_generic); 45 mark::hit!(add_turbo_fish_non_generic);
46 return None; 46 return None;
47 } 47 }
48 acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| { 48 acc.add(
49 match ctx.config.snippet_cap { 49 AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
50 "Add `::<>`",
51 ident.text_range(),
52 |builder| match ctx.config.snippet_cap {
50 Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"), 53 Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
51 None => builder.insert(ident.text_range().end(), "::<_>"), 54 None => builder.insert(ident.text_range().end(), "::<_>"),
52 } 55 },
53 }) 56 )
54} 57}
55 58
56#[cfg(test)] 59#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs
index 233e8fb8e..de701f8b8 100644
--- a/crates/ra_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ra_assists/src/handlers/apply_demorgan.rs
@@ -1,6 +1,6 @@
1use ra_syntax::ast::{self, AstNode}; 1use ra_syntax::ast::{self, AstNode};
2 2
3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists}; 3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
4 4
5// Assist: apply_demorgan 5// Assist: apply_demorgan
6// 6//
@@ -39,11 +39,16 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
39 let rhs_range = rhs.syntax().text_range(); 39 let rhs_range = rhs.syntax().text_range();
40 let not_rhs = invert_boolean_expression(rhs); 40 let not_rhs = invert_boolean_expression(rhs);
41 41
42 acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| { 42 acc.add(
43 edit.replace(op_range, opposite_op); 43 AssistId("apply_demorgan", AssistKind::RefactorRewrite),
44 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); 44 "Apply De Morgan's law",
45 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); 45 op_range,
46 }) 46 |edit| {
47 edit.replace(op_range, opposite_op);
48 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
49 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
50 },
51 )
47} 52}
48 53
49// Return the opposite text for a given logical operator, if it makes sense 54// Return the opposite text for a given logical operator, if it makes sense
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index edf96d50e..947be3b9b 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -5,7 +5,7 @@ use hir::{
5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, 5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
6 Type, 6 Type,
7}; 7};
8use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase}; 8use ra_ide_db::{imports_locator, RootDatabase};
9use ra_prof::profile; 9use ra_prof::profile;
10use ra_syntax::{ 10use ra_syntax::{
11 ast::{self, AstNode}, 11 ast::{self, AstNode},
@@ -13,7 +13,9 @@ use ra_syntax::{
13}; 13};
14use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
15 15
16use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel}; 16use crate::{
17 utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel,
18};
17 19
18// Assist: auto_import 20// Assist: auto_import
19// 21//
@@ -35,8 +37,8 @@ use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, Group
35// # pub mod std { pub mod collections { pub struct HashMap { } } } 37// # pub mod std { pub mod collections { pub struct HashMap { } } }
36// ``` 38// ```
37pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 39pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let auto_import_assets = AutoImportAssets::new(&ctx)?; 40 let auto_import_assets = AutoImportAssets::new(ctx)?;
39 let proposed_imports = auto_import_assets.search_for_imports(ctx.db); 41 let proposed_imports = auto_import_assets.search_for_imports(ctx);
40 if proposed_imports.is_empty() { 42 if proposed_imports.is_empty() {
41 return None; 43 return None;
42 } 44 }
@@ -46,7 +48,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
46 for import in proposed_imports { 48 for import in proposed_imports {
47 acc.add_group( 49 acc.add_group(
48 &group, 50 &group,
49 AssistId("auto_import"), 51 AssistId("auto_import", AssistKind::QuickFix),
50 format!("Import `{}`", &import), 52 format!("Import `{}`", &import),
51 range, 53 range,
52 |builder| { 54 |builder| {
@@ -127,11 +129,11 @@ impl AutoImportAssets {
127 GroupLabel(name) 129 GroupLabel(name)
128 } 130 }
129 131
130 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { 132 fn search_for_imports(&self, ctx: &AssistContext) -> BTreeSet<ModPath> {
131 let _p = profile("auto_import::search_for_imports"); 133 let _p = profile("auto_import::search_for_imports");
134 let db = ctx.db();
132 let current_crate = self.module_with_name_to_import.krate(); 135 let current_crate = self.module_with_name_to_import.krate();
133 ImportsLocator::new(db) 136 imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query())
134 .find_imports(&self.get_search_query())
135 .into_iter() 137 .into_iter()
136 .filter_map(|candidate| match &self.import_candidate { 138 .filter_map(|candidate| match &self.import_candidate {
137 ImportCandidate::TraitAssocItem(assoc_item_type, _) => { 139 ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
@@ -488,16 +490,17 @@ mod tests {
488 check_assist( 490 check_assist(
489 auto_import, 491 auto_import,
490 r" 492 r"
491 //- /lib.rs crate:crate_with_macro 493//- /lib.rs crate:crate_with_macro
492 #[macro_export] 494#[macro_export]
493 macro_rules! foo { 495macro_rules! foo {
494 () => () 496 () => ()
495 } 497}
496 498
497 //- /main.rs crate:main deps:crate_with_macro 499//- /main.rs crate:main deps:crate_with_macro
498 fn main() { 500fn main() {
499 foo<|> 501 foo<|>
500 }", 502}
503",
501 r"use crate_with_macro::foo; 504 r"use crate_with_macro::foo;
502 505
503fn main() { 506fn main() {
@@ -810,6 +813,146 @@ fn main() {
810 } 813 }
811 814
812 #[test] 815 #[test]
816 fn trait_method_cross_crate() {
817 check_assist(
818 auto_import,
819 r"
820 //- /main.rs crate:main deps:dep
821 fn main() {
822 let test_struct = dep::test_mod::TestStruct {};
823 test_struct.test_meth<|>od()
824 }
825 //- /dep.rs crate:dep
826 pub mod test_mod {
827 pub trait TestTrait {
828 fn test_method(&self);
829 }
830 pub struct TestStruct {}
831 impl TestTrait for TestStruct {
832 fn test_method(&self) {}
833 }
834 }
835 ",
836 r"
837 use dep::test_mod::TestTrait;
838
839 fn main() {
840 let test_struct = dep::test_mod::TestStruct {};
841 test_struct.test_method()
842 }
843 ",
844 );
845 }
846
847 #[test]
848 fn assoc_fn_cross_crate() {
849 check_assist(
850 auto_import,
851 r"
852 //- /main.rs crate:main deps:dep
853 fn main() {
854 dep::test_mod::TestStruct::test_func<|>tion
855 }
856 //- /dep.rs crate:dep
857 pub mod test_mod {
858 pub trait TestTrait {
859 fn test_function();
860 }
861 pub struct TestStruct {}
862 impl TestTrait for TestStruct {
863 fn test_function() {}
864 }
865 }
866 ",
867 r"
868 use dep::test_mod::TestTrait;
869
870 fn main() {
871 dep::test_mod::TestStruct::test_function
872 }
873 ",
874 );
875 }
876
877 #[test]
878 fn assoc_const_cross_crate() {
879 check_assist(
880 auto_import,
881 r"
882 //- /main.rs crate:main deps:dep
883 fn main() {
884 dep::test_mod::TestStruct::CONST<|>
885 }
886 //- /dep.rs crate:dep
887 pub mod test_mod {
888 pub trait TestTrait {
889 const CONST: bool;
890 }
891 pub struct TestStruct {}
892 impl TestTrait for TestStruct {
893 const CONST: bool = true;
894 }
895 }
896 ",
897 r"
898 use dep::test_mod::TestTrait;
899
900 fn main() {
901 dep::test_mod::TestStruct::CONST
902 }
903 ",
904 );
905 }
906
907 #[test]
908 fn assoc_fn_as_method_cross_crate() {
909 check_assist_not_applicable(
910 auto_import,
911 r"
912 //- /main.rs crate:main deps:dep
913 fn main() {
914 let test_struct = dep::test_mod::TestStruct {};
915 test_struct.test_func<|>tion()
916 }
917 //- /dep.rs crate:dep
918 pub mod test_mod {
919 pub trait TestTrait {
920 fn test_function();
921 }
922 pub struct TestStruct {}
923 impl TestTrait for TestStruct {
924 fn test_function() {}
925 }
926 }
927 ",
928 );
929 }
930
931 #[test]
932 fn private_trait_cross_crate() {
933 check_assist_not_applicable(
934 auto_import,
935 r"
936 //- /main.rs crate:main deps:dep
937 fn main() {
938 let test_struct = dep::test_mod::TestStruct {};
939 test_struct.test_meth<|>od()
940 }
941 //- /dep.rs crate:dep
942 pub mod test_mod {
943 trait TestTrait {
944 fn test_method(&self);
945 }
946 pub struct TestStruct {}
947 impl TestTrait for TestStruct {
948 fn test_method(&self) {}
949 }
950 }
951 ",
952 );
953 }
954
955 #[test]
813 fn not_applicable_for_imported_trait_for_method() { 956 fn not_applicable_for_imported_trait_for_method() {
814 check_assist_not_applicable( 957 check_assist_not_applicable(
815 auto_import, 958 auto_import,
@@ -841,4 +984,106 @@ fn main() {
841 ", 984 ",
842 ) 985 )
843 } 986 }
987
988 #[test]
989 fn dep_import() {
990 check_assist(
991 auto_import,
992 r"
993//- /lib.rs crate:dep
994pub struct Struct;
995
996//- /main.rs crate:main deps:dep
997fn main() {
998 Struct<|>
999}
1000",
1001 r"use dep::Struct;
1002
1003fn main() {
1004 Struct
1005}
1006",
1007 );
1008 }
1009
1010 #[test]
1011 fn whole_segment() {
1012 // Tests that only imports whose last segment matches the identifier get suggested.
1013 check_assist(
1014 auto_import,
1015 r"
1016//- /lib.rs crate:dep
1017pub mod fmt {
1018 pub trait Display {}
1019}
1020
1021pub fn panic_fmt() {}
1022
1023//- /main.rs crate:main deps:dep
1024struct S;
1025
1026impl f<|>mt::Display for S {}
1027",
1028 r"use dep::fmt;
1029
1030struct S;
1031
1032impl fmt::Display for S {}
1033",
1034 );
1035 }
1036
1037 #[test]
1038 fn macro_generated() {
1039 // Tests that macro-generated items are suggested from external crates.
1040 check_assist(
1041 auto_import,
1042 r"
1043//- /lib.rs crate:dep
1044macro_rules! mac {
1045 () => {
1046 pub struct Cheese;
1047 };
1048}
1049
1050mac!();
1051
1052//- /main.rs crate:main deps:dep
1053fn main() {
1054 Cheese<|>;
1055}
1056",
1057 r"use dep::Cheese;
1058
1059fn main() {
1060 Cheese;
1061}
1062",
1063 );
1064 }
1065
1066 #[test]
1067 fn casing() {
1068 // Tests that differently cased names don't interfere and we only suggest the matching one.
1069 check_assist(
1070 auto_import,
1071 r"
1072//- /lib.rs crate:dep
1073pub struct FMT;
1074pub struct fmt;
1075
1076//- /main.rs crate:main deps:dep
1077fn main() {
1078 FMT<|>;
1079}
1080",
1081 r"use dep::FMT;
1082
1083fn main() {
1084 FMT;
1085}
1086",
1087 );
1088 }
844} 1089}
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
index c6baa0a57..24e5f6963 100644
--- a/crates/ra_assists/src/handlers/change_return_type_to_result.rs
+++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
@@ -3,7 +3,8 @@ use ra_syntax::{
3 AstNode, SyntaxNode, 3 AstNode, SyntaxNode,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
7use test_utils::mark;
7 8
8// Assist: change_return_type_to_result 9// Assist: change_return_type_to_result
9// 10//
@@ -22,14 +23,19 @@ pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContex
22 let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?; 23 let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?;
23 24
24 let type_ref = &ret_type.type_ref()?; 25 let type_ref = &ret_type.type_ref()?;
25 if type_ref.syntax().text().to_string().starts_with("Result<") { 26 let ret_type_str = type_ref.syntax().text().to_string();
26 return None; 27 let first_part_ret_type = ret_type_str.splitn(2, '<').next();
28 if let Some(ret_type_first_part) = first_part_ret_type {
29 if ret_type_first_part.ends_with("Result") {
30 mark::hit!(change_return_type_to_result_simple_return_type_already_result);
31 return None;
32 }
27 } 33 }
28 34
29 let block_expr = &fn_def.body()?; 35 let block_expr = &fn_def.body()?;
30 36
31 acc.add( 37 acc.add(
32 AssistId("change_return_type_to_result"), 38 AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
33 "Change return type to Result", 39 "Change return type to Result",
34 type_ref.syntax().text_range(), 40 type_ref.syntax().text_range(),
35 |builder| { 41 |builder| {
@@ -297,6 +303,29 @@ mod tests {
297 } 303 }
298 304
299 #[test] 305 #[test]
306 fn change_return_type_to_result_simple_return_type_already_result_std() {
307 check_assist_not_applicable(
308 change_return_type_to_result,
309 r#"fn foo() -> std::result::Result<i32<|>, String> {
310 let test = "test";
311 return 42i32;
312 }"#,
313 );
314 }
315
316 #[test]
317 fn change_return_type_to_result_simple_return_type_already_result() {
318 mark::check!(change_return_type_to_result_simple_return_type_already_result);
319 check_assist_not_applicable(
320 change_return_type_to_result,
321 r#"fn foo() -> Result<i32<|>, String> {
322 let test = "test";
323 return 42i32;
324 }"#,
325 );
326 }
327
328 #[test]
300 fn change_return_type_to_result_simple_with_cursor() { 329 fn change_return_type_to_result_simple_with_cursor() {
301 check_assist( 330 check_assist(
302 change_return_type_to_result, 331 change_return_type_to_result,
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index c21d75be0..4343b423c 100644
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -2,14 +2,13 @@ use ra_syntax::{
2 ast::{self, NameOwner, VisibilityOwner}, 2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode, 3 AstNode,
4 SyntaxKind::{ 4 SyntaxKind::{
5 ATTR, COMMENT, CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY, 5 CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STATIC_DEF, STRUCT_DEF, TRAIT_DEF, VISIBILITY,
6 WHITESPACE,
7 }, 6 },
8 SyntaxNode, TextSize, T, 7 T,
9}; 8};
10use test_utils::mark; 9use test_utils::mark;
11 10
12use crate::{AssistContext, AssistId, Assists}; 11use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
13 12
14// Assist: change_visibility 13// Assist: change_visibility
15// 14//
@@ -30,14 +29,16 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio
30} 29}
31 30
32fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 31fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { 32 let item_keyword = ctx.token_at_offset().find(|leaf| {
34 T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, 33 matches!(
35 _ => false, 34 leaf.kind(),
35 T![const] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait]
36 )
36 }); 37 });
37 38
38 let (offset, target) = if let Some(keyword) = item_keyword { 39 let (offset, target) = if let Some(keyword) = item_keyword {
39 let parent = keyword.parent(); 40 let parent = keyword.parent();
40 let def_kws = vec![CONST_DEF, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; 41 let def_kws = vec![CONST_DEF, STATIC_DEF, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
41 // Parent is not a definition, can't add visibility 42 // Parent is not a definition, can't add visibility
42 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { 43 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
43 return None; 44 return None;
@@ -66,27 +67,21 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
66 return None; 67 return None;
67 }; 68 };
68 69
69 acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| { 70 acc.add(
70 edit.insert(offset, "pub(crate) "); 71 AssistId("change_visibility", AssistKind::RefactorRewrite),
71 }) 72 "Change visibility to pub(crate)",
72} 73 target,
73 74 |edit| {
74fn vis_offset(node: &SyntaxNode) -> TextSize { 75 edit.insert(offset, "pub(crate) ");
75 node.children_with_tokens() 76 },
76 .skip_while(|it| match it.kind() { 77 )
77 WHITESPACE | COMMENT | ATTR => true,
78 _ => false,
79 })
80 .next()
81 .map(|it| it.text_range().start())
82 .unwrap_or_else(|| node.text_range().start())
83} 78}
84 79
85fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { 80fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
86 if vis.syntax().text() == "pub" { 81 if vis.syntax().text() == "pub" {
87 let target = vis.syntax().text_range(); 82 let target = vis.syntax().text_range();
88 return acc.add( 83 return acc.add(
89 AssistId("change_visibility"), 84 AssistId("change_visibility", AssistKind::RefactorRewrite),
90 "Change Visibility to pub(crate)", 85 "Change Visibility to pub(crate)",
91 target, 86 target,
92 |edit| { 87 |edit| {
@@ -97,7 +92,7 @@ fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
97 if vis.syntax().text() == "pub(crate)" { 92 if vis.syntax().text() == "pub(crate)" {
98 let target = vis.syntax().text_range(); 93 let target = vis.syntax().text_range();
99 return acc.add( 94 return acc.add(
100 AssistId("change_visibility"), 95 AssistId("change_visibility", AssistKind::RefactorRewrite),
101 "Change visibility to pub", 96 "Change visibility to pub",
102 target, 97 target,
103 |edit| { 98 |edit| {
@@ -162,6 +157,11 @@ mod tests {
162 } 157 }
163 158
164 #[test] 159 #[test]
160 fn change_visibility_static() {
161 check_assist(change_visibility, "<|>static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
162 }
163
164 #[test]
165 fn change_visibility_handles_comment_attrs() { 165 fn change_visibility_handles_comment_attrs() {
166 check_assist( 166 check_assist(
167 change_visibility, 167 change_visibility,
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index 4cc75a7ce..330459f3c 100644
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -15,7 +15,7 @@ use ra_syntax::{
15use crate::{ 15use crate::{
16 assist_context::{AssistContext, Assists}, 16 assist_context::{AssistContext, Assists},
17 utils::invert_boolean_expression, 17 utils::invert_boolean_expression,
18 AssistId, 18 AssistId, AssistKind,
19}; 19};
20 20
21// Assist: convert_to_guarded_return 21// Assist: convert_to_guarded_return
@@ -99,86 +99,92 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
99 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; 99 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
100 100
101 let target = if_expr.syntax().text_range(); 101 let target = if_expr.syntax().text_range();
102 acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| { 102 acc.add(
103 let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); 103 AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite),
104 let new_block = match if_let_pat { 104 "Convert to guarded return",
105 None => { 105 target,
106 // If. 106 |edit| {
107 let new_expr = { 107 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
108 let then_branch = 108 let new_block = match if_let_pat {
109 make::block_expr(once(make::expr_stmt(early_expression).into()), None); 109 None => {
110 let cond = invert_boolean_expression(cond_expr); 110 // If.
111 make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level) 111 let new_expr = {
112 }; 112 let then_branch =
113 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) 113 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
114 } 114 let cond = invert_boolean_expression(cond_expr);
115 Some((path, bound_ident)) => { 115 make::expr_if(make::condition(cond, None), then_branch)
116 // If-let. 116 .indent(if_indent_level)
117 let match_expr = {
118 let happy_arm = {
119 let pat = make::tuple_struct_pat(
120 path,
121 once(make::bind_pat(make::name("it")).into()),
122 );
123 let expr = {
124 let name_ref = make::name_ref("it");
125 let segment = make::path_segment(name_ref);
126 let path = make::path_unqualified(segment);
127 make::expr_path(path)
128 };
129 make::match_arm(once(pat.into()), expr)
130 }; 117 };
118 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
119 }
120 Some((path, bound_ident)) => {
121 // If-let.
122 let match_expr = {
123 let happy_arm = {
124 let pat = make::tuple_struct_pat(
125 path,
126 once(make::bind_pat(make::name("it")).into()),
127 );
128 let expr = {
129 let name_ref = make::name_ref("it");
130 let segment = make::path_segment(name_ref);
131 let path = make::path_unqualified(segment);
132 make::expr_path(path)
133 };
134 make::match_arm(once(pat.into()), expr)
135 };
131 136
132 let sad_arm = make::match_arm( 137 let sad_arm = make::match_arm(
133 // FIXME: would be cool to use `None` or `Err(_)` if appropriate 138 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
134 once(make::placeholder_pat().into()), 139 once(make::placeholder_pat().into()),
135 early_expression, 140 early_expression,
136 ); 141 );
137 142
138 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) 143 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
139 }; 144 };
140 145
141 let let_stmt = make::let_stmt( 146 let let_stmt = make::let_stmt(
142 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), 147 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
143 Some(match_expr), 148 Some(match_expr),
149 );
150 let let_stmt = let_stmt.indent(if_indent_level);
151 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
152 }
153 };
154 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
155
156 fn replace(
157 new_expr: &SyntaxNode,
158 then_block: &ast::BlockExpr,
159 parent_block: &ast::BlockExpr,
160 if_expr: &ast::IfExpr,
161 ) -> SyntaxNode {
162 let then_block_items = then_block.dedent(IndentLevel(1));
163 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
164 let end_of_then =
165 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
166 end_of_then.prev_sibling_or_token().unwrap()
167 } else {
168 end_of_then
169 };
170 let mut then_statements = new_expr.children_with_tokens().chain(
171 then_block_items
172 .syntax()
173 .children_with_tokens()
174 .skip(1)
175 .take_while(|i| *i != end_of_then),
144 ); 176 );
145 let let_stmt = let_stmt.indent(if_indent_level); 177 replace_children(
146 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) 178 &parent_block.syntax(),
179 RangeInclusive::new(
180 if_expr.clone().syntax().clone().into(),
181 if_expr.syntax().clone().into(),
182 ),
183 &mut then_statements,
184 )
147 } 185 }
148 }; 186 },
149 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); 187 )
150
151 fn replace(
152 new_expr: &SyntaxNode,
153 then_block: &ast::BlockExpr,
154 parent_block: &ast::BlockExpr,
155 if_expr: &ast::IfExpr,
156 ) -> SyntaxNode {
157 let then_block_items = then_block.dedent(IndentLevel::from(1));
158 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
159 let end_of_then =
160 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
161 end_of_then.prev_sibling_or_token().unwrap()
162 } else {
163 end_of_then
164 };
165 let mut then_statements = new_expr.children_with_tokens().chain(
166 then_block_items
167 .syntax()
168 .children_with_tokens()
169 .skip(1)
170 .take_while(|i| *i != end_of_then),
171 );
172 replace_children(
173 &parent_block.syntax(),
174 RangeInclusive::new(
175 if_expr.clone().syntax().clone().into(),
176 if_expr.syntax().clone().into(),
177 ),
178 &mut then_statements,
179 )
180 }
181 })
182} 188}
183 189
184#[cfg(test)] 190#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
new file mode 100644
index 000000000..2b8e273b3
--- /dev/null
+++ b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -0,0 +1,321 @@
1use hir::{EnumVariant, Module, ModuleDef, Name};
2use ra_db::FileId;
3use ra_fmt::leading_indent;
4use ra_ide_db::{defs::Definition, search::Reference, RootDatabase};
5use ra_syntax::{
6 algo::find_node_at_offset,
7 ast::{self, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
8 SourceFile, SyntaxNode, TextRange, TextSize,
9};
10use rustc_hash::FxHashSet;
11
12use crate::{
13 assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId,
14 AssistKind, Assists,
15};
16
17// Assist: extract_struct_from_enum_variant
18//
19// Extracts a struct from enum variant.
20//
21// ```
22// enum A { <|>One(u32, u32) }
23// ```
24// ->
25// ```
26// struct One(pub u32, pub u32);
27//
28// enum A { One(One) }
29// ```
30pub(crate) fn extract_struct_from_enum_variant(
31 acc: &mut Assists,
32 ctx: &AssistContext,
33) -> Option<()> {
34 let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
35 let field_list = match variant.kind() {
36 ast::StructKind::Tuple(field_list) => field_list,
37 _ => return None,
38 };
39 let variant_name = variant.name()?.to_string();
40 let variant_hir = ctx.sema.to_def(&variant)?;
41 if existing_struct_def(ctx.db(), &variant_name, &variant_hir) {
42 return None;
43 }
44 let enum_ast = variant.parent_enum();
45 let visibility = enum_ast.visibility();
46 let enum_hir = ctx.sema.to_def(&enum_ast)?;
47 let variant_hir_name = variant_hir.name(ctx.db());
48 let enum_module_def = ModuleDef::from(enum_hir);
49 let current_module = enum_hir.module(ctx.db());
50 let target = variant.syntax().text_range();
51 acc.add(
52 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
53 "Extract struct from enum variant",
54 target,
55 |builder| {
56 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
57 let res = definition.find_usages(&ctx.sema, None);
58 let start_offset = variant.parent_enum().syntax().text_range().start();
59 let mut visited_modules_set = FxHashSet::default();
60 visited_modules_set.insert(current_module);
61 for reference in res {
62 let source_file = ctx.sema.parse(reference.file_range.file_id);
63 update_reference(
64 ctx,
65 builder,
66 reference,
67 &source_file,
68 &enum_module_def,
69 &variant_hir_name,
70 &mut visited_modules_set,
71 );
72 }
73 extract_struct_def(
74 builder,
75 enum_ast.syntax(),
76 &variant_name,
77 &field_list.to_string(),
78 start_offset,
79 ctx.frange.file_id,
80 &visibility,
81 );
82 let list_range = field_list.syntax().text_range();
83 update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
84 },
85 )
86}
87
88fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool {
89 variant
90 .parent_enum(db)
91 .module(db)
92 .scope(db, None)
93 .into_iter()
94 .any(|(name, _)| name.to_string() == variant_name.to_string())
95}
96
97fn insert_import(
98 ctx: &AssistContext,
99 builder: &mut AssistBuilder,
100 path: &ast::PathExpr,
101 module: &Module,
102 enum_module_def: &ModuleDef,
103 variant_hir_name: &Name,
104) -> Option<()> {
105 let db = ctx.db();
106 let mod_path = module.find_use_path(db, enum_module_def.clone());
107 if let Some(mut mod_path) = mod_path {
108 mod_path.segments.pop();
109 mod_path.segments.push(variant_hir_name.clone());
110 insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder());
111 }
112 Some(())
113}
114
115fn extract_struct_def(
116 builder: &mut AssistBuilder,
117 enum_ast: &SyntaxNode,
118 variant_name: &str,
119 variant_list: &str,
120 start_offset: TextSize,
121 file_id: FileId,
122 visibility: &Option<ast::Visibility>,
123) -> Option<()> {
124 let visibility_string = if let Some(visibility) = visibility {
125 format!("{} ", visibility.to_string())
126 } else {
127 "".to_string()
128 };
129 let indent = if let Some(indent) = leading_indent(enum_ast) {
130 indent.to_string()
131 } else {
132 "".to_string()
133 };
134 let struct_def = format!(
135 r#"{}struct {}{};
136
137{}"#,
138 visibility_string,
139 variant_name,
140 list_with_visibility(variant_list),
141 indent
142 );
143 builder.edit_file(file_id);
144 builder.insert(start_offset, struct_def);
145 Some(())
146}
147
148fn update_variant(
149 builder: &mut AssistBuilder,
150 variant_name: &str,
151 file_id: FileId,
152 list_range: TextRange,
153) -> Option<()> {
154 let inside_variant_range = TextRange::new(
155 list_range.start().checked_add(TextSize::from(1))?,
156 list_range.end().checked_sub(TextSize::from(1))?,
157 );
158 builder.edit_file(file_id);
159 builder.replace(inside_variant_range, variant_name);
160 Some(())
161}
162
163fn update_reference(
164 ctx: &AssistContext,
165 builder: &mut AssistBuilder,
166 reference: Reference,
167 source_file: &SourceFile,
168 enum_module_def: &ModuleDef,
169 variant_hir_name: &Name,
170 visited_modules_set: &mut FxHashSet<Module>,
171) -> Option<()> {
172 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
173 source_file.syntax(),
174 reference.file_range.range.start(),
175 )?;
176 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
177 let list = call.arg_list()?;
178 let segment = path_expr.path()?.segment()?;
179 let module = ctx.sema.scope(&path_expr.syntax()).module()?;
180 let list_range = list.syntax().text_range();
181 let inside_list_range = TextRange::new(
182 list_range.start().checked_add(TextSize::from(1))?,
183 list_range.end().checked_sub(TextSize::from(1))?,
184 );
185 builder.edit_file(reference.file_range.file_id);
186 if !visited_modules_set.contains(&module) {
187 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
188 .is_some()
189 {
190 visited_modules_set.insert(module);
191 }
192 }
193 builder.replace(inside_list_range, format!("{}{}", segment, list));
194 Some(())
195}
196
197fn list_with_visibility(list: &str) -> String {
198 list.split(',')
199 .map(|part| {
200 let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 };
201 let mut mod_part = part.trim().to_string();
202 mod_part.insert_str(index, "pub ");
203 mod_part
204 })
205 .collect::<Vec<String>>()
206 .join(", ")
207}
208
209#[cfg(test)]
210mod tests {
211
212 use crate::{
213 tests::{check_assist, check_assist_not_applicable},
214 utils::FamousDefs,
215 };
216
217 use super::*;
218
219 #[test]
220 fn test_extract_struct_several_fields() {
221 check_assist(
222 extract_struct_from_enum_variant,
223 "enum A { <|>One(u32, u32) }",
224 r#"struct One(pub u32, pub u32);
225
226enum A { One(One) }"#,
227 );
228 }
229
230 #[test]
231 fn test_extract_struct_one_field() {
232 check_assist(
233 extract_struct_from_enum_variant,
234 "enum A { <|>One(u32) }",
235 r#"struct One(pub u32);
236
237enum A { One(One) }"#,
238 );
239 }
240
241 #[test]
242 fn test_extract_struct_pub_visibility() {
243 check_assist(
244 extract_struct_from_enum_variant,
245 "pub enum A { <|>One(u32, u32) }",
246 r#"pub struct One(pub u32, pub u32);
247
248pub enum A { One(One) }"#,
249 );
250 }
251
252 #[test]
253 fn test_extract_struct_with_complex_imports() {
254 check_assist(
255 extract_struct_from_enum_variant,
256 r#"mod my_mod {
257 fn another_fn() {
258 let m = my_other_mod::MyEnum::MyField(1, 1);
259 }
260
261 pub mod my_other_mod {
262 fn another_fn() {
263 let m = MyEnum::MyField(1, 1);
264 }
265
266 pub enum MyEnum {
267 <|>MyField(u8, u8),
268 }
269 }
270}
271
272fn another_fn() {
273 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
274}"#,
275 r#"use my_mod::my_other_mod::MyField;
276
277mod my_mod {
278 use my_other_mod::MyField;
279
280 fn another_fn() {
281 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
282 }
283
284 pub mod my_other_mod {
285 fn another_fn() {
286 let m = MyEnum::MyField(MyField(1, 1));
287 }
288
289 pub struct MyField(pub u8, pub u8);
290
291 pub enum MyEnum {
292 MyField(MyField),
293 }
294 }
295}
296
297fn another_fn() {
298 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
299}"#,
300 );
301 }
302
303 fn check_not_applicable(ra_fixture: &str) {
304 let fixture =
305 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
306 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
307 }
308
309 #[test]
310 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
311 check_not_applicable("enum A { <|>One }");
312 }
313
314 #[test]
315 fn test_extract_enum_not_applicable_if_struct_exists() {
316 check_not_applicable(
317 r#"struct One;
318 enum A { <|>One(u8) }"#,
319 );
320 }
321}
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/extract_variable.rs
index 31d6539f7..481baf1a4 100644
--- a/crates/ra_assists/src/handlers/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/extract_variable.rs
@@ -9,9 +9,9 @@ use ra_syntax::{
9use stdx::format_to; 9use stdx::format_to;
10use test_utils::mark; 10use test_utils::mark;
11 11
12use crate::{AssistContext, AssistId, Assists}; 12use crate::{AssistContext, AssistId, AssistKind, Assists};
13 13
14// Assist: introduce_variable 14// Assist: extract_variable
15// 15//
16// Extracts subexpression into a variable. 16// Extracts subexpression into a variable.
17// 17//
@@ -27,13 +27,13 @@ use crate::{AssistContext, AssistId, Assists};
27// var_name * 4; 27// var_name * 4;
28// } 28// }
29// ``` 29// ```
30pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 30pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 if ctx.frange.range.is_empty() { 31 if ctx.frange.range.is_empty() {
32 return None; 32 return None;
33 } 33 }
34 let node = ctx.covering_element(); 34 let node = ctx.covering_element();
35 if node.kind() == COMMENT { 35 if node.kind() == COMMENT {
36 mark::hit!(introduce_var_in_comment_is_not_applicable); 36 mark::hit!(extract_var_in_comment_is_not_applicable);
37 return None; 37 return None;
38 } 38 }
39 let expr = node.ancestors().find_map(valid_target_expr)?; 39 let expr = node.ancestors().find_map(valid_target_expr)?;
@@ -43,65 +43,85 @@ pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Opti
43 return None; 43 return None;
44 } 44 }
45 let target = expr.syntax().text_range(); 45 let target = expr.syntax().text_range();
46 acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { 46 acc.add(
47 let mut buf = String::new(); 47 AssistId("extract_variable", AssistKind::RefactorExtract),
48 48 "Extract into variable",
49 if wrap_in_block { 49 target,
50 buf.push_str("{ let var_name = "); 50 move |edit| {
51 } else { 51 let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) {
52 buf.push_str("let var_name = "); 52 Some(field) => field.name_ref(),
53 }; 53 None => None,
54 format_to!(buf, "{}", expr.syntax()); 54 };
55 55
56 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); 56 let mut buf = String::new();
57 let is_full_stmt = if let Some(expr_stmt) = &full_stmt { 57
58 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) 58 let var_name = match &field_shorthand {
59 } else { 59 Some(it) => it.to_string(),
60 false 60 None => "var_name".to_string(),
61 }; 61 };
62 if is_full_stmt { 62 let expr_range = match &field_shorthand {
63 mark::hit!(test_introduce_var_expr_stmt); 63 Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()),
64 if full_stmt.unwrap().semicolon_token().is_none() { 64 None => expr.syntax().text_range(),
65 buf.push_str(";"); 65 };
66
67 if wrap_in_block {
68 format_to!(buf, "{{ let {} = ", var_name);
69 } else {
70 format_to!(buf, "let {} = ", var_name);
71 };
72 format_to!(buf, "{}", expr.syntax());
73
74 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
75 let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
76 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
77 } else {
78 false
79 };
80 if is_full_stmt {
81 mark::hit!(test_extract_var_expr_stmt);
82 if full_stmt.unwrap().semicolon_token().is_none() {
83 buf.push_str(";");
84 }
85 match ctx.config.snippet_cap {
86 Some(cap) => {
87 let snip = buf
88 .replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
89 edit.replace_snippet(cap, expr_range, snip)
90 }
91 None => edit.replace(expr_range, buf),
92 }
93 return;
94 }
95
96 buf.push_str(";");
97
98 // We want to maintain the indent level,
99 // but we do not want to duplicate possible
100 // extra newlines in the indent block
101 let text = indent.text();
102 if text.starts_with('\n') {
103 buf.push_str("\n");
104 buf.push_str(text.trim_start_matches('\n'));
105 } else {
106 buf.push_str(text);
66 } 107 }
67 let offset = expr.syntax().text_range(); 108
109 edit.replace(expr_range, var_name.clone());
110 let offset = anchor_stmt.text_range().start();
68 match ctx.config.snippet_cap { 111 match ctx.config.snippet_cap {
69 Some(cap) => { 112 Some(cap) => {
70 let snip = buf.replace("let var_name", "let $0var_name"); 113 let snip =
71 edit.replace_snippet(cap, offset, snip) 114 buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
115 edit.insert_snippet(cap, offset, snip)
72 } 116 }
73 None => edit.replace(offset, buf), 117 None => edit.insert(offset, buf),
74 } 118 }
75 return;
76 }
77 119
78 buf.push_str(";"); 120 if wrap_in_block {
79 121 edit.insert(anchor_stmt.text_range().end(), " }");
80 // We want to maintain the indent level,
81 // but we do not want to duplicate possible
82 // extra newlines in the indent block
83 let text = indent.text();
84 if text.starts_with('\n') {
85 buf.push_str("\n");
86 buf.push_str(text.trim_start_matches('\n'));
87 } else {
88 buf.push_str(text);
89 }
90
91 edit.replace(expr.syntax().text_range(), "var_name".to_string());
92 let offset = anchor_stmt.text_range().start();
93 match ctx.config.snippet_cap {
94 Some(cap) => {
95 let snip = buf.replace("let var_name", "let $0var_name");
96 edit.insert_snippet(cap, offset, snip)
97 } 122 }
98 None => edit.insert(offset, buf), 123 },
99 } 124 )
100
101 if wrap_in_block {
102 edit.insert(anchor_stmt.text_range().end(), " }");
103 }
104 })
105} 125}
106 126
107/// Check whether the node is a valid expression which can be extracted to a variable. 127/// Check whether the node is a valid expression which can be extracted to a variable.
@@ -118,7 +138,7 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
118 } 138 }
119} 139}
120 140
121/// Returns the syntax node which will follow the freshly introduced var 141/// Returns the syntax node which will follow the freshly extractd var
122/// and a boolean indicating whether we have to wrap it within a { } block 142/// and a boolean indicating whether we have to wrap it within a { } block
123/// to produce correct code. 143/// to produce correct code.
124/// It can be a statement, the last in a block expression or a wanna be block 144/// It can be a statement, the last in a block expression or a wanna be block
@@ -127,7 +147,7 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
127 expr.syntax().ancestors().find_map(|node| { 147 expr.syntax().ancestors().find_map(|node| {
128 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { 148 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) {
129 if expr.syntax() == &node { 149 if expr.syntax() == &node {
130 mark::hit!(test_introduce_var_last_expr); 150 mark::hit!(test_extract_var_last_expr);
131 return Some((node, false)); 151 return Some((node, false));
132 } 152 }
133 } 153 }
@@ -155,9 +175,9 @@ mod tests {
155 use super::*; 175 use super::*;
156 176
157 #[test] 177 #[test]
158 fn test_introduce_var_simple() { 178 fn test_extract_var_simple() {
159 check_assist( 179 check_assist(
160 introduce_variable, 180 extract_variable,
161 r#" 181 r#"
162fn foo() { 182fn foo() {
163 foo(<|>1 + 1<|>); 183 foo(<|>1 + 1<|>);
@@ -171,16 +191,16 @@ fn foo() {
171 } 191 }
172 192
173 #[test] 193 #[test]
174 fn introduce_var_in_comment_is_not_applicable() { 194 fn extract_var_in_comment_is_not_applicable() {
175 mark::check!(introduce_var_in_comment_is_not_applicable); 195 mark::check!(extract_var_in_comment_is_not_applicable);
176 check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); 196 check_assist_not_applicable(extract_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }");
177 } 197 }
178 198
179 #[test] 199 #[test]
180 fn test_introduce_var_expr_stmt() { 200 fn test_extract_var_expr_stmt() {
181 mark::check!(test_introduce_var_expr_stmt); 201 mark::check!(test_extract_var_expr_stmt);
182 check_assist( 202 check_assist(
183 introduce_variable, 203 extract_variable,
184 r#" 204 r#"
185fn foo() { 205fn foo() {
186 <|>1 + 1<|>; 206 <|>1 + 1<|>;
@@ -191,7 +211,7 @@ fn foo() {
191}"#, 211}"#,
192 ); 212 );
193 check_assist( 213 check_assist(
194 introduce_variable, 214 extract_variable,
195 " 215 "
196fn foo() { 216fn foo() {
197 <|>{ let x = 0; x }<|> 217 <|>{ let x = 0; x }<|>
@@ -206,9 +226,9 @@ fn foo() {
206 } 226 }
207 227
208 #[test] 228 #[test]
209 fn test_introduce_var_part_of_expr_stmt() { 229 fn test_extract_var_part_of_expr_stmt() {
210 check_assist( 230 check_assist(
211 introduce_variable, 231 extract_variable,
212 " 232 "
213fn foo() { 233fn foo() {
214 <|>1<|> + 1; 234 <|>1<|> + 1;
@@ -222,38 +242,42 @@ fn foo() {
222 } 242 }
223 243
224 #[test] 244 #[test]
225 fn test_introduce_var_last_expr() { 245 fn test_extract_var_last_expr() {
226 mark::check!(test_introduce_var_last_expr); 246 mark::check!(test_extract_var_last_expr);
227 check_assist( 247 check_assist(
228 introduce_variable, 248 extract_variable,
229 " 249 r#"
230fn foo() { 250fn foo() {
231 bar(<|>1 + 1<|>) 251 bar(<|>1 + 1<|>)
232}", 252}
233 " 253"#,
254 r#"
234fn foo() { 255fn foo() {
235 let $0var_name = 1 + 1; 256 let $0var_name = 1 + 1;
236 bar(var_name) 257 bar(var_name)
237}", 258}
259"#,
238 ); 260 );
239 check_assist( 261 check_assist(
240 introduce_variable, 262 extract_variable,
241 " 263 r#"
242fn foo() { 264fn foo() {
243 <|>bar(1 + 1)<|> 265 <|>bar(1 + 1)<|>
244}", 266}
245 " 267"#,
268 r#"
246fn foo() { 269fn foo() {
247 let $0var_name = bar(1 + 1); 270 let $0var_name = bar(1 + 1);
248 var_name 271 var_name
249}", 272}
273"#,
250 ) 274 )
251 } 275 }
252 276
253 #[test] 277 #[test]
254 fn test_introduce_var_in_match_arm_no_block() { 278 fn test_extract_var_in_match_arm_no_block() {
255 check_assist( 279 check_assist(
256 introduce_variable, 280 extract_variable,
257 " 281 "
258fn main() { 282fn main() {
259 let x = true; 283 let x = true;
@@ -276,9 +300,9 @@ fn main() {
276 } 300 }
277 301
278 #[test] 302 #[test]
279 fn test_introduce_var_in_match_arm_with_block() { 303 fn test_extract_var_in_match_arm_with_block() {
280 check_assist( 304 check_assist(
281 introduce_variable, 305 extract_variable,
282 " 306 "
283fn main() { 307fn main() {
284 let x = true; 308 let x = true;
@@ -308,9 +332,9 @@ fn main() {
308 } 332 }
309 333
310 #[test] 334 #[test]
311 fn test_introduce_var_in_closure_no_block() { 335 fn test_extract_var_in_closure_no_block() {
312 check_assist( 336 check_assist(
313 introduce_variable, 337 extract_variable,
314 " 338 "
315fn main() { 339fn main() {
316 let lambda = |x: u32| <|>x * 2<|>; 340 let lambda = |x: u32| <|>x * 2<|>;
@@ -325,9 +349,9 @@ fn main() {
325 } 349 }
326 350
327 #[test] 351 #[test]
328 fn test_introduce_var_in_closure_with_block() { 352 fn test_extract_var_in_closure_with_block() {
329 check_assist( 353 check_assist(
330 introduce_variable, 354 extract_variable,
331 " 355 "
332fn main() { 356fn main() {
333 let lambda = |x: u32| { <|>x * 2<|> }; 357 let lambda = |x: u32| { <|>x * 2<|> };
@@ -342,9 +366,9 @@ fn main() {
342 } 366 }
343 367
344 #[test] 368 #[test]
345 fn test_introduce_var_path_simple() { 369 fn test_extract_var_path_simple() {
346 check_assist( 370 check_assist(
347 introduce_variable, 371 extract_variable,
348 " 372 "
349fn main() { 373fn main() {
350 let o = <|>Some(true)<|>; 374 let o = <|>Some(true)<|>;
@@ -360,9 +384,9 @@ fn main() {
360 } 384 }
361 385
362 #[test] 386 #[test]
363 fn test_introduce_var_path_method() { 387 fn test_extract_var_path_method() {
364 check_assist( 388 check_assist(
365 introduce_variable, 389 extract_variable,
366 " 390 "
367fn main() { 391fn main() {
368 let v = <|>bar.foo()<|>; 392 let v = <|>bar.foo()<|>;
@@ -378,9 +402,9 @@ fn main() {
378 } 402 }