Skip to content

fix: nothing works in a file without a package #77

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 2 commits into from
May 3, 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
14 changes: 14 additions & 0 deletions sample/test.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,17 @@ message SomeMessage {

CustomType another = 2;
}

message CapitalA {
// B is a b
message CapitalB {

}

a.b.c.CapitalA.CapitalB b = 1;
}

message C {
CapitalA.CapitalB ab = 1;
.a.b.c.CapitalA a = 2;
}
5 changes: 2 additions & 3 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ pub struct ProtolsConfig {
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FormatterConfig {
}
pub struct FormatterConfig {}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(default)]
Expand All @@ -38,7 +37,7 @@ impl Default for PathConfig {
fn default() -> Self {
Self {
clang_format: default_clang_format_path(),
protoc: default_protoc_path()
protoc: default_protoc_path(),
}
}
}
13 changes: 8 additions & 5 deletions src/config/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ impl WorkspaceProtoConfigs {
mod test {
use async_lsp::lsp_types::{Url, WorkspaceFolder};
use insta::assert_yaml_snapshot;
use tempfile::tempdir;
use std::path::PathBuf;
use tempfile::tempdir;

use super::{CONFIG_FILE_NAMES, WorkspaceProtoConfigs};

Expand Down Expand Up @@ -263,13 +263,16 @@ mod test {
let include_paths = ws.get_include_paths(&inworkspace).unwrap();

// Check that CLI paths are included in the result
assert!(include_paths.iter().any(|p| p.ends_with("relative/path") ||
p == &PathBuf::from("/path/to/protos")));

assert!(
include_paths
.iter()
.any(|p| p.ends_with("relative/path") || p == &PathBuf::from("/path/to/protos"))
);

// The relative path should be resolved relative to the workspace
let resolved_relative_path = tmpdir.path().join("relative/path");
assert!(include_paths.contains(&resolved_relative_path));

// The absolute path should be included as is
assert!(include_paths.contains(&PathBuf::from("/path/to/protos")));
}
Expand Down
44 changes: 16 additions & 28 deletions src/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,13 @@ impl LanguageServer for ProtoLanguageServer {

let content = self.state.get_content(&uri);
let hv = tree.get_hoverable_at_position(&pos, content.as_bytes());
let current_package_name = tree.get_package_name(content.as_bytes());
let current_package_name = tree.get_package_name(content.as_bytes()).unwrap_or(".");

let Some(hv) = hv else {
error!(uri=%uri, "failed to get hoverable identifier");
return Box::pin(async move { Ok(None) });
};

let Some(current_package_name) = current_package_name else {
error!(uri=%uri, "failed to get package name");
return Box::pin(async move { Ok(None) });
};

let ipath = self.configs.get_include_paths(&uri).unwrap_or_default();
let result = self.state.hover(&ipath, current_package_name.as_ref(), hv);

Expand Down Expand Up @@ -220,10 +215,7 @@ impl LanguageServer for ProtoLanguageServer {

let content = self.state.get_content(&uri);

let Some(current_package) = tree.get_package_name(content.as_bytes()) else {
error!(uri=%uri, "failed to get package name");
return Box::pin(async move { Ok(None) });
};
let current_package = tree.get_package_name(content.as_bytes()).unwrap_or(".");

let Some((edit, otext, ntext)) = tree.rename_tree(&pos, &new_name, content.as_bytes())
else {
Expand All @@ -240,7 +232,6 @@ impl LanguageServer for ProtoLanguageServer {
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,
Expand All @@ -249,6 +240,8 @@ impl LanguageServer for ProtoLanguageServer {
progress_sender,
));

h.entry(tree.uri).or_insert(edit.clone()).extend(edit);

let response = Some(WorkspaceEdit {
changes: Some(h),
..Default::default()
Expand All @@ -272,10 +265,7 @@ impl LanguageServer for ProtoLanguageServer {

let content = self.state.get_content(&uri);

let Some(current_package) = tree.get_package_name(content.as_bytes()) else {
error!(uri=%uri, "failed to get package name");
return Box::pin(async move { Ok(None) });
};
let current_package = tree.get_package_name(content.as_bytes()).unwrap_or(".");

let Some((mut refs, otext)) = tree.reference_tree(&pos, content.as_bytes()) else {
error!(uri=%uri, "failed to find references in a tree");
Expand Down Expand Up @@ -321,18 +311,13 @@ impl LanguageServer for ProtoLanguageServer {

let content = self.state.get_content(&uri);
let jump = tree.get_jumpable_at_position(&pos, content.as_bytes());
let current_package_name = tree.get_package_name(content.as_bytes());
let current_package_name = tree.get_package_name(content.as_bytes()).unwrap_or(".");

let Some(jump) = jump else {
error!(uri=%uri, "failed to get jump identifier");
return Box::pin(async move { Ok(None) });
};

let Some(current_package_name) = current_package_name else {
error!(uri=%uri, "failed to get package name");
return Box::pin(async move { Ok(None) });
};

let ipath = self.configs.get_include_paths(&uri).unwrap_or_default();
let locations = self
.state
Expand Down Expand Up @@ -407,9 +392,9 @@ impl LanguageServer for ProtoLanguageServer {
return ControlFlow::Continue(());
};

if let Some(diagnostics) = self
.state
.upsert_file(&uri, content, &ipath, 8, &pconf.config, true)
if let Some(diagnostics) =
self.state
.upsert_file(&uri, content, &ipath, 8, &pconf.config, true)
{
if let Err(e) = self.client.publish_diagnostics(diagnostics) {
error!(error=%e, "failed to publish diagnostics")
Expand All @@ -434,9 +419,9 @@ impl LanguageServer for ProtoLanguageServer {
return ControlFlow::Continue(());
};

if let Some(diagnostics) = self
.state
.upsert_file(&uri, content, &ipath, 8, &pconf.config, true)
if let Some(diagnostics) =
self.state
.upsert_file(&uri, content, &ipath, 8, &pconf.config, true)
{
if let Err(e) = self.client.publish_diagnostics(diagnostics) {
error!(error=%e, "failed to publish diagnostics")
Expand All @@ -457,7 +442,10 @@ impl LanguageServer for ProtoLanguageServer {
return ControlFlow::Continue(());
};

if let Some(diagnostics) = self.state.upsert_file(&uri, content, &ipath, 8, &pconf.config, false) {
if let Some(diagnostics) =
self.state
.upsert_file(&uri, content, &ipath, 8, &pconf.config, false)
{
if let Err(e) = self.client.publish_diagnostics(diagnostics) {
error!(error=%e, "failed to publish diagnostics")
}
Expand Down
1 change: 1 addition & 0 deletions src/parser/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ impl ParsedTree {
self.definition_impl(identifier, self.tree.root_node(), &mut results, content);
results
}

fn definition_impl(
&self,
identifier: &str,
Expand Down
4 changes: 1 addition & 3 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ impl ProtoLanguageState {
.values()
.filter(|tree| {
let content = self.get_content(&tree.uri);
tree.get_package_name(content.as_bytes())
.unwrap_or_default()
== package
tree.get_package_name(content.as_bytes()).unwrap_or(".") == package
})
.map(ToOwned::to_owned)
.collect()
Expand Down
1 change: 0 additions & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ pub fn is_inner_identifier(s: &str) -> bool {
}

pub fn split_identifier_package(s: &str) -> (&str, &str) {
// trim beginning dots, some use `.` prefix for fully qualified field names
let s = s.trim_start_matches(".");
if is_inner_identifier(s) || !s.contains('.') {
return ("", s);
Expand Down
65 changes: 56 additions & 9 deletions src/workspace/rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,42 @@ impl ProtoLanguageState {
.into_iter()
.fold(HashMap::new(), |mut h, tree| {
let content = self.get_content(&tree.uri);
let package = tree.get_package_name(content.as_ref()).unwrap_or_default();
let package = tree.get_package_name(content.as_ref()).unwrap_or(".");
let mut old = identifier.to_string();
let mut new = new_text.to_string();
if current_package != package {
old = format!("{current_package}.{old}");
new = format!("{current_package}.{new}");
let mut v = vec![];

// Global scope: Reference by only . or within global directly
if current_package == "." {
if package == "." {
v.extend(tree.rename_field(&old, &new, content.as_str()));
}

old = format!(".{old}");
new = format!(".{new}");

v.extend(tree.rename_field(&old, &new, content.as_str()));

if !v.is_empty() {
h.insert(tree.uri.clone(), v);
}
return h;
}
let v = tree.rename_field(&old, &new, content.as_str());

let full_old = format!("{current_package}.{old}");
let full_new = format!("{current_package}.{new}");
let global_full_old = format!(".{current_package}.{old}");
let global_full_new = format!(".{current_package}.{new}");

// Current package: Reference by full or relative name or directly
if current_package == package {
v.extend(tree.rename_field(&old, &new, content.as_str()));
}

// Otherwise, full reference
v.extend(tree.rename_field(&full_old, &full_new, content.as_str()));
v.extend(tree.rename_field(&global_full_old, &global_full_new, content.as_str()));

if !v.is_empty() {
h.insert(tree.uri.clone(), v);
}
Expand All @@ -52,12 +80,31 @@ impl ProtoLanguageState {
.into_iter()
.fold(Vec::<Location>::new(), |mut v, tree| {
let content = self.get_content(&tree.uri);
let package = tree.get_package_name(content.as_ref()).unwrap_or_default();
let package = tree.get_package_name(content.as_ref()).unwrap_or(".");
let mut old = identifier.to_owned();
if current_package != package {
old = format!("{current_package}.{old}");
// Global scope: Reference by only . or within global directly
if current_package == "." {
if package == "." {
v.extend(tree.reference_field(&old, content.as_str()));
}

old = format!(".{old}");
v.extend(tree.reference_field(&old, content.as_str()));

return v;
}
v.extend(tree.reference_field(&old, content.as_str()));

let full_old = format!("{current_package}.{old}");
let global_full_old = format!(".{current_package}.{old}");

// Current package: Reference by full or relative name or directly
if current_package == package {
v.extend(tree.reference_field(&old, content.as_str()));
}

// Otherwise, full reference
v.extend(tree.reference_field(&full_old, content.as_str()));
v.extend(tree.reference_field(&global_full_old, content.as_str()));
v
});
if r.is_empty() { None } else { Some(r) }
Expand Down