diff --git a/Cargo.lock b/Cargo.lock index 74ec7448b66..5dbbd711e5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2533,6 +2533,7 @@ dependencies = [ "fm", "lsp-types 0.94.1", "nargo", + "nargo_fmt", "nargo_toml", "noirc_driver", "noirc_errors", diff --git a/tooling/lsp/Cargo.toml b/tooling/lsp/Cargo.toml index 9e642d5fe9c..67778c744db 100644 --- a/tooling/lsp/Cargo.toml +++ b/tooling/lsp/Cargo.toml @@ -14,6 +14,7 @@ codespan-lsp.workspace = true codespan-reporting.workspace = true lsp-types.workspace = true nargo.workspace = true +nargo_fmt.workspace = true nargo_toml.workspace = true noirc_driver.workspace = true noirc_errors.workspace = true diff --git a/tooling/lsp/src/lib.rs b/tooling/lsp/src/lib.rs index 79fc544692a..d8a992155dd 100644 --- a/tooling/lsp/src/lib.rs +++ b/tooling/lsp/src/lib.rs @@ -4,6 +4,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] use std::{ + collections::HashMap, future::Future, ops::{self, ControlFlow}, path::{Path, PathBuf}, @@ -26,8 +27,8 @@ use notifications::{ on_did_open_text_document, on_did_save_text_document, on_exit, on_initialized, }; use requests::{ - on_code_lens_request, on_initialize, on_profile_run_request, on_shutdown, on_test_run_request, - on_tests_request, + on_code_lens_request, on_formatting, on_initialize, on_profile_run_request, on_shutdown, + on_test_run_request, on_tests_request, }; use serde_json::Value as JsonValue; use tower::Service; @@ -45,11 +46,17 @@ pub struct LspState { root_path: Option, client: ClientSocket, solver: WrapperSolver, + input_files: HashMap, } impl LspState { fn new(client: &ClientSocket, solver: impl BlackBoxFunctionSolver + 'static) -> Self { - Self { client: client.clone(), root_path: None, solver: WrapperSolver(Box::new(solver)) } + Self { + client: client.clone(), + root_path: None, + solver: WrapperSolver(Box::new(solver)), + input_files: HashMap::new(), + } } } @@ -63,6 +70,7 @@ impl NargoLspService { let mut router = Router::new(state); router .request::(on_initialize) + .request::(on_formatting) .request::(on_shutdown) .request::(on_code_lens_request) .request::(on_tests_request) diff --git a/tooling/lsp/src/notifications/mod.rs b/tooling/lsp/src/notifications/mod.rs index 93fa8baf6ac..f6484f49d48 100644 --- a/tooling/lsp/src/notifications/mod.rs +++ b/tooling/lsp/src/notifications/mod.rs @@ -30,23 +30,27 @@ pub(super) fn on_did_change_configuration( } pub(super) fn on_did_open_text_document( - _state: &mut LspState, - _params: DidOpenTextDocumentParams, + state: &mut LspState, + params: DidOpenTextDocumentParams, ) -> ControlFlow> { + state.input_files.insert(params.text_document.uri.to_string(), params.text_document.text); ControlFlow::Continue(()) } pub(super) fn on_did_change_text_document( - _state: &mut LspState, - _params: DidChangeTextDocumentParams, + state: &mut LspState, + params: DidChangeTextDocumentParams, ) -> ControlFlow> { + let text = params.content_changes.into_iter().next().unwrap().text; + state.input_files.insert(params.text_document.uri.to_string(), text); ControlFlow::Continue(()) } pub(super) fn on_did_close_text_document( - _state: &mut LspState, - _params: DidCloseTextDocumentParams, + state: &mut LspState, + params: DidCloseTextDocumentParams, ) -> ControlFlow> { + state.input_files.remove(¶ms.text_document.uri.to_string()); ControlFlow::Continue(()) } diff --git a/tooling/lsp/src/requests/mod.rs b/tooling/lsp/src/requests/mod.rs index b2be24e1187..a319f2593a4 100644 --- a/tooling/lsp/src/requests/mod.rs +++ b/tooling/lsp/src/requests/mod.rs @@ -1,7 +1,9 @@ use std::future::Future; -use crate::types::{CodeLensOptions, InitializeParams, TextDocumentSyncOptions}; +use crate::types::{CodeLensOptions, InitializeParams}; use async_lsp::ResponseError; +use lsp_types::{Position, TextDocumentSyncCapability, TextDocumentSyncKind}; +use nargo_fmt::Config; use crate::{ types::{InitializeResult, NargoCapability, NargoTestsOptions, ServerCapabilities}, @@ -35,8 +37,7 @@ pub(crate) fn on_initialize( state.root_path = params.root_uri.and_then(|root_uri| root_uri.to_file_path().ok()); async { - let text_document_sync = - TextDocumentSyncOptions { save: Some(true.into()), ..Default::default() }; + let text_document_sync = TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL); let code_lens = CodeLensOptions { resolve_provider: Some(false) }; @@ -50,8 +51,9 @@ pub(crate) fn on_initialize( Ok(InitializeResult { capabilities: ServerCapabilities { - text_document_sync: Some(text_document_sync.into()), + text_document_sync: Some(text_document_sync), code_lens_provider: Some(code_lens), + document_formatting_provider: true, nargo: Some(nargo), }, server_info: None, @@ -59,6 +61,42 @@ pub(crate) fn on_initialize( } } +pub(crate) fn on_formatting( + state: &mut LspState, + params: lsp_types::DocumentFormattingParams, +) -> impl Future>, ResponseError>> { + std::future::ready(on_formatting_inner(state, params)) +} + +fn on_formatting_inner( + state: &LspState, + params: lsp_types::DocumentFormattingParams, +) -> Result>, ResponseError> { + let path = params.text_document.uri.to_string(); + + if let Some(source) = state.input_files.get(&path) { + let (module, errors) = noirc_frontend::parse_program(source); + if !errors.is_empty() { + return Ok(None); + } + + let new_text = nargo_fmt::format(source, module, &Config::default()); + + let start_position = Position { line: 0, character: 0 }; + let end_position = Position { + line: source.lines().count() as u32, + character: source.chars().count() as u32, + }; + + Ok(Some(vec![lsp_types::TextEdit { + range: lsp_types::Range::new(start_position, end_position), + new_text, + }])) + } else { + Ok(None) + } +} + pub(crate) fn on_shutdown( _state: &mut LspState, _params: (), @@ -70,7 +108,7 @@ pub(crate) fn on_shutdown( mod initialization { use async_lsp::ClientSocket; use lsp_types::{ - CodeLensOptions, InitializeParams, TextDocumentSyncCapability, TextDocumentSyncOptions, + CodeLensOptions, InitializeParams, TextDocumentSyncCapability, TextDocumentSyncKind, }; use tokio::test; @@ -88,10 +126,11 @@ mod initialization { assert!(matches!( response.capabilities, ServerCapabilities { - text_document_sync: Some(TextDocumentSyncCapability::Options( - TextDocumentSyncOptions { save: Some(_), .. } + text_document_sync: Some(TextDocumentSyncCapability::Kind( + TextDocumentSyncKind::FULL )), code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(false) }), + document_formatting_provider: true, .. } )); diff --git a/tooling/lsp/src/types.rs b/tooling/lsp/src/types.rs index 7a50c538051..ba964cba0c1 100644 --- a/tooling/lsp/src/types.rs +++ b/tooling/lsp/src/types.rs @@ -12,7 +12,7 @@ pub(crate) use lsp_types::{ DidChangeConfigurationParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializeParams, InitializedParams, LogMessageParams, MessageType, Position, PublishDiagnosticsParams, Range, ServerInfo, - TextDocumentSyncCapability, TextDocumentSyncOptions, Url, + TextDocumentSyncCapability, Url, }; pub(crate) mod request { @@ -24,7 +24,7 @@ pub(crate) mod request { }; // Re-providing lsp_types that we don't need to override - pub(crate) use lsp_types::request::{CodeLensRequest as CodeLens, Shutdown}; + pub(crate) use lsp_types::request::{CodeLensRequest as CodeLens, Formatting, Shutdown}; #[derive(Debug)] pub(crate) struct Initialize; @@ -112,6 +112,9 @@ pub(crate) struct ServerCapabilities { #[serde(skip_serializing_if = "Option::is_none")] pub(crate) code_lens_provider: Option, + /// The server provides document formatting. + pub(crate) document_formatting_provider: bool, + /// The server handles and provides custom nargo messages. #[serde(skip_serializing_if = "Option::is_none")] pub(crate) nargo: Option,