Skip to content

Commit 78681dc

Browse files
committed
Add create unknown module code action
1 parent f1f45d1 commit 78681dc

File tree

3 files changed

+113
-5
lines changed

3 files changed

+113
-5
lines changed

compiler-core/src/ast.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2265,6 +2265,10 @@ impl SrcSpan {
22652265
byte_index >= self.start && byte_index <= self.end
22662266
}
22672267

2268+
pub fn intersects(&self, other: Self) -> bool {
2269+
self.start < other.end && self.end > other.start
2270+
}
2271+
22682272
/// Merges two spans into a new one that starts at the start of the smaller
22692273
/// one and ends at the end of the bigger one. For example:
22702274
///

compiler-core/src/language_server/code_action.rs

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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
};
2728
use ecow::{EcoString, eco_format};
2829
use im::HashMap;
2930
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+
};
3135
use vec1::{Vec1, vec1};
3236

3337
use super::{
@@ -46,7 +50,7 @@ pub struct CodeActionBuilder {
4650
}
4751

4852
impl 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,91 @@ fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> {
78957908
expression => Some(expression),
78967909
}
78977910
}
7911+
7912+
/// Code action to create an unknown module
7913+
///
7914+
/// ```gleam
7915+
/// // foo.gleam
7916+
/// // Diagnostic: Unknown module
7917+
/// import foo/bar/baz
7918+
/// ```
7919+
///
7920+
/// Would create:
7921+
///
7922+
/// ```gleam
7923+
/// // foo/bar/baz.gleam
7924+
/// ```
7925+
///
7926+
pub struct CreateUnknownModule<'a> {
7927+
module: &'a Module,
7928+
error: &'a Option<Error>,
7929+
code_action_span: SrcSpan,
7930+
}
7931+
7932+
impl<'a> CreateUnknownModule<'a> {
7933+
pub fn new(
7934+
module: &'a Module,
7935+
lines: &'a LineNumbers,
7936+
params: &'a CodeActionParams,
7937+
error: &'a Option<Error>,
7938+
) -> Self {
7939+
Self {
7940+
module,
7941+
error,
7942+
code_action_span: lsp_range_to_src_span(params.range, lines),
7943+
}
7944+
}
7945+
7946+
pub fn code_actions(self) -> Vec<CodeAction> {
7947+
struct UnknownModule<'a> {
7948+
name: &'a EcoString,
7949+
location: &'a SrcSpan,
7950+
}
7951+
7952+
let mut actions = vec![];
7953+
7954+
let Some(Error::Type { errors, .. }) = self.error else {
7955+
return actions;
7956+
};
7957+
7958+
let origin_path = self
7959+
.module
7960+
.input_path
7961+
.as_str()
7962+
.strip_suffix(&format!("{}.gleam", self.module.name))
7963+
.expect("origin is ancestor of module path");
7964+
7965+
let unknown_modules = errors.iter().filter_map(|error| {
7966+
if let TypeError::UnknownModule { name, location, .. } = error {
7967+
return Some(UnknownModule { name, location });
7968+
}
7969+
7970+
None
7971+
});
7972+
7973+
for unknown_module in unknown_modules {
7974+
if !self.code_action_span.intersects(*unknown_module.location) {
7975+
continue;
7976+
}
7977+
7978+
let uri = Url::from_file_path(format!("{origin_path}/{}.gleam", unknown_module.name))
7979+
.expect("origin path is absolute");
7980+
7981+
CodeActionBuilder::new(format!("Create module {}.gleam", unknown_module.name))
7982+
.kind(CodeActionKind::QUICKFIX)
7983+
.document_changes(DocumentChanges::Operations(vec![
7984+
DocumentChangeOperation::Op(ResourceOp::Create(CreateFile {
7985+
uri,
7986+
options: Some(CreateFileOptions {
7987+
overwrite: Some(false),
7988+
ignore_if_exists: Some(true),
7989+
}),
7990+
annotation_id: None,
7991+
})),
7992+
]))
7993+
.push_to(&mut actions);
7994+
}
7995+
7996+
actions
7997+
}
7998+
}

compiler-core/src/language_server/engine.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use super::{
4242
DownloadDependencies, MakeLocker,
4343
code_action::{
4444
AddAnnotations, CodeActionBuilder, ConvertFromUse, ConvertToFunctionCall, ConvertToPipe,
45-
ConvertToUse, ExpandFunctionCapture, ExtractConstant, ExtractVariable,
45+
ConvertToUse, CreateUnknownModule, ExpandFunctionCapture, ExtractConstant, ExtractVariable,
4646
FillInMissingLabelledArgs, FillUnusedFields, FixBinaryOperation,
4747
FixTruncatedBitArraySegment, GenerateDynamicDecoder, GenerateFunction, GenerateJsonEncoder,
4848
GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, PatternMatchOnValue,
@@ -451,6 +451,9 @@ where
451451
)
452452
.code_actions();
453453
AddAnnotations::new(module, &lines, &params).code_action(&mut actions);
454+
actions.extend(
455+
CreateUnknownModule::new(module, &lines, &params, &this.error).code_actions(),
456+
);
454457
Ok(if actions.is_empty() {
455458
None
456459
} else {

0 commit comments

Comments
 (0)