Skip to content

Commit 5fe69c6

Browse files
authored
feat(lsp): Add Compile code lens for main function and contracts (#2309)
1 parent 75fd3e0 commit 5fe69c6

File tree

2 files changed

+78
-4
lines changed

2 files changed

+78
-4
lines changed

crates/lsp/src/lib.rs

+76-3
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ use noirc_frontend::hir::FunctionNameMatch;
2727
use serde_json::Value as JsonValue;
2828
use tower::Service;
2929

30+
const ARROW: &str = "▶\u{fe0e}";
3031
const TEST_COMMAND: &str = "nargo.test";
31-
const TEST_CODELENS_TITLE: &str = "▶\u{fe0e} Run Test";
32+
const TEST_CODELENS_TITLE: &str = "Run Test";
33+
const COMPILE_COMMAND: &str = "nargo.compile";
34+
const COMPILE_CODELENS_TITLE: &str = "Compile";
3235

3336
// State for the LSP gets implemented on this struct and is internal to the implementation
3437
pub struct LspState {
@@ -185,7 +188,7 @@ fn on_code_lens_request(
185188

186189
for package in &workspace {
187190
let (mut context, crate_id) = prepare_package(package);
188-
// We ignore the warnings and errors produced by compilation for producing codelenses
191+
// We ignore the warnings and errors produced by compilation for producing code lenses
189192
// because we can still get the test functions even if compilation fails
190193
let _ = check_crate(&mut context, crate_id, false);
191194

@@ -210,7 +213,7 @@ fn on_code_lens_request(
210213
.unwrap_or_default();
211214

212215
let command = Command {
213-
title: TEST_CODELENS_TITLE.into(),
216+
title: format!("{ARROW} {TEST_CODELENS_TITLE}"),
214217
command: TEST_COMMAND.into(),
215218
arguments: Some(vec![
216219
"--program-dir".into(),
@@ -226,6 +229,73 @@ fn on_code_lens_request(
226229

227230
lenses.push(lens);
228231
}
232+
233+
if package.is_binary() {
234+
if let Some(main_func_id) = context.get_main_function(&crate_id) {
235+
let location = context.function_meta(&main_func_id).name.location;
236+
let file_id = location.file;
237+
238+
// Ignore diagnostics for any file that wasn't the file we saved
239+
// TODO: In the future, we could create "related" diagnostics for these files
240+
// TODO: This currently just appends the `.nr` file extension that we store as a constant,
241+
// but that won't work if we accept other extensions
242+
if fm.path(file_id).with_extension(FILE_EXTENSION) != file_path {
243+
continue;
244+
}
245+
246+
let range = byte_span_to_range(files, file_id.as_usize(), location.span.into())
247+
.unwrap_or_default();
248+
249+
let command = Command {
250+
title: format!("{ARROW} {COMPILE_CODELENS_TITLE}"),
251+
command: COMPILE_COMMAND.into(),
252+
arguments: Some(vec![
253+
"--program-dir".into(),
254+
format!("{}", workspace.root_dir.display()).into(),
255+
"--package".into(),
256+
format!("{}", package.name).into(),
257+
]),
258+
};
259+
260+
let lens = CodeLens { range, command: command.into(), data: None };
261+
262+
lenses.push(lens);
263+
}
264+
}
265+
266+
if package.is_contract() {
267+
// Currently not looking to deduplicate this since we don't have a clear decision on if the Contract stuff is staying
268+
for contract in context.get_all_contracts(&crate_id) {
269+
let location = contract.location;
270+
let file_id = location.file;
271+
272+
// Ignore diagnostics for any file that wasn't the file we saved
273+
// TODO: In the future, we could create "related" diagnostics for these files
274+
// TODO: This currently just appends the `.nr` file extension that we store as a constant,
275+
// but that won't work if we accept other extensions
276+
if fm.path(file_id).with_extension(FILE_EXTENSION) != file_path {
277+
continue;
278+
}
279+
280+
let range = byte_span_to_range(files, file_id.as_usize(), location.span.into())
281+
.unwrap_or_default();
282+
283+
let command = Command {
284+
title: format!("{ARROW} {COMPILE_CODELENS_TITLE}"),
285+
command: COMPILE_COMMAND.into(),
286+
arguments: Some(vec![
287+
"--program-dir".into(),
288+
format!("{}", workspace.root_dir.display()).into(),
289+
"--package".into(),
290+
format!("{}", package.name).into(),
291+
]),
292+
};
293+
294+
let lens = CodeLens { range, command: command.into(), data: None };
295+
296+
lenses.push(lens);
297+
}
298+
}
229299
}
230300

231301
let res = if lenses.is_empty() { Ok(None) } else { Ok(Some(lenses)) };
@@ -365,6 +435,9 @@ fn on_did_save_text_document(
365435
}
366436
}
367437

438+
// We need to refresh lenses when we compile since that's the only time they can be accurately reflected
439+
let _ = state.client.code_lens_refresh(());
440+
368441
let _ = state.client.publish_diagnostics(PublishDiagnosticsParams {
369442
uri: params.text_document.uri,
370443
version: None,

crates/noirc_frontend/src/hir/def_map/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ impl CrateDefMap {
148148
let functions =
149149
module.value_definitions().filter_map(|id| id.as_function()).collect();
150150
let name = self.get_module_path(id, module.parent);
151-
Some(Contract { name, functions })
151+
Some(Contract { name, location: module.location, functions })
152152
} else {
153153
None
154154
}
@@ -194,6 +194,7 @@ impl CrateDefMap {
194194
pub struct Contract {
195195
/// To keep `name` semi-unique, it is prefixed with the names of parent modules via CrateDefMap::get_module_path
196196
pub name: String,
197+
pub location: Location,
197198
pub functions: Vec<FuncId>,
198199
}
199200

0 commit comments

Comments
 (0)