@@ -10,24 +10,29 @@ use crate::{
1010 TypedPattern , TypedPipelineAssignment , TypedRecordConstructor , TypedStatement , TypedUse ,
1111 visit:: Visit as _,
1212 } ,
13- build:: { Located , Module } ,
13+ build:: { Located , Module , Origin } ,
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} ,
20+ paths:: ProjectPaths ,
2021 strings:: to_snake_case,
2122 type_:: {
22- self , FieldMap , ModuleValueConstructor , Type , TypeVar , TypedCallArg , ValueConstructor ,
23+ self , Error as TypeError , FieldMap , ModuleValueConstructor , Type , TypeVar , TypedCallArg ,
24+ ValueConstructor ,
2325 error:: { ModuleSuggestion , VariableDeclaration , VariableOrigin } ,
2426 printer:: Printer ,
2527 } ,
2628} ;
2729use ecow:: { EcoString , eco_format} ;
2830use im:: HashMap ;
2931use 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+ } ;
3136use vec1:: { Vec1 , vec1} ;
3237
3338use super :: {
@@ -46,7 +51,7 @@ pub struct CodeActionBuilder {
4651}
4752
4853impl CodeActionBuilder {
49- pub fn new ( title : & str ) -> Self {
54+ pub fn new ( title : impl ToString ) -> Self {
5055 Self {
5156 action : CodeAction {
5257 title : title. to_string ( ) ,
@@ -76,6 +81,15 @@ impl CodeActionBuilder {
7681 self
7782 }
7883
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+
7993 pub fn preferred ( mut self , is_preferred : bool ) -> Self {
8094 self . action . is_preferred = Some ( is_preferred) ;
8195 self
@@ -7895,3 +7909,106 @@ fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> {
78957909 expression => Some ( expression) ,
78967910 }
78977911}
7912+
7913+ /// Code action to create unknown modules when an import is added for a
7914+ /// module that doesn't exist.
7915+ ///
7916+ /// For example, if `import wobble/woo` is added to `src/wiggle.gleam`,
7917+ /// then a code action to create `src/wobble/woo.gleam` will be presented
7918+ /// when triggered over `import wobble/woo`.
7919+ pub struct CreateUnknownModule < ' a > {
7920+ module : & ' a Module ,
7921+ lines : & ' a LineNumbers ,
7922+ params : & ' a CodeActionParams ,
7923+ paths : & ' a ProjectPaths ,
7924+ error : & ' a Option < Error > ,
7925+ }
7926+
7927+ impl < ' a > CreateUnknownModule < ' a > {
7928+ pub fn new (
7929+ module : & ' a Module ,
7930+ lines : & ' a LineNumbers ,
7931+ params : & ' a CodeActionParams ,
7932+ paths : & ' a ProjectPaths ,
7933+ error : & ' a Option < Error > ,
7934+ ) -> Self {
7935+ Self {
7936+ module,
7937+ lines,
7938+ params,
7939+ paths,
7940+ error,
7941+ }
7942+ }
7943+
7944+ pub fn code_actions ( self ) -> Vec < CodeAction > {
7945+ struct UnknownModule < ' a > {
7946+ name : & ' a EcoString ,
7947+ location : & ' a SrcSpan ,
7948+ }
7949+
7950+ let mut actions = vec ! [ ] ;
7951+
7952+ // This code action can be derived from UnknownModule type errors. If those
7953+ // errors don't exist, there are no actions to add.
7954+ let Some ( Error :: Type { errors, .. } ) = self . error else {
7955+ return actions;
7956+ } ;
7957+
7958+ // Span of the code action so we can check if it exists within the span of
7959+ // the UnkownModule type error
7960+ let code_action_span = lsp_range_to_src_span ( self . params . range , self . lines ) ;
7961+
7962+ // Origin directory we can build the new module path from
7963+ let origin_directory = match self . module . origin {
7964+ Origin :: Src => self . paths . src_directory ( ) ,
7965+ Origin :: Test => self . paths . test_directory ( ) ,
7966+ Origin :: Dev => self . paths . dev_directory ( ) ,
7967+ } ;
7968+
7969+ // Filter for any UnknownModule type errors
7970+ let unknown_modules = errors. iter ( ) . filter_map ( |error| {
7971+ if let TypeError :: UnknownModule { name, location, .. } = error {
7972+ return Some ( UnknownModule { name, location } ) ;
7973+ }
7974+
7975+ None
7976+ } ) ;
7977+
7978+ // For each UnknownModule type error, check to see if it contains the
7979+ // incoming code action & if so, add a document change to create the module
7980+ for unknown_module in unknown_modules {
7981+ // Was this code action triggered within the UnknownModule error?
7982+ let error_contains_action = unknown_module. location . contains ( code_action_span. start )
7983+ && unknown_module. location . contains ( code_action_span. end ) ;
7984+
7985+ if !error_contains_action {
7986+ continue ;
7987+ }
7988+
7989+ let uri =
7990+ Url :: from_file_path ( format ! ( "{origin_directory}/{}.gleam" , unknown_module. name) )
7991+ . expect ( "origin directory is absolute" ) ;
7992+
7993+ CodeActionBuilder :: new ( format ! (
7994+ "Create module {}/{}.gleam" ,
7995+ self . module. origin. folder_name( ) ,
7996+ unknown_module. name
7997+ ) )
7998+ . kind ( CodeActionKind :: QUICKFIX )
7999+ . document_changes ( DocumentChanges :: Operations ( vec ! [
8000+ DocumentChangeOperation :: Op ( ResourceOp :: Create ( CreateFile {
8001+ uri,
8002+ options: Some ( CreateFileOptions {
8003+ overwrite: Some ( false ) ,
8004+ ignore_if_exists: Some ( true ) ,
8005+ } ) ,
8006+ annotation_id: None ,
8007+ } ) ) ,
8008+ ] ) )
8009+ . push_to ( & mut actions) ;
8010+ }
8011+
8012+ actions
8013+ }
8014+ }
0 commit comments