Skip to content

Commit

Permalink
Merge branch 'main' into ashar/config
Browse files Browse the repository at this point in the history
  • Loading branch information
coder3101 authored Dec 26, 2024
2 parents 78808b7 + bb41aab commit 0db5ddf
Show file tree
Hide file tree
Showing 21 changed files with 473 additions and 40 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "protols"
description = "Language server for proto3 files"
version = "0.7.1"
version = "0.8.0"
edition = "2021"
license = "MIT"
homepage = "https://github.com/coder3101/protols"
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
- ✅ Go to Definition
- ✅ Hover Information
- ✅ Rename Symbols
- ✅ Find references

## 🚀 Getting Started

### Installation

#### For Neovim

To install Protols, run:
You can install [protols with mason.nvim](https://github.com/mason-org/mason-registry/blob/main/packages/protols/package.yaml) or directly from crates.io with:

```bash
cargo install protols
Expand Down Expand Up @@ -71,6 +72,10 @@ Displays comments and documentation for protobuf symbols on hover. Works seamles

Allows renaming of symbols like messages and enums, along with all their usages across packages. Currently, renaming fields within symbols is not supported directly.

### Find References

Allows user defined types like messages and enums can be checked for references. Nested fields are completely supported.

---

Protols is designed to supercharge your workflow with **proto** files. We welcome contributions and feedback from the community! Feel free to check out the [repository](https://github.com/coder3101/protols) and join in on improving this tool! 🎉
44 changes: 41 additions & 3 deletions src/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use async_lsp::lsp_types::{
DocumentSymbolParams, DocumentSymbolResponse, FileOperationFilter, FileOperationPattern,
FileOperationPatternKind, FileOperationRegistrationOptions, GotoDefinitionParams,
GotoDefinitionResponse, Hover, HoverContents, HoverParams, HoverProviderCapability,
InitializeParams, InitializeResult, OneOf, PrepareRenameResponse, ProgressParams,
RenameFilesParams, RenameOptions, RenameParams, ServerCapabilities, ServerInfo,
InitializeParams, InitializeResult, Location, OneOf, PrepareRenameResponse, ProgressParams,
ReferenceParams, RenameFilesParams, RenameOptions, RenameParams, ServerCapabilities, ServerInfo,
TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url,
WorkspaceEdit, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
WorkspaceServerCapabilities,
Expand Down Expand Up @@ -126,6 +126,7 @@ impl LanguageServer for ProtoLanguageServer {
rename_provider: Some(rename_provider),
document_formatting_provider: Some(OneOf::Left(true)),
document_range_formatting_provider: Some(OneOf::Left(true)),
references_provider: Some(OneOf::Left(true)),

..ServerCapabilities::default()
},
Expand All @@ -151,7 +152,7 @@ impl LanguageServer for ProtoLanguageServer {
};

let content = self.state.get_content(&uri);
let identifier = tree.get_actionable_node_text_at_position(&pos, content.as_bytes());
let identifier = tree.get_hoverable_node_text_at_position(&pos, content.as_bytes());
let current_package_name = tree.get_package_name(content.as_bytes());

let Some(identifier) = identifier else {
Expand Down Expand Up @@ -267,6 +268,43 @@ impl LanguageServer for ProtoLanguageServer {
Box::pin(async move { Ok(response) })
}

fn references(
&mut self,
param: ReferenceParams,
) -> BoxFuture<'static, Result<Option<Vec<Location>>, ResponseError>> {
let uri = param.text_document_position.text_document.uri;
let pos = param.text_document_position.position;

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

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 Some((mut refs, otext)) = tree.reference_tree(&pos, content.as_bytes()) else {
error!(uri=%uri, "failed to find references in a tree");
return Box::pin(async move { Ok(None) });
};

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

Box::pin(async move {
if refs.is_empty() {
Ok(None)
} else {
Ok(Some(refs))
}
})
}

fn definition(
&mut self,
param: GotoDefinitionParams,
Expand Down
31 changes: 31 additions & 0 deletions src/parser/input/test_reference.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
syntax = "proto3";

package com.parser;

// A Book is book
message Book {

// This is represents author
// A author is a someone who writes books
//
// Author has a name and a country where they were born
message Author {
string name = 1;
string country = 2;
};
Author author = 1;
int price_usd = 2;
}

message BookShelf {}

message Library {
repeated Book books = 1;
Book.Author collection = 2;
BookShelf shelf = 3;
}

service Myservice {
rpc GetBook(Empty) returns (Book);
rpc GetAuthor(Empty) returns (Book.Author)
}
3 changes: 3 additions & 0 deletions src/parser/input/test_rename.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ message Book {
int price_usd = 2;
}

message BookShelf {}

message Library {
repeated Book books = 1;
Book.Author collection = 2;
BookShelf shelf = 3;
}

service Myservice {
Expand Down
114 changes: 97 additions & 17 deletions src/parser/rename.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use async_lsp::lsp_types::{Position, Range, TextEdit};
use async_lsp::lsp_types::{Location, Position, Range, TextEdit};
use tree_sitter::Node;

use crate::{nodekind::NodeKind, utils::ts_to_lsp_position};
Expand All @@ -21,28 +21,52 @@ impl ParsedTree {
})
}

fn rename_within(
fn nodes_within<'a>(
&self,
n: Node<'_>,
n: Node<'a>,
identifier: &str,
new_identifier: &str,
content: impl AsRef<[u8]>,
) -> Option<Vec<TextEdit>> {
) -> Option<Vec<Node<'a>>> {
n.parent().map(|p| {
self.filter_nodes_from(p, NodeKind::is_field_name)
.into_iter()
.filter(|i| i.utf8_text(content.as_ref()).expect("utf-8 parse error") == identifier)
.map(|i| TextEdit {
range: Range {
start: ts_to_lsp_position(&i.start_position()),
end: ts_to_lsp_position(&i.end_position()),
},
new_text: new_identifier.to_string(),
})
.collect()
})
}

pub fn reference_tree(
&self,
pos: &Position,
content: impl AsRef<[u8]>,
) -> Option<(Vec<Location>, String)> {
let rename_range = self.can_rename(pos)?;

let mut res = vec![Location {
uri: self.uri.clone(),
range: rename_range,
}];

let nodes = self.get_ancestor_nodes_at_position(pos);
let mut i = 1;
let mut otext = nodes.first()?.utf8_text(content.as_ref()).ok()?.to_owned();
while nodes.len() > i {
let id = nodes[i].utf8_text(content.as_ref()).ok()?;
if let Some(inodes) = self.nodes_within(nodes[i], &otext, content.as_ref()) {
res.extend(inodes.into_iter().map(|n| Location {
uri: self.uri.clone(),
range: Range {
start: ts_to_lsp_position(&n.start_position()),
end: ts_to_lsp_position(&n.end_position()),
},
}))
}
otext = format!("{id}.{otext}");
i += 1
}
Some((res, otext))
}

pub fn rename_tree(
&self,
pos: &Position,
Expand All @@ -65,8 +89,14 @@ impl ParsedTree {
while nodes.len() > i {
let id = nodes[i].utf8_text(content.as_ref()).ok()?;

if let Some(edit) = self.rename_within(nodes[i], &otext, &ntext, content.as_ref()) {
v.extend(edit);
if let Some(inodes) = self.nodes_within(nodes[i], &otext, content.as_ref()) {
v.extend(inodes.into_iter().map(|n| TextEdit {
range: Range {
start: ts_to_lsp_position(&n.start_position()),
end: ts_to_lsp_position(&n.end_position()),
},
new_text: ntext.to_owned(),
}));
}

otext = format!("{id}.{otext}");
Expand All @@ -87,9 +117,9 @@ impl ParsedTree {
self.filter_nodes(NodeKind::is_field_name)
.into_iter()
.filter(|n| {
n.utf8_text(content.as_ref())
.expect("utf-8 parse error")
.starts_with(old_identifier)
let ntext = n.utf8_text(content.as_ref()).expect("utf-8 parse error");
let sc = format!("{old_identifier}.");
return ntext == old_identifier || ntext.starts_with(&sc);
})
.map(|n| {
let text = n.utf8_text(content.as_ref()).expect("utf-8 parse error");
Expand All @@ -103,6 +133,20 @@ impl ParsedTree {
})
.collect()
}

pub fn reference_field(&self, id: &str, content: impl AsRef<[u8]>) -> Vec<Location> {
self.filter_nodes(NodeKind::is_field_name)
.into_iter()
.filter(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error") == id)
.map(|n| Location {
uri: self.uri.clone(),
range: Range {
start: ts_to_lsp_position(&n.start_position()),
end: ts_to_lsp_position(&n.end_position()),
},
})
.collect()
}
}

#[cfg(test)]
Expand Down Expand Up @@ -148,6 +192,42 @@ mod test {
assert_yaml_snapshot!(rename_fn("xyx", &pos_non_rename));
}

#[test]
fn test_reference() {
let uri: Url = "file://foo/bar.proto".parse().unwrap();
let pos_book = Position {
line: 5,
character: 9,
};
let pos_author = Position {
line: 11,
character: 14,
};
let pos_non_ref = Position {
line: 21,
character: 5,
};
let contents = include_str!("input/test_reference.proto");

let parsed = ProtoParser::new().parse(uri.clone(), contents);
assert!(parsed.is_some());
let tree = parsed.unwrap();

let reference_fn = |pos: &Position| {
if let Some(k) = tree.reference_tree(pos, contents) {
let mut v = tree.reference_field(&k.1, contents);
v.extend(k.0);
v
} else {
vec![]
}
};

assert_yaml_snapshot!(reference_fn(&pos_book));
assert_yaml_snapshot!(reference_fn(&pos_author));
assert_yaml_snapshot!(reference_fn(&pos_non_ref));
}

#[test]
fn test_can_rename() {
let uri: Url = "file://foo/bar/test.proto".parse().unwrap();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
source: src/parser/rename.rs
expression: reference_fn(&pos_author)
---
- uri: "file://foo/bar.proto"
range:
start:
line: 23
character: 2
end:
line: 23
character: 13
- uri: "file://foo/bar.proto"
range:
start:
line: 29
character: 32
end:
line: 29
character: 43
- uri: "file://foo/bar.proto"
range:
start:
line: 11
character: 10
end:
line: 11
character: 16
- uri: "file://foo/bar.proto"
range:
start:
line: 15
character: 2
end:
line: 15
character: 8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: src/parser/rename.rs
expression: reference_fn(&pos_non_ref)
---
[]
28 changes: 28 additions & 0 deletions src/parser/snapshots/protols__parser__rename__test__reference.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
source: src/parser/rename.rs
expression: reference_fn(&pos_book)
---
- uri: "file://foo/bar.proto"
range:
start:
line: 22
character: 11
end:
line: 22
character: 15
- uri: "file://foo/bar.proto"
range:
start:
line: 28
character: 30
end:
line: 28
character: 34
- uri: "file://foo/bar.proto"
range:
start:
line: 5
character: 8
end:
line: 5
character: 12
Loading

0 comments on commit 0db5ddf

Please sign in to comment.