diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 31214e01480..5f860e971c9 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -487,11 +487,11 @@ fn add_import_reference( match def_id { crate::macros_api::ModuleDefId::FunctionId(func_id) => { let variable = DependencyId::Variable(Location::new(name.span(), file_id)); - interner.add_reference_for(DependencyId::Function(func_id), variable); + interner.add_reference(DependencyId::Function(func_id), variable); } crate::macros_api::ModuleDefId::TypeId(struct_id) => { let variable = DependencyId::Variable(Location::new(name.span(), file_id)); - interner.add_reference_for(DependencyId::Struct(struct_id), variable); + interner.add_reference(DependencyId::Struct(struct_id), variable); } _ => (), } diff --git a/compiler/noirc_frontend/src/locations.rs b/compiler/noirc_frontend/src/locations.rs index b8fa35a1193..88f3424906f 100644 --- a/compiler/noirc_frontend/src/locations.rs +++ b/compiler/noirc_frontend/src/locations.rs @@ -45,32 +45,10 @@ impl NodeInterner { } let referenced_index = self.get_or_insert_reference(referenced); - let reference_index = self.reference_graph.add_node(reference); - - let referenced_location = self.dependency_location(referenced); - let reference_location = self.dependency_location(reference); - - self.reference_graph.add_edge(referenced_index, reference_index, ()); - self.location_indices.add_location(referenced_location, referenced_index); - self.location_indices.add_location(reference_location, reference_index); - } - - pub(crate) fn add_reference_for( - &mut self, - referenced_id: DependencyId, - reference: DependencyId, - ) { - if !self.track_references { - return; - } - - let Some(referenced_index) = self.reference_graph_indices.get(&referenced_id) else { - panic!("Compiler Error: Referenced index not found") - }; - let reference_location = self.dependency_location(reference); let reference_index = self.reference_graph.add_node(reference); - self.reference_graph.add_edge(*referenced_index, reference_index, ()); + + self.reference_graph.add_edge(reference_index, referenced_index, ()); self.location_indices.add_location(reference_location, reference_index); } @@ -95,42 +73,60 @@ impl NodeInterner { index } - pub fn check_rename_possible(&self, location: Location) -> bool { + // Given a reference location, find the location of the referenced node. + pub fn find_referenced_location(&self, reference_location: Location) -> Option { + self.location_indices + .get_node_from_location(reference_location) + .and_then(|node_index| self.referenced_index(node_index)) + .map(|node_index| self.dependency_location(self.reference_graph[node_index])) + } + + // Is the given location known to this interner? + pub fn is_location_known(&self, location: Location) -> bool { self.location_indices.get_node_from_location(location).is_some() } - pub fn find_rename_symbols_at(&self, location: Location) -> Option> { + // Starting at the given location, find the node referenced by it. Then, gather + // all locations that reference that node, and return all of them + // (the referenced node and the references). + // Returns `None` if the location is not known to this interner. + pub fn find_all_references(&self, location: Location) -> Option> { let node_index = self.location_indices.get_node_from_location(location)?; let reference_node = self.reference_graph[node_index]; let found_locations: Vec = match reference_node { DependencyId::Alias(_) | DependencyId::Global(_) => todo!(), DependencyId::Function(_) | DependencyId::Struct(_) => { - self.get_edit_locations(node_index) + self.find_all_references_for_index(node_index) } DependencyId::Variable(_) => { - let referenced_node_index = self - .reference_graph - .neighbors_directed(node_index, petgraph::Direction::Incoming) - .next()?; - - self.get_edit_locations(referenced_node_index) + let referenced_node_index = self.referenced_index(node_index)?; + self.find_all_references_for_index(referenced_node_index) } }; Some(found_locations) } - fn get_edit_locations(&self, referenced_node_index: PetGraphIndex) -> Vec { + // Given a referenced node index, find all references to it and return their locations, together + // with the reference node's location. + fn find_all_references_for_index(&self, referenced_node_index: PetGraphIndex) -> Vec { let id = self.reference_graph[referenced_node_index]; let mut edit_locations = vec![self.dependency_location(id)]; self.reference_graph - .neighbors_directed(referenced_node_index, petgraph::Direction::Outgoing) + .neighbors_directed(referenced_node_index, petgraph::Direction::Incoming) .for_each(|reference_node_index| { let id = self.reference_graph[reference_node_index]; edit_locations.push(self.dependency_location(id)); }); edit_locations } + + // Given a reference index, returns the referenced index, if any. + fn referenced_index(&self, reference_index: PetGraphIndex) -> Option { + self.reference_graph + .neighbors_directed(reference_index, petgraph::Direction::Outgoing) + .next() + } } diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 5f08bdc0383..618ed6a9ffa 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -191,7 +191,22 @@ pub struct NodeInterner { /// Whether to track references. In regular compilations this is false, but tools set it to true. pub(crate) track_references: bool, - /// Store the location of the references in the graph + /// Store the location of the references in the graph. + /// Edges are directed from reference nodes to referenced nodes. + /// For example: + /// + /// ``` + /// let foo = 3; + /// // referenced + /// // ^ + /// // | + /// // +------------+ + /// let bar = foo; | + /// // reference | + /// // v | + /// // | | + /// // +------+ + /// ``` pub(crate) reference_graph: DiGraph, /// Tracks the index of the references in the graph diff --git a/compiler/noirc_frontend/src/resolve_locations.rs b/compiler/noirc_frontend/src/resolve_locations.rs index 5efe2e4a041..efb430b75eb 100644 --- a/compiler/noirc_frontend/src/resolve_locations.rs +++ b/compiler/noirc_frontend/src/resolve_locations.rs @@ -38,12 +38,16 @@ impl NodeInterner { location: Location, return_type_location_instead: bool, ) -> Option { - self.find_location_index(location) - .and_then(|index| self.resolve_location(index, return_type_location_instead)) - .or_else(|| self.try_resolve_trait_impl_location(location)) - .or_else(|| self.try_resolve_trait_method_declaration(location)) - .or_else(|| self.try_resolve_type_ref(location)) - .or_else(|| self.try_resolve_type_alias(location)) + // First try to find the location in the reference graph + self.find_referenced_location(location).or_else(|| { + // Otherwise fallback to the location indices + self.find_location_index(location) + .and_then(|index| self.resolve_location(index, return_type_location_instead)) + .or_else(|| self.try_resolve_trait_impl_location(location)) + .or_else(|| self.try_resolve_trait_method_declaration(location)) + .or_else(|| self.try_resolve_type_ref(location)) + .or_else(|| self.try_resolve_type_alias(location)) + }) } pub fn get_declaration_location_from(&self, location: Location) -> Option { diff --git a/tooling/lsp/src/requests/goto_declaration.rs b/tooling/lsp/src/requests/goto_declaration.rs index 730b87493b0..627cd8d203e 100644 --- a/tooling/lsp/src/requests/goto_declaration.rs +++ b/tooling/lsp/src/requests/goto_declaration.rs @@ -2,16 +2,11 @@ use std::future::{self, Future}; use crate::types::GotoDeclarationResult; use crate::LspState; -use crate::{parse_diff, resolve_workspace_for_source_path}; -use async_lsp::{ErrorCode, ResponseError}; +use async_lsp::ResponseError; -use fm::PathString; use lsp_types::request::{GotoDeclarationParams, GotoDeclarationResponse}; -use nargo::insert_all_files_for_workspace_into_file_manager; -use noirc_driver::file_manager_with_stdlib; - -use super::{position_to_location, to_lsp_location}; +use super::{process_request, to_lsp_location}; pub(crate) fn on_goto_declaration_request( state: &mut LspState, @@ -25,42 +20,12 @@ fn on_goto_definition_inner( state: &mut LspState, params: GotoDeclarationParams, ) -> Result { - let file_path = - params.text_document_position_params.text_document.uri.to_file_path().map_err(|_| { - ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") - })?; - - let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); - let package = workspace.members.first().unwrap(); - - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); - insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); - let parsed_files = parse_diff(&workspace_file_manager, state); - - let (mut context, crate_id) = - crate::prepare_package(&workspace_file_manager, &parsed_files, package); - - let package_root_path = package.root_dir.as_os_str().to_string_lossy().into_owned(); - let interner = if let Some(def_interner) = state.cached_definitions.get(&package_root_path) { - def_interner - } else { - // We ignore the warnings and errors produced by compilation while resolving the definition - let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, false); - &context.def_interner - }; - - let files = workspace_file_manager.as_file_map(); - let file_path = PathString::from(file_path); - let search_for_location = - position_to_location(files, &file_path, ¶ms.text_document_position_params.position)?; - - let goto_declaration_response = - interner.get_declaration_location_from(search_for_location).and_then(|found_location| { + process_request(state, params.text_document_position_params, |location, interner, files| { + interner.get_declaration_location_from(location).and_then(|found_location| { let file_id = found_location.file; let definition_position = to_lsp_location(files, file_id, found_location.span)?; let response = GotoDeclarationResponse::from(definition_position).to_owned(); Some(response) - }); - - Ok(goto_declaration_response) + }) + }) } diff --git a/tooling/lsp/src/requests/goto_definition.rs b/tooling/lsp/src/requests/goto_definition.rs index 81b1e7d5d8d..158b8a1dc35 100644 --- a/tooling/lsp/src/requests/goto_definition.rs +++ b/tooling/lsp/src/requests/goto_definition.rs @@ -1,16 +1,12 @@ use std::future::{self, Future}; -use crate::{parse_diff, resolve_workspace_for_source_path}; use crate::{types::GotoDefinitionResult, LspState}; -use async_lsp::{ErrorCode, ResponseError}; +use async_lsp::ResponseError; -use fm::PathString; use lsp_types::request::GotoTypeDefinitionParams; use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse}; -use nargo::insert_all_files_for_workspace_into_file_manager; -use noirc_driver::file_manager_with_stdlib; -use super::{position_to_location, to_lsp_location}; +use super::{process_request, to_lsp_location}; pub(crate) fn on_goto_definition_request( state: &mut LspState, @@ -33,85 +29,68 @@ fn on_goto_definition_inner( params: GotoDefinitionParams, return_type_location_instead: bool, ) -> Result { - let file_path = - params.text_document_position_params.text_document.uri.to_file_path().map_err(|_| { - ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") - })?; - - let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); - let package = workspace.members.first().unwrap(); - - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); - insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); - let parsed_files = parse_diff(&workspace_file_manager, state); - - let (mut context, crate_id) = - crate::prepare_package(&workspace_file_manager, &parsed_files, package); - - let package_root_path = package.root_dir.as_os_str().to_string_lossy().into_owned(); - let interner = if let Some(def_interner) = state.cached_definitions.get(&package_root_path) { - def_interner - } else { - // We ignore the warnings and errors produced by compilation while resolving the definition - let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, false); - &context.def_interner - }; - - let files = workspace_file_manager.as_file_map(); - let file_path = PathString::from(file_path); - let search_for_location = - position_to_location(files, &file_path, ¶ms.text_document_position_params.position)?; - - let goto_definition_response = interner - .get_definition_location_from(search_for_location, return_type_location_instead) - .and_then(|found_location| { - let file_id = found_location.file; - let definition_position = to_lsp_location(files, file_id, found_location.span)?; - let response = GotoDefinitionResponse::from(definition_position).to_owned(); - Some(response) - }); - - Ok(goto_definition_response) + process_request(state, params.text_document_position_params, |location, interner, files| { + interner.get_definition_location_from(location, return_type_location_instead).and_then( + |found_location| { + let file_id = found_location.file; + let definition_position = to_lsp_location(files, file_id, found_location.span)?; + let response = GotoDefinitionResponse::from(definition_position).to_owned(); + Some(response) + }, + ) + }) } #[cfg(test)] mod goto_definition_tests { use std::panic; - use crate::test_utils; - use lsp_types::{Position, Range}; + use crate::test_utils::{self, search_in_file}; use tokio::test; use super::*; - #[test] - async fn test_on_goto_definition() { - let (mut state, noir_text_document) = test_utils::init_lsp_server("go_to_definition").await; + async fn expect_goto(directory: &str, name: &str, definition_index: usize) { + let (mut state, noir_text_document) = test_utils::init_lsp_server(directory).await; + + let ranges = search_in_file(noir_text_document.path(), name); + let expected_range = ranges[definition_index]; + + for (index, range) in ranges.iter().enumerate() { + // Ideally "go to" at the definition should return the same location, but this isn't currently + // working. But it's also not that important, so we'll keep it for later. + if index == definition_index { + continue; + } + + let params = GotoDefinitionParams { + text_document_position_params: lsp_types::TextDocumentPositionParams { + text_document: lsp_types::TextDocumentIdentifier { + uri: noir_text_document.clone(), + }, + position: range.start, + }, + work_done_progress_params: Default::default(), + partial_result_params: Default::default(), + }; + + let response = on_goto_definition_request(&mut state, params) + .await + .expect("Could execute on_goto_definition_request") + .unwrap_or_else(|| { + panic!("Didn't get a goto definition response for index {index}") + }); + + if let GotoDefinitionResponse::Scalar(location) = response { + assert_eq!(location.range, expected_range); + } else { + panic!("Expected a scalar response"); + }; + } + } - let params = GotoDefinitionParams { - text_document_position_params: lsp_types::TextDocumentPositionParams { - text_document: lsp_types::TextDocumentIdentifier { uri: noir_text_document }, - position: Position { line: 9, character: 12 }, // Right at the beginning of "another_function" - }, - work_done_progress_params: Default::default(), - partial_result_params: Default::default(), - }; - - let response: GotoDefinitionResponse = on_goto_definition_request(&mut state, params) - .await - .expect("Could execute on_goto_definition_request") - .expect("Didn't get a goto definition response"); - - if let GotoDefinitionResponse::Scalar(location) = response { - assert_eq!( - location.range, - Range { - start: Position { line: 4, character: 3 }, - end: Position { line: 4, character: 19 }, - } - ); - } else { - panic!("Expected a scalar response"); - }; + #[test] + async fn goto_from_function_location_to_declaration() { + expect_goto("go_to_definition", "another_function", 0).await; } } diff --git a/tooling/lsp/src/requests/mod.rs b/tooling/lsp/src/requests/mod.rs index 545b5fef3d2..1d4b0982f93 100644 --- a/tooling/lsp/src/requests/mod.rs +++ b/tooling/lsp/src/requests/mod.rs @@ -1,13 +1,20 @@ use std::future::Future; -use crate::types::{CodeLensOptions, InitializeParams}; +use crate::{ + parse_diff, resolve_workspace_for_source_path, + types::{CodeLensOptions, InitializeParams}, +}; use async_lsp::{ErrorCode, ResponseError}; use fm::{codespan_files::Error, FileMap, PathString}; use lsp_types::{ - DeclarationCapability, Location, Position, TextDocumentSyncCapability, TextDocumentSyncKind, - TypeDefinitionProviderCapability, Url, WorkDoneProgressOptions, + DeclarationCapability, Location, Position, TextDocumentPositionParams, + TextDocumentSyncCapability, TextDocumentSyncKind, TypeDefinitionProviderCapability, Url, + WorkDoneProgressOptions, }; +use nargo::insert_all_files_for_workspace_into_file_manager; use nargo_fmt::Config; +use noirc_driver::file_manager_with_stdlib; +use noirc_frontend::macros_api::NodeInterner; use serde::{Deserialize, Serialize}; use crate::{ @@ -251,6 +258,51 @@ pub(crate) fn on_shutdown( async { Ok(()) } } +pub(crate) fn process_request( + state: &mut LspState, + text_document_position_params: TextDocumentPositionParams, + callback: F, +) -> Result +where + F: FnOnce(noirc_errors::Location, &NodeInterner, &FileMap) -> T, +{ + let file_path = + text_document_position_params.text_document.uri.to_file_path().map_err(|_| { + ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") + })?; + + let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); + let package = workspace.members.first().unwrap(); + + let package_root_path: String = package.root_dir.as_os_str().to_string_lossy().into(); + + let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); + insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); + let parsed_files = parse_diff(&workspace_file_manager, state); + + let (mut context, crate_id) = + crate::prepare_package(&workspace_file_manager, &parsed_files, package); + + let interner; + if let Some(def_interner) = state.cached_definitions.get(&package_root_path) { + interner = def_interner; + } else { + // We ignore the warnings and errors produced by compilation while resolving the definition + let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, false); + interner = &context.def_interner; + } + + let files = context.file_manager.as_file_map(); + + let location = position_to_location( + files, + &PathString::from(file_path), + &text_document_position_params.position, + )?; + + Ok(callback(location, interner, files)) +} + #[cfg(test)] mod initialization { use acvm::blackbox_solver::StubbedBlackBoxSolver; diff --git a/tooling/lsp/src/requests/rename.rs b/tooling/lsp/src/requests/rename.rs index e073178598b..729c51c6b19 100644 --- a/tooling/lsp/src/requests/rename.rs +++ b/tooling/lsp/src/requests/rename.rs @@ -3,26 +3,21 @@ use std::{ future::{self, Future}, }; -use async_lsp::{ErrorCode, ResponseError}; -use fm::FileMap; +use async_lsp::ResponseError; use lsp_types::{ PrepareRenameResponse, RenameParams, TextDocumentPositionParams, TextEdit, Url, WorkspaceEdit, }; -use nargo::insert_all_files_for_workspace_into_file_manager; -use noirc_driver::file_manager_with_stdlib; -use noirc_errors::Location; -use noirc_frontend::macros_api::NodeInterner; -use crate::{parse_diff, resolve_workspace_for_source_path, LspState}; +use crate::LspState; -use super::{position_to_byte_index, to_lsp_location}; +use super::{process_request, to_lsp_location}; pub(crate) fn on_prepare_rename_request( state: &mut LspState, params: TextDocumentPositionParams, ) -> impl Future, ResponseError>> { - let result = process_rename_request(state, params, |search_for_location, interner, _| { - let rename_possible = interner.check_rename_possible(search_for_location); + let result = process_request(state, params, |location, interner, _| { + let rename_possible = interner.is_location_known(location); Some(PrepareRenameResponse::DefaultBehavior { default_behavior: rename_possible }) }); future::ready(result) @@ -32,34 +27,31 @@ pub(crate) fn on_rename_request( state: &mut LspState, params: RenameParams, ) -> impl Future, ResponseError>> { - let result = process_rename_request( - state, - params.text_document_position, - |search_for_location, interner, files| { - let rename_changes = - interner.find_rename_symbols_at(search_for_location).map(|locations| { - let rs = locations.iter().fold( - HashMap::new(), - |mut acc: HashMap>, location| { - let file_id = location.file; - let span = location.span; - - let Some(lsp_location) = to_lsp_location(files, file_id, span) else { - return acc; - }; - - let edit = TextEdit { - range: lsp_location.range, - new_text: params.new_name.clone(), - }; - - acc.entry(lsp_location.uri).or_default().push(edit); - - acc - }, - ); - rs - }); + let result = + process_request(state, params.text_document_position, |location, interner, files| { + let rename_changes = interner.find_all_references(location).map(|locations| { + let rs = locations.iter().fold( + HashMap::new(), + |mut acc: HashMap>, location| { + let file_id = location.file; + let span = location.span; + + let Some(lsp_location) = to_lsp_location(files, file_id, span) else { + return acc; + }; + + let edit = TextEdit { + range: lsp_location.range, + new_text: params.new_name.clone(), + }; + + acc.entry(lsp_location.uri).or_default().push(edit); + + acc + }, + ); + rs + }); let response = WorkspaceEdit { changes: rename_changes, @@ -68,98 +60,24 @@ pub(crate) fn on_rename_request( }; Some(response) - }, - ); + }); future::ready(result) } -fn process_rename_request( - state: &mut LspState, - text_document_position_params: TextDocumentPositionParams, - callback: F, -) -> Result -where - F: FnOnce(Location, &NodeInterner, &FileMap) -> T, -{ - let file_path = - text_document_position_params.text_document.uri.to_file_path().map_err(|_| { - ResponseError::new(ErrorCode::REQUEST_FAILED, "URI is not a valid file path") - })?; - - let workspace = resolve_workspace_for_source_path(file_path.as_path()).unwrap(); - let package = workspace.members.first().unwrap(); - - let package_root_path: String = package.root_dir.as_os_str().to_string_lossy().into(); - - let mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir); - insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); - let parsed_files = parse_diff(&workspace_file_manager, state); - - let (mut context, crate_id) = - crate::prepare_package(&workspace_file_manager, &parsed_files, package); - - let interner; - if let Some(def_interner) = state.cached_definitions.get(&package_root_path) { - interner = def_interner; - } else { - // We ignore the warnings and errors produced by compilation while resolving the definition - let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, false); - interner = &context.def_interner; - } - - let files = context.file_manager.as_file_map(); - let file_id = context.file_manager.name_to_id(file_path.clone()).ok_or(ResponseError::new( - ErrorCode::REQUEST_FAILED, - format!("Could not find file in file manager. File path: {:?}", file_path), - ))?; - let byte_index = - position_to_byte_index(files, file_id, &text_document_position_params.position).map_err( - |err| { - ResponseError::new( - ErrorCode::REQUEST_FAILED, - format!("Could not convert position to byte index. Error: {:?}", err), - ) - }, - )?; - - let search_for_location = noirc_errors::Location { - file: file_id, - span: noirc_errors::Span::single_char(byte_index as u32), - }; - - Ok(callback(search_for_location, interner, files)) -} - #[cfg(test)] mod rename_tests { use super::*; - use crate::test_utils; - use lsp_types::{Position, Range, WorkDoneProgressParams}; + use crate::test_utils::{self, search_in_file}; + use lsp_types::{Range, WorkDoneProgressParams}; use tokio::test; async fn check_rename_succeeds(directory: &str, name: &str) { let (mut state, noir_text_document) = test_utils::init_lsp_server(directory).await; - let main_path = noir_text_document.path(); - // First we find out all of the occurrences of `name` in the main.nr file. // Note that this only works if that name doesn't show up in other places where we don't // expect a rename, but we craft our tests to avoid that. - let file_contents = std::fs::read_to_string(main_path) - .unwrap_or_else(|_| panic!("Couldn't read file {}", main_path)); - let file_lines: Vec<&str> = file_contents.lines().collect(); - let ranges: Vec<_> = file_lines - .iter() - .enumerate() - .filter_map(|(line_num, line)| { - line.find(name).map(|index| { - let start = Position { line: line_num as u32, character: index as u32 }; - let end = - Position { line: line_num as u32, character: (index + name.len()) as u32 }; - Range { start, end } - }) - }) - .collect(); + let ranges = search_in_file(noir_text_document.path(), name); // Test renaming works on any instance of the symbol. for target_range in &ranges { diff --git a/tooling/lsp/src/test_utils.rs b/tooling/lsp/src/test_utils.rs index dcaec2fd615..fd1a9090965 100644 --- a/tooling/lsp/src/test_utils.rs +++ b/tooling/lsp/src/test_utils.rs @@ -1,7 +1,7 @@ use crate::LspState; use acvm::blackbox_solver::StubbedBlackBoxSolver; use async_lsp::ClientSocket; -use lsp_types::Url; +use lsp_types::{Position, Range, Url}; pub(crate) async fn init_lsp_server(directory: &str) -> (LspState, Url) { let client = ClientSocket::new_closed(); @@ -37,3 +37,25 @@ pub(crate) async fn init_lsp_server(directory: &str) -> (LspState, Url) { (state, noir_text_document) } + +/// Searches for all instances of `search_string` in file `file_name` and returns a list of their locations. +pub(crate) fn search_in_file(filename: &str, search_string: &str) -> Vec { + let file_contents = std::fs::read_to_string(filename) + .unwrap_or_else(|_| panic!("Couldn't read file {}", filename)); + let file_lines: Vec<&str> = file_contents.lines().collect(); + file_lines + .iter() + .enumerate() + .filter_map(|(line_num, line)| { + // Note: this only finds the first instance of `search_string` on this line. + line.find(search_string).map(|index| { + let start = Position { line: line_num as u32, character: index as u32 }; + let end = Position { + line: line_num as u32, + character: (index + search_string.len()) as u32, + }; + Range { start, end } + }) + }) + .collect() +} diff --git a/tooling/lsp/test_programs/go_to_definition/src/main.nr b/tooling/lsp/test_programs/go_to_definition/src/main.nr index c27f8fed868..01ea5a3eadf 100644 --- a/tooling/lsp/test_programs/go_to_definition/src/main.nr +++ b/tooling/lsp/test_programs/go_to_definition/src/main.nr @@ -1,9 +1,13 @@ -fn some_function() -> Field { - 1 + 2 +mod foo { + pub fn another_function() -> Field { + 3 + 4 + } } -fn another_function() -> Field { - 3 + 4 +use foo::another_function; + +fn some_function() -> Field { + 1 + 2 } fn main() {