@@ -14,20 +14,24 @@ use crate::{
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
20
strings:: to_snake_case,
21
21
type_:: {
22
- self , FieldMap , ModuleValueConstructor , Type , TypeVar , TypedCallArg , ValueConstructor ,
22
+ self , Error as TypeError , FieldMap , ModuleValueConstructor , Type , TypeVar , TypedCallArg ,
23
+ ValueConstructor ,
23
24
error:: { ModuleSuggestion , VariableDeclaration , VariableOrigin } ,
24
25
printer:: Printer ,
25
26
} ,
26
27
} ;
27
28
use ecow:: { EcoString , eco_format} ;
28
29
use im:: HashMap ;
29
30
use itertools:: Itertools ;
30
- 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
+ } ;
31
35
use vec1:: { Vec1 , vec1} ;
32
36
33
37
use super :: {
@@ -46,7 +50,7 @@ pub struct CodeActionBuilder {
46
50
}
47
51
48
52
impl CodeActionBuilder {
49
- pub fn new ( title : & str ) -> Self {
53
+ pub fn new ( title : impl ToString ) -> Self {
50
54
Self {
51
55
action : CodeAction {
52
56
title : title. to_string ( ) ,
@@ -76,6 +80,15 @@ impl CodeActionBuilder {
76
80
self
77
81
}
78
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
+
79
92
pub fn preferred ( mut self , is_preferred : bool ) -> Self {
80
93
self . action . is_preferred = Some ( is_preferred) ;
81
94
self
@@ -7895,3 +7908,104 @@ fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> {
7895
7908
expression => Some ( expression) ,
7896
7909
}
7897
7910
}
7911
+
7912
+ /// Code action to create an unknown child module
7913
+ ///
7914
+ /// ```gleam
7915
+ /// // foo.gleam
7916
+ /// // Diagnostic: Unknown module
7917
+ /// import foo/bar
7918
+ /// ```
7919
+ ///
7920
+ /// Would create:
7921
+ ///
7922
+ /// ```gleam
7923
+ /// // foo/bar.gleam
7924
+ /// ```
7925
+ ///
7926
+ pub struct CreateUnknownChildModule < ' a > {
7927
+ module : & ' a Module ,
7928
+ params : & ' a CodeActionParams ,
7929
+ error : & ' a Option < Error > ,
7930
+ code_action_span : SrcSpan ,
7931
+ }
7932
+
7933
+ impl < ' a > CreateUnknownChildModule < ' a > {
7934
+ pub fn new (
7935
+ module : & ' a Module ,
7936
+ lines : & ' a LineNumbers ,
7937
+ params : & ' a CodeActionParams ,
7938
+ error : & ' a Option < Error > ,
7939
+ ) -> Self {
7940
+ Self {
7941
+ module,
7942
+ params,
7943
+ error,
7944
+ code_action_span : lsp_range_to_src_span ( params. range , lines) ,
7945
+ }
7946
+ }
7947
+
7948
+ pub fn code_actions ( self ) -> Vec < CodeAction > {
7949
+ struct UnknownModule < ' a > {
7950
+ name : & ' a EcoString ,
7951
+ location : & ' a SrcSpan ,
7952
+ }
7953
+
7954
+ let mut actions = vec ! [ ] ;
7955
+
7956
+ let Some ( Error :: Type { errors, .. } ) = self . error else {
7957
+ return actions;
7958
+ } ;
7959
+
7960
+ let unknown_modules = errors. iter ( ) . filter_map ( |error| {
7961
+ if let TypeError :: UnknownModule { name, location, .. } = error {
7962
+ return Some ( UnknownModule { name, location } ) ;
7963
+ }
7964
+
7965
+ None
7966
+ } ) ;
7967
+
7968
+ for unknown_module in unknown_modules {
7969
+ if !self . code_action_span . intersects ( * unknown_module. location ) {
7970
+ continue ;
7971
+ }
7972
+
7973
+ let Some ( relative_name) = unknown_module
7974
+ . name
7975
+ . strip_prefix ( self . module . name . as_str ( ) )
7976
+ . map ( |s| s. trim_start_matches ( '/' ) )
7977
+ else {
7978
+ continue ;
7979
+ } ;
7980
+
7981
+ // Must be a direct child of the current module (doesn't contain / separator)
7982
+ if relative_name. contains ( '/' ) {
7983
+ continue ;
7984
+ }
7985
+
7986
+ let mut uri = self . params . text_document . uri . clone ( ) ;
7987
+
7988
+ uri. set_path ( & format ! (
7989
+ "{}/{}.gleam" ,
7990
+ uri. path( ) . trim_end_matches( ".gleam" ) ,
7991
+ relative_name
7992
+ ) ) ;
7993
+
7994
+ CodeActionBuilder :: new ( format ! ( "Create module {}.gleam" , unknown_module. name) )
7995
+ . kind ( CodeActionKind :: QUICKFIX )
7996
+ . document_changes ( DocumentChanges :: Operations ( vec ! [
7997
+ DocumentChangeOperation :: Op ( ResourceOp :: Create ( CreateFile {
7998
+ uri,
7999
+ options: Some ( CreateFileOptions {
8000
+ overwrite: Some ( false ) ,
8001
+ ignore_if_exists: Some ( true ) ,
8002
+ } ) ,
8003
+ annotation_id: None ,
8004
+ } ) ) ,
8005
+ ] ) )
8006
+ . push_to ( & mut actions) ;
8007
+ }
8008
+
8009
+ actions
8010
+ }
8011
+ }
0 commit comments