@@ -10,24 +10,29 @@ use crate::{
10
10
TypedExpr , TypedModuleConstant , TypedPattern , TypedPipelineAssignment ,
11
11
TypedRecordConstructor , TypedStatement , TypedUse , visit:: Visit as _,
12
12
} ,
13
- build:: { Located , Module } ,
13
+ build:: { Located , Module , Origin } ,
14
14
config:: PackageConfig ,
15
15
exhaustiveness:: CompiledCase ,
16
16
io:: { BeamCompiler , CommandExecutor , FileSystemReader , FileSystemWriter } ,
17
- language_server:: { edits, reference:: FindVariableReferences } ,
17
+ language_server:: { edits, lsp_range_to_src_span , reference:: FindVariableReferences } ,
18
18
line_numbers:: LineNumbers ,
19
19
parse:: { extra:: ModuleExtra , lexer:: str_to_keyword} ,
20
+ paths:: ProjectPaths ,
20
21
strings:: to_snake_case,
21
22
type_:: {
22
- self , FieldMap , ModuleValueConstructor , Type , TypeVar , TypedCallArg , ValueConstructor ,
23
+ self , Error as TypeError , FieldMap , ModuleValueConstructor , Type , TypeVar , TypedCallArg ,
24
+ ValueConstructor ,
23
25
error:: { ModuleSuggestion , VariableDeclaration , VariableOrigin } ,
24
26
printer:: Printer ,
25
27
} ,
26
28
} ;
27
29
use ecow:: { EcoString , eco_format} ;
28
30
use im:: HashMap ;
29
31
use itertools:: Itertools ;
30
- use lsp_types:: { CodeAction , CodeActionKind , CodeActionParams , Position , Range , TextEdit , Url } ;
32
+ use lsp_types:: {
33
+ CodeAction , CodeActionKind , CodeActionParams , CreateFile , CreateFileOptions ,
34
+ DocumentChangeOperation , DocumentChanges , Position , Range , ResourceOp , TextEdit , Url ,
35
+ } ;
31
36
use vec1:: { Vec1 , vec1} ;
32
37
33
38
use super :: {
@@ -46,7 +51,7 @@ pub struct CodeActionBuilder {
46
51
}
47
52
48
53
impl CodeActionBuilder {
49
- pub fn new ( title : & str ) -> Self {
54
+ pub fn new ( title : impl ToString ) -> Self {
50
55
Self {
51
56
action : CodeAction {
52
57
title : title. to_string ( ) ,
@@ -76,6 +81,15 @@ impl CodeActionBuilder {
76
81
self
77
82
}
78
83
84
+ pub fn document_changes ( mut self , changes : DocumentChanges ) -> Self {
85
+ let mut edit = self . action . edit . take ( ) . unwrap_or_default ( ) ;
86
+
87
+ edit. document_changes = Some ( changes) ;
88
+
89
+ self . action . edit = Some ( edit) ;
90
+ self
91
+ }
92
+
79
93
pub fn preferred ( mut self , is_preferred : bool ) -> Self {
80
94
self . action . is_preferred = Some ( is_preferred) ;
81
95
self
@@ -1571,7 +1585,7 @@ impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> {
1571
1585
}
1572
1586
self . edit_import ( ) ;
1573
1587
let mut action = Vec :: with_capacity ( 1 ) ;
1574
- CodeActionBuilder :: new ( & format ! (
1588
+ CodeActionBuilder :: new ( format ! (
1575
1589
"Unqualify {}.{}" ,
1576
1590
self . qualified_constructor. used_name, self . qualified_constructor. constructor
1577
1591
) )
@@ -1959,7 +1973,7 @@ impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> {
1959
1973
constructor,
1960
1974
..
1961
1975
} = self . unqualified_constructor ;
1962
- CodeActionBuilder :: new ( & format ! (
1976
+ CodeActionBuilder :: new ( format ! (
1963
1977
"Qualify {} as {}.{}" ,
1964
1978
constructor. used_name( ) ,
1965
1979
module_name,
@@ -7188,7 +7202,7 @@ impl<'a> FixBinaryOperation<'a> {
7188
7202
self . edits . replace ( location, replacement. name ( ) . into ( ) ) ;
7189
7203
7190
7204
let mut action = Vec :: with_capacity ( 1 ) ;
7191
- CodeActionBuilder :: new ( & format ! ( "Use `{}`" , replacement. name( ) ) )
7205
+ CodeActionBuilder :: new ( format ! ( "Use `{}`" , replacement. name( ) ) )
7192
7206
. kind ( CodeActionKind :: REFACTOR_REWRITE )
7193
7207
. changes ( self . params . text_document . uri . clone ( ) , self . edits . edits )
7194
7208
. preferred ( true )
@@ -7271,7 +7285,7 @@ impl<'a> FixTruncatedBitArraySegment<'a> {
7271
7285
. replace ( truncation. value_location , replacement. clone ( ) ) ;
7272
7286
7273
7287
let mut action = Vec :: with_capacity ( 1 ) ;
7274
- CodeActionBuilder :: new ( & format ! ( "Replace with `{replacement}`" ) )
7288
+ CodeActionBuilder :: new ( format ! ( "Replace with `{replacement}`" ) )
7275
7289
. kind ( CodeActionKind :: REFACTOR_REWRITE )
7276
7290
. changes ( self . params . text_document . uri . clone ( ) , self . edits . edits )
7277
7291
. preferred ( true )
@@ -8196,3 +8210,105 @@ impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableBranches<'ast> {
8196
8210
ast:: visit:: visit_typed_expr_case ( self , location, type_, subjects, clauses, compiled_case) ;
8197
8211
}
8198
8212
}
8213
+
8214
+ /// Code action to create unknown modules when an import is added for a
8215
+ /// module that doesn't exist.
8216
+ ///
8217
+ /// For example, if `import wobble/woo` is added to `src/wiggle.gleam`,
8218
+ /// then a code action to create `src/wobble/woo.gleam` will be presented
8219
+ /// when triggered over `import wobble/woo`.
8220
+ pub struct CreateUnknownModule < ' a > {
8221
+ module : & ' a Module ,
8222
+ lines : & ' a LineNumbers ,
8223
+ params : & ' a CodeActionParams ,
8224
+ paths : & ' a ProjectPaths ,
8225
+ error : & ' a Option < Error > ,
8226
+ }
8227
+
8228
+ impl < ' a > CreateUnknownModule < ' a > {
8229
+ pub fn new (
8230
+ module : & ' a Module ,
8231
+ lines : & ' a LineNumbers ,
8232
+ params : & ' a CodeActionParams ,
8233
+ paths : & ' a ProjectPaths ,
8234
+ error : & ' a Option < Error > ,
8235
+ ) -> Self {
8236
+ Self {
8237
+ module,
8238
+ lines,
8239
+ params,
8240
+ paths,
8241
+ error,
8242
+ }
8243
+ }
8244
+
8245
+ pub fn code_actions ( self ) -> Vec < CodeAction > {
8246
+ struct UnknownModule < ' a > {
8247
+ name : & ' a EcoString ,
8248
+ location : & ' a SrcSpan ,
8249
+ }
8250
+
8251
+ let mut actions = vec ! [ ] ;
8252
+
8253
+ // This code action can be derived from UnknownModule type errors. If those
8254
+ // errors don't exist, there are no actions to add.
8255
+ let Some ( Error :: Type { errors, .. } ) = self . error else {
8256
+ return actions;
8257
+ } ;
8258
+
8259
+ // Span of the code action so we can check if it exists within the span of
8260
+ // the UnkownModule type error
8261
+ let code_action_span = lsp_range_to_src_span ( self . params . range , self . lines ) ;
8262
+
8263
+ // Origin directory we can build the new module path from
8264
+ let origin_directory = match self . module . origin {
8265
+ Origin :: Src => self . paths . src_directory ( ) ,
8266
+ Origin :: Test => self . paths . test_directory ( ) ,
8267
+ Origin :: Dev => self . paths . dev_directory ( ) ,
8268
+ } ;
8269
+
8270
+ // Filter for any UnknownModule type errors
8271
+ let unknown_modules = errors. iter ( ) . filter_map ( |error| {
8272
+ if let TypeError :: UnknownModule { name, location, .. } = error {
8273
+ return Some ( UnknownModule { name, location } ) ;
8274
+ }
8275
+
8276
+ None
8277
+ } ) ;
8278
+
8279
+ // For each UnknownModule type error, check to see if it contains the
8280
+ // incoming code action & if so, add a document change to create the module
8281
+ for unknown_module in unknown_modules {
8282
+ // Was this code action triggered within the UnknownModule error?
8283
+ let error_contains_action = unknown_module. location . contains ( code_action_span. start )
8284
+ && unknown_module. location . contains ( code_action_span. end ) ;
8285
+
8286
+ if !error_contains_action {
8287
+ continue ;
8288
+ }
8289
+
8290
+ let uri = url_from_path ( & format ! ( "{origin_directory}/{}.gleam" , unknown_module. name) )
8291
+ . expect ( "origin directory is absolute" ) ;
8292
+
8293
+ CodeActionBuilder :: new ( format ! (
8294
+ "Create {}/{}.gleam" ,
8295
+ self . module. origin. folder_name( ) ,
8296
+ unknown_module. name
8297
+ ) )
8298
+ . kind ( CodeActionKind :: QUICKFIX )
8299
+ . document_changes ( DocumentChanges :: Operations ( vec ! [
8300
+ DocumentChangeOperation :: Op ( ResourceOp :: Create ( CreateFile {
8301
+ uri,
8302
+ options: Some ( CreateFileOptions {
8303
+ overwrite: Some ( false ) ,
8304
+ ignore_if_exists: Some ( true ) ,
8305
+ } ) ,
8306
+ annotation_id: None ,
8307
+ } ) ) ,
8308
+ ] ) )
8309
+ . push_to ( & mut actions) ;
8310
+ }
8311
+
8312
+ actions
8313
+ }
8314
+ }
0 commit comments