@@ -14,20 +14,24 @@ use crate::{
1414 config:: PackageConfig ,
1515 exhaustiveness:: CompiledCase ,
1616 io:: { BeamCompiler , CommandExecutor , FileSystemReader , FileSystemWriter } ,
17- language_server:: { edits, reference:: FindVariableReferences } ,
17+ language_server:: { edits, lsp_range_to_src_span , reference:: FindVariableReferences } ,
1818 line_numbers:: LineNumbers ,
1919 parse:: { extra:: ModuleExtra , lexer:: str_to_keyword} ,
2020 strings:: to_snake_case,
2121 type_:: {
22- self , FieldMap , ModuleValueConstructor , Type , TypeVar , TypedCallArg , ValueConstructor ,
22+ self , Error as TypeError , FieldMap , ModuleValueConstructor , Type , TypeVar , TypedCallArg ,
23+ ValueConstructor ,
2324 error:: { ModuleSuggestion , VariableDeclaration , VariableOrigin } ,
2425 printer:: Printer ,
2526 } ,
2627} ;
2728use ecow:: { EcoString , eco_format} ;
2829use im:: HashMap ;
2930use 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+ } ;
3135use vec1:: { Vec1 , vec1} ;
3236
3337use super :: {
@@ -46,7 +50,7 @@ pub struct CodeActionBuilder {
4650}
4751
4852impl CodeActionBuilder {
49- pub fn new ( title : & str ) -> Self {
53+ pub fn new ( title : impl ToString ) -> Self {
5054 Self {
5155 action : CodeAction {
5256 title : title. to_string ( ) ,
@@ -76,6 +80,15 @@ impl CodeActionBuilder {
7680 self
7781 }
7882
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+
7992 pub fn preferred ( mut self , is_preferred : bool ) -> Self {
8093 self . action . is_preferred = Some ( is_preferred) ;
8194 self
@@ -7895,3 +7908,104 @@ fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> {
78957908 expression => Some ( expression) ,
78967909 }
78977910}
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