diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index e6608e84625..7af00fa15a8 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -1,6 +1,7 @@ use std::{ future::{self, Future}, ops::{self, ControlFlow}, + path::PathBuf, pin::Pin, task::{self, Poll}, }; @@ -31,12 +32,13 @@ const TEST_CODELENS_TITLE: &str = "▶\u{fe0e} Run Test"; // State for the LSP gets implemented on this struct and is internal to the implementation pub struct LspState { + root_path: Option, client: ClientSocket, } impl LspState { fn new(client: &ClientSocket) -> Self { - Self { client: client.clone() } + Self { client: client.clone(), root_path: None } } } @@ -102,9 +104,11 @@ impl LspService for NargoLspService { // and params passed in. fn on_initialize( - _state: &mut LspState, - _params: InitializeParams, + state: &mut LspState, + params: InitializeParams, ) -> impl Future> { + 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() }; @@ -144,7 +148,17 @@ fn on_code_lens_request( } }; - let toml_path = match find_package_manifest(&file_path) { + let root_path = match &state.root_path { + Some(root) => root, + None => { + return future::ready(Err(ResponseError::new( + ErrorCode::REQUEST_FAILED, + "Could not find project root", + ))) + } + }; + + let toml_path = match find_package_manifest(root_path, &file_path) { Ok(toml_path) => toml_path, Err(err) => { // If we cannot find a manifest, we log a warning but return no code lenses @@ -269,7 +283,18 @@ fn on_did_save_text_document( } }; - let toml_path = match find_package_manifest(&file_path) { + let root_path = match &state.root_path { + Some(root) => root, + None => { + return ControlFlow::Break(Err(ResponseError::new( + ErrorCode::REQUEST_FAILED, + "Could not find project root", + ) + .into())); + } + }; + + let toml_path = match find_package_manifest(root_path, &file_path) { Ok(toml_path) => toml_path, Err(err) => { // If we cannot find a manifest, we log a warning but return no diagnostics diff --git a/crates/nargo_cli/src/cli/check_cmd.rs b/crates/nargo_cli/src/cli/check_cmd.rs index 4364c78a6dc..cd61832dabc 100644 --- a/crates/nargo_cli/src/cli/check_cmd.rs +++ b/crates/nargo_cli/src/cli/check_cmd.rs @@ -3,7 +3,7 @@ use acvm::Backend; use clap::Args; use iter_extended::btree_map; use nargo::{package::Package, prepare_package}; -use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::{AbiParameter, AbiType, MAIN_RETURN_NAME}; use noirc_driver::{check_crate, compute_function_signature, CompileOptions}; use noirc_frontend::{ @@ -34,7 +34,7 @@ pub(crate) fn run( args: CheckCommand, config: NargoConfig, ) -> Result<(), CliError> { - let toml_path = find_package_manifest(&config.program_dir)?; + let toml_path = get_package_manifest(&config.program_dir)?; let default_selection = if args.workspace { PackageSelection::All } else { PackageSelection::DefaultOrAll }; let selection = args.package.map_or(default_selection, PackageSelection::Selected); diff --git a/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs b/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs index 2a6cec1534a..7662eaa8401 100644 --- a/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs +++ b/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs @@ -20,7 +20,7 @@ use nargo::{ ops::{codegen_verifier, preprocess_program}, package::Package, }; -use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::CompileOptions; use noirc_frontend::graph::CrateName; @@ -44,7 +44,7 @@ pub(crate) fn run( args: CodegenVerifierCommand, config: NargoConfig, ) -> Result<(), CliError> { - let toml_path = find_package_manifest(&config.program_dir)?; + let toml_path = get_package_manifest(&config.program_dir)?; let default_selection = if args.workspace { PackageSelection::All } else { PackageSelection::DefaultOrAll }; let selection = args.package.map_or(default_selection, PackageSelection::Selected); diff --git a/crates/nargo_cli/src/cli/compile_cmd.rs b/crates/nargo_cli/src/cli/compile_cmd.rs index 08a24091a59..8c09c248093 100644 --- a/crates/nargo_cli/src/cli/compile_cmd.rs +++ b/crates/nargo_cli/src/cli/compile_cmd.rs @@ -6,7 +6,7 @@ use nargo::artifacts::debug::DebugArtifact; use nargo::package::Package; use nargo::prepare_package; use nargo::{artifacts::contract::PreprocessedContract, NargoError}; -use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{ compile_contracts, compile_main, CompileOptions, CompiledContract, CompiledProgram, ErrorsAndWarnings, Warnings, @@ -62,7 +62,7 @@ pub(crate) fn run( args: CompileCommand, config: NargoConfig, ) -> Result<(), CliError> { - let toml_path = find_package_manifest(&config.program_dir)?; + let toml_path = get_package_manifest(&config.program_dir)?; let default_selection = if args.workspace { PackageSelection::All } else { PackageSelection::DefaultOrAll }; let selection = args.package.map_or(default_selection, PackageSelection::Selected); diff --git a/crates/nargo_cli/src/cli/execute_cmd.rs b/crates/nargo_cli/src/cli/execute_cmd.rs index 82455ea3163..f2fa6426a7e 100644 --- a/crates/nargo_cli/src/cli/execute_cmd.rs +++ b/crates/nargo_cli/src/cli/execute_cmd.rs @@ -5,7 +5,7 @@ use clap::Args; use nargo::constants::PROVER_INPUT_FILE; use nargo::package::Package; use nargo::NargoError; -use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::input_parser::{Format, InputValue}; use noirc_abi::{Abi, InputMap}; use noirc_driver::{CompileOptions, CompiledProgram}; @@ -45,7 +45,7 @@ pub(crate) fn run( args: ExecuteCommand, config: NargoConfig, ) -> Result<(), CliError> { - let toml_path = find_package_manifest(&config.program_dir)?; + let toml_path = get_package_manifest(&config.program_dir)?; let default_selection = if args.workspace { PackageSelection::All } else { PackageSelection::DefaultOrAll }; let selection = args.package.map_or(default_selection, PackageSelection::Selected); diff --git a/crates/nargo_cli/src/cli/info_cmd.rs b/crates/nargo_cli/src/cli/info_cmd.rs index 48ccd76f9cd..a761c376973 100644 --- a/crates/nargo_cli/src/cli/info_cmd.rs +++ b/crates/nargo_cli/src/cli/info_cmd.rs @@ -2,7 +2,7 @@ use acvm::Backend; use clap::Args; use iter_extended::try_vecmap; use nargo::{package::Package, prepare_package}; -use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{compile_contracts, CompileOptions}; use noirc_frontend::graph::CrateName; use prettytable::{row, Table}; @@ -38,7 +38,7 @@ pub(crate) fn run( args: InfoCommand, config: NargoConfig, ) -> Result<(), CliError> { - let toml_path = find_package_manifest(&config.program_dir)?; + let toml_path = get_package_manifest(&config.program_dir)?; let default_selection = if args.workspace { PackageSelection::All } else { PackageSelection::DefaultOrAll }; let selection = args.package.map_or(default_selection, PackageSelection::Selected); diff --git a/crates/nargo_cli/src/cli/prove_cmd.rs b/crates/nargo_cli/src/cli/prove_cmd.rs index 66129f8748f..e4766828a5b 100644 --- a/crates/nargo_cli/src/cli/prove_cmd.rs +++ b/crates/nargo_cli/src/cli/prove_cmd.rs @@ -6,7 +6,7 @@ use nargo::artifacts::program::PreprocessedProgram; use nargo::constants::{PROVER_INPUT_FILE, VERIFIER_INPUT_FILE}; use nargo::ops::{preprocess_program, prove_execution, verify_proof}; use nargo::package::Package; -use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::input_parser::Format; use noirc_driver::CompileOptions; use noirc_frontend::graph::CrateName; @@ -56,7 +56,7 @@ pub(crate) fn run( args: ProveCommand, config: NargoConfig, ) -> Result<(), CliError> { - let toml_path = find_package_manifest(&config.program_dir)?; + let toml_path = get_package_manifest(&config.program_dir)?; let default_selection = if args.workspace { PackageSelection::All } else { PackageSelection::DefaultOrAll }; let selection = args.package.map_or(default_selection, PackageSelection::Selected); diff --git a/crates/nargo_cli/src/cli/test_cmd.rs b/crates/nargo_cli/src/cli/test_cmd.rs index bdce8e35096..94c8ff86dcd 100644 --- a/crates/nargo_cli/src/cli/test_cmd.rs +++ b/crates/nargo_cli/src/cli/test_cmd.rs @@ -3,7 +3,7 @@ use std::io::Write; use acvm::{acir::native_types::WitnessMap, Backend}; use clap::Args; use nargo::{ops::execute_circuit, package::Package, prepare_package}; -use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{compile_no_check, CompileOptions}; use noirc_frontend::{ graph::CrateName, @@ -47,7 +47,7 @@ pub(crate) fn run( args: TestCommand, config: NargoConfig, ) -> Result<(), CliError> { - let toml_path = find_package_manifest(&config.program_dir)?; + let toml_path = get_package_manifest(&config.program_dir)?; let default_selection = if args.workspace { PackageSelection::All } else { PackageSelection::DefaultOrAll }; let selection = args.package.map_or(default_selection, PackageSelection::Selected); diff --git a/crates/nargo_cli/src/cli/verify_cmd.rs b/crates/nargo_cli/src/cli/verify_cmd.rs index 5319d7a4f39..c2f21a2123b 100644 --- a/crates/nargo_cli/src/cli/verify_cmd.rs +++ b/crates/nargo_cli/src/cli/verify_cmd.rs @@ -18,7 +18,7 @@ use clap::Args; use nargo::constants::{PROOF_EXT, VERIFIER_INPUT_FILE}; use nargo::ops::{preprocess_program, verify_proof}; use nargo::{artifacts::program::PreprocessedProgram, package::Package}; -use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::input_parser::Format; use noirc_driver::CompileOptions; use noirc_frontend::graph::CrateName; @@ -48,7 +48,7 @@ pub(crate) fn run( args: VerifyCommand, config: NargoConfig, ) -> Result<(), CliError> { - let toml_path = find_package_manifest(&config.program_dir)?; + let toml_path = get_package_manifest(&config.program_dir)?; let default_selection = if args.workspace { PackageSelection::All } else { PackageSelection::DefaultOrAll }; let selection = args.package.map_or(default_selection, PackageSelection::Selected); diff --git a/crates/nargo_toml/src/errors.rs b/crates/nargo_toml/src/errors.rs index e986a181e43..2b68f681f92 100644 --- a/crates/nargo_toml/src/errors.rs +++ b/crates/nargo_toml/src/errors.rs @@ -59,4 +59,7 @@ pub enum ManifestError { #[error("Missing `name` field in {toml}")] MissingNameField { toml: PathBuf }, + + #[error("No common ancestor between {root} and {current}")] + NoCommonAncestor { root: PathBuf, current: PathBuf }, } diff --git a/crates/nargo_toml/src/lib.rs b/crates/nargo_toml/src/lib.rs index 6189d6426ba..8372942931b 100644 --- a/crates/nargo_toml/src/lib.rs +++ b/crates/nargo_toml/src/lib.rs @@ -1,7 +1,6 @@ use std::{ collections::BTreeMap, - fs::ReadDir, - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, }; use fm::{NormalizePath, FILE_EXTENSION}; @@ -18,11 +17,12 @@ mod git; pub use errors::ManifestError; use git::clone_git_repo; -/// Returns the path of the root directory of the package containing `current_path`. +/// Returns the [PathBuf] of the directory containing the `Nargo.toml` by searching from `current_path` to the root of its [Path]. /// -/// Returns a `CliError` if no parent directories of `current_path` contain a manifest file. +/// Returns a [ManifestError] if no parent directories of `current_path` contain a manifest file. pub fn find_package_root(current_path: &Path) -> Result { - let manifest_path = find_package_manifest(current_path)?; + let root = path_root(current_path); + let manifest_path = find_package_manifest(&root, current_path)?; let package_root = manifest_path.parent().expect("infallible: manifest file path can't be root directory"); @@ -30,36 +30,58 @@ pub fn find_package_root(current_path: &Path) -> Result Ok(package_root.to_path_buf()) } -/// Returns the path of the manifest file (`Nargo.toml`) of the package containing `current_path`. -/// -/// Returns a `CliError` if no parent directories of `current_path` contain a manifest file. -pub fn find_package_manifest(current_path: &Path) -> Result { - current_path - .ancestors() - .find_map(|dir| find_file(dir, "Nargo", "toml")) - .ok_or_else(|| ManifestError::MissingFile(current_path.to_path_buf())) -} +// TODO(#2323): We are probably going to need a "filepath utils" crate soon +fn path_root(path: &Path) -> PathBuf { + let mut components = path.components(); -// Looks for file named `file_name` in path -fn find_file>(path: P, file_name: &str, extension: &str) -> Option { - let entries = list_files_and_folders_in(path)?; - let file_name = format!("{file_name}.{extension}"); - - find_artifact(entries, &file_name) + match (components.next(), components.next()) { + // Preserve prefix if one exists + (Some(prefix @ Component::Prefix(_)), Some(root @ Component::RootDir)) => { + PathBuf::from(prefix.as_os_str()).join(root.as_os_str()) + } + (Some(root @ Component::RootDir), _) => PathBuf::from(root.as_os_str()), + _ => PathBuf::new(), + } } -// There is no distinction between files and folders -fn find_artifact(entries: ReadDir, artifact_name: &str) -> Option { - let entry = entries - .into_iter() - .flatten() - .find(|entry| entry.file_name().to_str() == Some(artifact_name))?; +/// Returns the [PathBuf] of the `Nargo.toml` file by searching from `current_path` and stopping at `root_path`. +/// +/// Returns a [ManifestError] if no parent directories of `current_path` contain a manifest file. +pub fn find_package_manifest( + root_path: &Path, + current_path: &Path, +) -> Result { + if current_path.starts_with(root_path) { + let mut found_toml_paths = Vec::new(); + for path in current_path.ancestors() { + if let Ok(toml_path) = get_package_manifest(path) { + found_toml_paths.push(toml_path); + } + // While traversing, break once we process the root specified + if path == root_path { + break; + } + } - Some(entry.path()) + // Return the shallowest Nargo.toml, which will be the last in the list + found_toml_paths.pop().ok_or_else(|| ManifestError::MissingFile(current_path.to_path_buf())) + } else { + Err(ManifestError::NoCommonAncestor { + root: root_path.to_path_buf(), + current: current_path.to_path_buf(), + }) + } } - -fn list_files_and_folders_in>(path: P) -> Option { - std::fs::read_dir(path).ok() +/// Returns the [PathBuf] of the `Nargo.toml` file in the `current_path` directory. +/// +/// Returns a [ManifestError] if `current_path` does not contain a manifest file. +pub fn get_package_manifest(current_path: &Path) -> Result { + let toml_path = current_path.join("Nargo.toml"); + if toml_path.exists() { + Ok(toml_path) + } else { + Err(ManifestError::MissingFile(current_path.to_path_buf())) + } } #[derive(Debug, Deserialize, Clone)]