Skip to content

feat: Parse whole workspace before sending rename and reference result #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions src/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,23 @@ impl LanguageServer for ProtoLanguageServer {
return Box::pin(async move { Ok(None) });
};

let Some(workspace) = self.configs.get_workspace_for_uri(&uri) else {
error!(uri=%uri, "failed to get workspace");
return Box::pin(async move { Ok(None) });
};

let work_done_token = params.work_done_progress_params.work_done_token;
let progress_sender = work_done_token.map(|token| self.with_report_progress(token));

let mut h = HashMap::new();
h.insert(tree.uri.clone(), edit);
h.extend(self.state.rename_fields(current_package, &otext, &ntext));
h.extend(self.state.rename_fields(
current_package,
&otext,
&ntext,
workspace.to_file_path().unwrap(),
progress_sender,
));

let response = Some(WorkspaceEdit {
changes: Some(h),
Expand All @@ -246,6 +260,7 @@ impl LanguageServer for ProtoLanguageServer {
) -> BoxFuture<'static, Result<Option<Vec<Location>>, ResponseError>> {
let uri = param.text_document_position.text_document.uri;
let pos = param.text_document_position.position;
let work_done_token = param.work_done_progress_params.work_done_token;

let Some(tree) = self.state.get_tree(&uri) else {
error!(uri=%uri, "failed to get tree");
Expand All @@ -264,7 +279,19 @@ impl LanguageServer for ProtoLanguageServer {
return Box::pin(async move { Ok(None) });
};

if let Some(v) = self.state.reference_fields(current_package, &otext) {
let Some(workspace) = self.configs.get_workspace_for_uri(&uri) else {
error!(uri=%uri, "failed to get workspace");
return Box::pin(async move { Ok(None) });
};

let progress_sender = work_done_token.map(|token| self.with_report_progress(token));

if let Some(v) = self.state.reference_fields(
current_package,
&otext,
workspace.to_file_path().unwrap(),
progress_sender,
) {
refs.extend(v);
}

Expand Down
3 changes: 1 addition & 2 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ impl ProtoLanguageServer {
ControlFlow::Continue(())
}

#[allow(unused)]
fn with_report_progress(&self, token: NumberOrString) -> Sender<ProgressParamsValue> {
pub fn with_report_progress(&self, token: NumberOrString) -> Sender<ProgressParamsValue> {
let (tx, rx) = mpsc::channel();
let mut socket = self.client.clone();

Expand Down
141 changes: 64 additions & 77 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ use std::{
};
use tracing::info;

use async_lsp::lsp_types::ProgressParamsValue;
use async_lsp::lsp_types::{CompletionItem, CompletionItemKind, PublishDiagnosticsParams, Url};
use std::sync::mpsc::Sender;
use tree_sitter::Node;
use walkdir::WalkDir;

use crate::{
nodekind::NodeKind,
Expand All @@ -17,6 +20,7 @@ pub struct ProtoLanguageState {
documents: Arc<RwLock<HashMap<Url, String>>>,
trees: Arc<RwLock<HashMap<Url, ParsedTree>>>,
parser: Arc<Mutex<ProtoParser>>,
parsed_workspaces: Arc<RwLock<HashSet<String>>>,
}

impl ProtoLanguageState {
Expand All @@ -25,6 +29,7 @@ impl ProtoLanguageState {
documents: Default::default(),
trees: Default::default(),
parser: Arc::new(Mutex::new(ProtoParser::new())),
parsed_workspaces: Arc::new(RwLock::new(HashSet::new())),
}
}

Expand Down Expand Up @@ -146,83 +151,65 @@ impl ProtoLanguageState {
.collect()
}

// #[allow(unused)]
// pub fn add_workspace_folder_async(
// &mut self,
// workspace: WorkspaceFolder,
// tx: Sender<ProgressParamsValue>,
// ) {
// let parser = self.parser.clone();
// let tree = self.trees.clone();
// let docs = self.documents.clone();
//
// let begin = ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(WorkDoneProgressBegin {
// title: String::from("indexing"),
// cancellable: Some(false),
// percentage: Some(0),
// ..Default::default()
// }));
//
// if let Err(e) = tx.send(begin) {
// error!(error=%e, "failed to send work begin progress");
// }
//
// thread::spawn(move || {
// let files: Vec<_> = WalkDir::new(workspace.uri.path())
// .into_iter()
// .filter_map(|e| e.ok())
// .filter(|e| e.path().extension().is_some())
// .filter(|e| e.path().extension().unwrap() == "proto")
// .collect();
//
// let total_files = files.len();
// let mut current = 0;
//
// for file in files.into_iter() {
// let path = file.path();
// if path.is_absolute() && path.is_file() {
// let Ok(content) = read_to_string(path) else {
// continue;
// };
//
// let Ok(uri) = Url::from_file_path(path) else {
// continue;
// };
//
// Self::upsert_content_impl(
// parser.lock().expect("poison"),
// &uri,
// content,
// docs.write().expect("poison"),
// tree.write().expect("poison"),
// );
//
// current += 1;
//
// let report = ProgressParamsValue::WorkDone(WorkDoneProgress::Report(
// WorkDoneProgressReport {
// cancellable: Some(false),
// message: Some(path.display().to_string()),
// percentage: Some((current * 100 / total_files) as u32),
// },
// ));
//
// if let Err(e) = tx.send(report) {
// error!(error=%e, "failed to send work report progress");
// }
// }
// }
// let report =
// ProgressParamsValue::WorkDone(WorkDoneProgress::End(WorkDoneProgressEnd {
// message: Some(String::from("completed")),
// }));
//
// info!(len = total_files, "workspace file parsing completed");
// if let Err(e) = tx.send(report) {
// error!(error=%e, "failed to send work completed result");
// }
// });
// }
pub fn parse_all_from_workspace(
&mut self,
workspace: PathBuf,
progress_sender: Option<Sender<ProgressParamsValue>>,
) {
if self
.parsed_workspaces
.read()
.expect("poison")
.contains(workspace.to_str().unwrap_or_default())
{
return;
}

let files: Vec<_> = WalkDir::new(workspace.to_str().unwrap_or_default())
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some())
.filter(|e| e.path().extension().unwrap() == "proto")
.collect();

let total_files = files.len();

for (idx, file) in files.into_iter().enumerate() {
let path = file.path();
if path.is_absolute() && path.is_file() {
if let Ok(content) = std::fs::read_to_string(path) {
if let Ok(uri) = Url::from_file_path(path) {
if self.documents.read().expect("poison").contains_key(&uri) {
continue;
}
self.upsert_content(&uri, content, &[], 1);

if let Some(sender) = &progress_sender {
let percentage = ((idx + 1) as f64 / total_files as f64 * 100.0) as u32;
let _ = sender.send(ProgressParamsValue::WorkDone(
async_lsp::lsp_types::WorkDoneProgress::Report(
async_lsp::lsp_types::WorkDoneProgressReport {
cancellable: None,
message: Some(format!(
"Parsing file {} of {}",
idx + 1,
total_files
)),
percentage: Some(percentage),
},
),
));
}
}
}
}
}

self.parsed_workspaces
.write()
.expect("poison")
.insert(workspace.to_str().unwrap_or_default().to_string());
}

pub fn upsert_file(
&mut self,
Expand Down
48 changes: 40 additions & 8 deletions src/workspace/rename.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
use crate::utils::split_identifier_package;
use std::collections::HashMap;
use std::path::PathBuf;

use async_lsp::lsp_types::{Location, TextEdit, Url};

use crate::state::ProtoLanguageState;
use async_lsp::lsp_types::ProgressParamsValue;
use std::sync::mpsc::Sender;

impl ProtoLanguageState {
pub fn rename_fields(
&self,
&mut self,
current_package: &str,
identifier: &str,
new_text: &str,
workspace: PathBuf,
progress_sender: Option<Sender<ProgressParamsValue>>,
) -> HashMap<Url, Vec<TextEdit>> {
self.parse_all_from_workspace(workspace, progress_sender);
let (_, identifier) = split_identifier_package(identifier);
self.get_trees()
.into_iter()
Expand All @@ -33,10 +39,13 @@ impl ProtoLanguageState {
}

pub fn reference_fields(
&self,
&mut self,
current_package: &str,
identifier: &str,
workspace: PathBuf,
progress_sender: Option<Sender<ProgressParamsValue>>,
) -> Option<Vec<Location>> {
self.parse_all_from_workspace(workspace, progress_sender);
let (_, identifier) = split_identifier_package(identifier);
let r = self
.get_trees()
Expand Down Expand Up @@ -79,13 +88,27 @@ mod test {
state.upsert_file(&b_uri, b.to_owned(), &ipath, 2);
state.upsert_file(&c_uri, c.to_owned(), &ipath, 2);

assert_yaml_snapshot!(state.rename_fields("com.workspace", "Author", "Writer"));
assert_yaml_snapshot!(state.rename_fields(
"com.workspace",
"Author",
"Writer",
PathBuf::from("src/workspace/input"),
None
));
assert_yaml_snapshot!(state.rename_fields(
"com.workspace",
"Author.Address",
"Author.Location"
"Author.Location",
PathBuf::from("src/workspace/input"),
None
));
assert_yaml_snapshot!(state.rename_fields(
"com.utility",
"Foobar.Baz",
"Foobar.Baaz",
PathBuf::from("src/workspace/input"),
None
));
assert_yaml_snapshot!(state.rename_fields("com.utility", "Foobar.Baz", "Foobar.Baaz"));
}

#[test]
Expand All @@ -104,8 +127,17 @@ mod test {
state.upsert_file(&b_uri, b.to_owned(), &ipath, 2);
state.upsert_file(&c_uri, c.to_owned(), &ipath, 2);

assert_yaml_snapshot!(state.reference_fields("com.workspace", "Author"));
assert_yaml_snapshot!(state.reference_fields("com.workspace", "Author.Address"));
assert_yaml_snapshot!(state.reference_fields("com.utility", "Foobar.Baz"));
assert_yaml_snapshot!(state.reference_fields(
"com.workspace",
"Author",
PathBuf::from("src/workspace/input"),
None
));
assert_yaml_snapshot!(state.reference_fields(
"com.workspace",
"Author.Address",
PathBuf::from("src/workspace/input"),
None
));
}
}