@@ -10,23 +10,28 @@ 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
- language_server:: { edits, reference:: FindVariableReferences } ,
16
+ language_server:: { edits, lsp_range_to_src_span , reference:: FindVariableReferences } ,
17
17
line_numbers:: LineNumbers ,
18
18
parse:: { extra:: ModuleExtra , lexer:: str_to_keyword} ,
19
+ paths:: ProjectPaths ,
19
20
strings:: to_snake_case,
20
21
type_:: {
21
- self , FieldMap , ModuleValueConstructor , Type , TypeVar , TypedCallArg , ValueConstructor ,
22
+ self , Error as TypeError , FieldMap , ModuleValueConstructor , Type , TypeVar , TypedCallArg ,
23
+ ValueConstructor ,
22
24
error:: { ModuleSuggestion , VariableDeclaration , VariableOrigin } ,
23
25
printer:: Printer ,
24
26
} ,
25
27
} ;
26
28
use ecow:: { EcoString , eco_format} ;
27
29
use im:: HashMap ;
28
30
use itertools:: Itertools ;
29
- use lsp_types:: { CodeAction , CodeActionKind , CodeActionParams , Position , Range , TextEdit , Url } ;
31
+ use lsp_types:: {
32
+ CodeAction , CodeActionKind , CodeActionParams , CreateFile , CreateFileOptions ,
33
+ DocumentChangeOperation , DocumentChanges , Position , Range , ResourceOp , TextEdit , Url ,
34
+ } ;
30
35
use vec1:: { Vec1 , vec1} ;
31
36
32
37
use super :: {
@@ -45,7 +50,7 @@ pub struct CodeActionBuilder {
45
50
}
46
51
47
52
impl CodeActionBuilder {
48
- pub fn new ( title : & str ) -> Self {
53
+ pub fn new ( title : impl ToString ) -> Self {
49
54
Self {
50
55
action : CodeAction {
51
56
title : title. to_string ( ) ,
@@ -75,6 +80,15 @@ impl CodeActionBuilder {
75
80
self
76
81
}
77
82
83
+ pub fn document_changes ( mut self , changes : DocumentChanges ) -> Self {
84
+ let mut edit = self . action . edit . take ( ) . unwrap_or_default ( ) ;
85
+
86
+ edit. document_changes = Some ( changes) ;
87
+
88
+ self . action . edit = Some ( edit) ;
89
+ self
90
+ }
91
+
78
92
pub fn preferred ( mut self , is_preferred : bool ) -> Self {
79
93
self . action . is_preferred = Some ( is_preferred) ;
80
94
self
@@ -1623,7 +1637,7 @@ impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> {
1623
1637
}
1624
1638
self . edit_import ( ) ;
1625
1639
let mut action = Vec :: with_capacity ( 1 ) ;
1626
- CodeActionBuilder :: new ( & format ! (
1640
+ CodeActionBuilder :: new ( format ! (
1627
1641
"Unqualify {}.{}" ,
1628
1642
self . qualified_constructor. used_name, self . qualified_constructor. constructor
1629
1643
) )
@@ -2013,7 +2027,7 @@ impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> {
2013
2027
constructor,
2014
2028
..
2015
2029
} = self . unqualified_constructor ;
2016
- CodeActionBuilder :: new ( & format ! (
2030
+ CodeActionBuilder :: new ( format ! (
2017
2031
"Qualify {} as {}.{}" ,
2018
2032
constructor. used_name( ) ,
2019
2033
module_name,
@@ -7297,7 +7311,7 @@ impl<'a> FixBinaryOperation<'a> {
7297
7311
self . edits . replace ( location, replacement. name ( ) . into ( ) ) ;
7298
7312
7299
7313
let mut action = Vec :: with_capacity ( 1 ) ;
7300
- CodeActionBuilder :: new ( & format ! ( "Use `{}`" , replacement. name( ) ) )
7314
+ CodeActionBuilder :: new ( format ! ( "Use `{}`" , replacement. name( ) ) )
7301
7315
. kind ( CodeActionKind :: REFACTOR_REWRITE )
7302
7316
. changes ( self . params . text_document . uri . clone ( ) , self . edits . edits )
7303
7317
. preferred ( true )
@@ -7380,7 +7394,7 @@ impl<'a> FixTruncatedBitArraySegment<'a> {
7380
7394
. replace ( truncation. value_location , replacement. clone ( ) ) ;
7381
7395
7382
7396
let mut action = Vec :: with_capacity ( 1 ) ;
7383
- CodeActionBuilder :: new ( & format ! ( "Replace with `{replacement}`" ) )
7397
+ CodeActionBuilder :: new ( format ! ( "Replace with `{replacement}`" ) )
7384
7398
. kind ( CodeActionKind :: REFACTOR_REWRITE )
7385
7399
. changes ( self . params . text_document . uri . clone ( ) , self . edits . edits )
7386
7400
. preferred ( true )
@@ -9103,3 +9117,105 @@ impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> {
9103
9117
}
9104
9118
}
9105
9119
}
9120
+
9121
+ /// Code action to create unknown modules when an import is added for a
9122
+ /// module that doesn't exist.
9123
+ ///
9124
+ /// For example, if `import wobble/woo` is added to `src/wiggle.gleam`,
9125
+ /// then a code action to create `src/wobble/woo.gleam` will be presented
9126
+ /// when triggered over `import wobble/woo`.
9127
+ pub struct CreateUnknownModule < ' a > {
9128
+ module : & ' a Module ,
9129
+ lines : & ' a LineNumbers ,
9130
+ params : & ' a CodeActionParams ,
9131
+ paths : & ' a ProjectPaths ,
9132
+ error : & ' a Option < Error > ,
9133
+ }
9134
+
9135
+ impl < ' a > CreateUnknownModule < ' a > {
9136
+ pub fn new (
9137
+ module : & ' a Module ,
9138
+ lines : & ' a LineNumbers ,
9139
+ params : & ' a CodeActionParams ,
9140
+ paths : & ' a ProjectPaths ,
9141
+ error : & ' a Option < Error > ,
9142
+ ) -> Self {
9143
+ Self {
9144
+ module,
9145
+ lines,
9146
+ params,
9147
+ paths,
9148
+ error,
9149
+ }
9150
+ }
9151
+
9152
+ pub fn code_actions ( self ) -> Vec < CodeAction > {
9153
+ struct UnknownModule < ' a > {
9154
+ name : & ' a EcoString ,
9155
+ location : & ' a SrcSpan ,
9156
+ }
9157
+
9158
+ let mut actions = vec ! [ ] ;
9159
+
9160
+ // This code action can be derived from UnknownModule type errors. If those
9161
+ // errors don't exist, there are no actions to add.
9162
+ let Some ( Error :: Type { errors, .. } ) = self . error else {
9163
+ return actions;
9164
+ } ;
9165
+
9166
+ // Span of the code action so we can check if it exists within the span of
9167
+ // the UnkownModule type error
9168
+ let code_action_span = lsp_range_to_src_span ( self . params . range , self . lines ) ;
9169
+
9170
+ // Origin directory we can build the new module path from
9171
+ let origin_directory = match self . module . origin {
9172
+ Origin :: Src => self . paths . src_directory ( ) ,
9173
+ Origin :: Test => self . paths . test_directory ( ) ,
9174
+ Origin :: Dev => self . paths . dev_directory ( ) ,
9175
+ } ;
9176
+
9177
+ // Filter for any UnknownModule type errors
9178
+ let unknown_modules = errors. iter ( ) . filter_map ( |error| {
9179
+ if let TypeError :: UnknownModule { name, location, .. } = error {
9180
+ return Some ( UnknownModule { name, location } ) ;
9181
+ }
9182
+
9183
+ None
9184
+ } ) ;
9185
+
9186
+ // For each UnknownModule type error, check to see if it contains the
9187
+ // incoming code action & if so, add a document change to create the module
9188
+ for unknown_module in unknown_modules {
9189
+ // Was this code action triggered within the UnknownModule error?
9190
+ let error_contains_action = unknown_module. location . contains ( code_action_span. start )
9191
+ && unknown_module. location . contains ( code_action_span. end ) ;
9192
+
9193
+ if !error_contains_action {
9194
+ continue ;
9195
+ }
9196
+
9197
+ let uri = url_from_path ( & format ! ( "{origin_directory}/{}.gleam" , unknown_module. name) )
9198
+ . expect ( "origin directory is absolute" ) ;
9199
+
9200
+ CodeActionBuilder :: new ( format ! (
9201
+ "Create {}/{}.gleam" ,
9202
+ self . module. origin. folder_name( ) ,
9203
+ unknown_module. name
9204
+ ) )
9205
+ . kind ( CodeActionKind :: QUICKFIX )
9206
+ . document_changes ( DocumentChanges :: Operations ( vec ! [
9207
+ DocumentChangeOperation :: Op ( ResourceOp :: Create ( CreateFile {
9208
+ uri,
9209
+ options: Some ( CreateFileOptions {
9210
+ overwrite: Some ( false ) ,
9211
+ ignore_if_exists: Some ( true ) ,
9212
+ } ) ,
9213
+ annotation_id: None ,
9214
+ } ) ) ,
9215
+ ] ) )
9216
+ . push_to ( & mut actions) ;
9217
+ }
9218
+
9219
+ actions
9220
+ }
9221
+ }
0 commit comments