Skip to content

Commit 9e0098d

Browse files
feat: add support for renaming across workspace (#26)
Co-authored-by: mohammadkhan <[email protected]>
1 parent 2e0b86e commit 9e0098d

23 files changed

+456
-183
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "protols"
33
description = "Language server for proto3 files"
4-
version = "0.4.0"
4+
version = "0.5.0"
55
edition = "2021"
66
license = "MIT"
77
homepage = "https://github.com/coder3101/protols"

sample/simple.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ syntax = "proto3";
22

33
package com.book;
44

5+
// This is a book represeted by some comments that we like to address in the review
56
message Book {
67
// This is a multi line comment on the field name
78
// Of a message called Book

src/lsp.rs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashMap;
12
use std::fs::read_to_string;
23
use std::ops::ControlFlow;
34
use std::sync::mpsc;
@@ -11,11 +12,10 @@ use async_lsp::lsp_types::{
1112
DocumentSymbolResponse, FileOperationFilter, FileOperationPattern, FileOperationPatternKind,
1213
FileOperationRegistrationOptions, GotoDefinitionParams, GotoDefinitionResponse, Hover,
1314
HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult, OneOf,
14-
PrepareRenameResponse, ProgressParams, RenameFilesParams, RenameOptions,
15-
RenameParams, ServerCapabilities, ServerInfo, TextDocumentPositionParams,
16-
TextDocumentSyncCapability, TextDocumentSyncKind, Url, WorkspaceEdit,
17-
WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
18-
WorkspaceServerCapabilities,
15+
PrepareRenameResponse, ProgressParams, RenameFilesParams, RenameOptions, RenameParams,
16+
ServerCapabilities, ServerInfo, TextDocumentPositionParams, TextDocumentSyncCapability,
17+
TextDocumentSyncKind, Url, WorkspaceEdit, WorkspaceFileOperationsServerCapabilities,
18+
WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
1919
};
2020
use async_lsp::{LanguageClient, LanguageServer, ResponseError};
2121
use futures::future::BoxFuture;
@@ -187,7 +187,7 @@ impl LanguageServer for ProtoLanguageServer {
187187
"enum", "oneof", "repeated", "reserved", "to",
188188
];
189189

190-
let mut keywords: Vec<CompletionItem> = keywords
190+
let mut completions: Vec<CompletionItem> = keywords
191191
.into_iter()
192192
.map(|w| CompletionItem {
193193
label: w.to_string(),
@@ -199,10 +199,10 @@ impl LanguageServer for ProtoLanguageServer {
199199
if let Some(tree) = self.state.get_tree(&uri) {
200200
let content = self.state.get_content(&uri);
201201
if let Some(package_name) = tree.get_package_name(content.as_bytes()) {
202-
keywords.extend(self.state.completion_items(package_name));
202+
completions.extend(self.state.completion_items(package_name));
203203
}
204204
}
205-
Box::pin(async move { Ok(Some(CompletionResponse::Array(keywords))) })
205+
Box::pin(async move { Ok(Some(CompletionResponse::Array(completions))) })
206206
}
207207

208208
fn prepare_rename(
@@ -238,12 +238,26 @@ impl LanguageServer for ProtoLanguageServer {
238238

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

241-
let response = if tree.can_rename(&pos).is_some() {
242-
tree.rename(&pos, &new_name, content)
243-
} else {
244-
None
241+
let Some(current_package) = tree.get_package_name(content.as_bytes()) else {
242+
error!(uri=%uri, "failed to get package name");
243+
return Box::pin(async move { Ok(None) });
244+
};
245+
246+
let Some((edit, otext, ntext)) = tree.rename_tree(&pos, &new_name, content.as_bytes())
247+
else {
248+
error!(uri=%uri, "failed to rename in a tree");
249+
return Box::pin(async move { Ok(None) });
245250
};
246251

252+
let mut h = HashMap::new();
253+
h.insert(tree.uri.clone(), edit);
254+
h.extend(self.state.rename_fields(current_package, &otext, &ntext));
255+
256+
let response = Some(WorkspaceEdit {
257+
changes: Some(h),
258+
..Default::default()
259+
});
260+
247261
Box::pin(async move { Ok(response) })
248262
}
249263

src/nodekind.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub enum NodeKind {
55
Identifier,
66
Error,
77
MessageName,
8+
Message,
89
EnumName,
910
FieldName,
1011
ServiceName,
@@ -19,6 +20,7 @@ impl NodeKind {
1920
NodeKind::Identifier => "identifier",
2021
NodeKind::Error => "ERROR",
2122
NodeKind::MessageName => "message_name",
23+
NodeKind::Message => "message",
2224
NodeKind::EnumName => "enum_name",
2325
NodeKind::FieldName => "message_or_enum_type",
2426
NodeKind::ServiceName => "service_name",
@@ -47,6 +49,14 @@ impl NodeKind {
4749
n.kind() == Self::MessageName.as_str()
4850
}
4951

52+
pub fn is_message(n: &Node) -> bool {
53+
n.kind() == Self::Message.as_str()
54+
}
55+
56+
pub fn is_field_name(n: &Node) -> bool {
57+
n.kind() == Self::FieldName.as_str()
58+
}
59+
5060
pub fn is_userdefined(n: &Node) -> bool {
5161
n.kind() == Self::EnumName.as_str() || n.kind() == Self::MessageName.as_str()
5262
}

src/parser/definition.rs

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,34 +22,39 @@ impl ParsedTree {
2222
return;
2323
}
2424

25-
if !identifier.contains('.') {
26-
let locations: Vec<Location> = self
27-
.filter_nodes_from(n, NodeKind::is_userdefined)
28-
.into_iter()
29-
.filter(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error") == identifier)
30-
.map(|n| Location {
31-
uri: self.uri.clone(),
32-
range: Range {
33-
start: ts_to_lsp_position(&n.start_position()),
34-
end: ts_to_lsp_position(&n.end_position()),
35-
},
36-
})
37-
.collect();
25+
match identifier.split_once('.') {
26+
Some((parent_identifier, remaining)) => {
27+
let child_node = self
28+
.filter_nodes_from(n, NodeKind::is_userdefined)
29+
.into_iter()
30+
.find(|n| {
31+
n.utf8_text(content.as_ref()).expect("utf8-parse error")
32+
== parent_identifier
33+
})
34+
.and_then(|n| n.parent());
3835

39-
v.extend(locations);
40-
return;
41-
}
42-
43-
// Safety: identifier contains a .
44-
let (parent_identifier, remaining) = identifier.split_once('.').unwrap();
45-
let child_node = self
46-
.filter_nodes_from(n, NodeKind::is_userdefined)
47-
.into_iter()
48-
.find(|n| n.utf8_text(content.as_ref()).expect("utf8-parse error") == parent_identifier)
49-
.and_then(|n| n.parent());
36+
if let Some(inner) = child_node {
37+
self.definition_impl(remaining, inner, v, content);
38+
}
39+
}
40+
None => {
41+
let locations: Vec<Location> = self
42+
.filter_nodes_from(n, NodeKind::is_userdefined)
43+
.into_iter()
44+
.filter(|n| {
45+
n.utf8_text(content.as_ref()).expect("utf-8 parse error") == identifier
46+
})
47+
.map(|n| Location {
48+
uri: self.uri.clone(),
49+
range: Range {
50+
start: ts_to_lsp_position(&n.start_position()),
51+
end: ts_to_lsp_position(&n.end_position()),
52+
},
53+
})
54+
.collect();
5055

51-
if let Some(inner) = child_node {
52-
self.definition_impl(remaining, inner, v, content);
56+
v.extend(locations);
57+
}
5358
}
5459
}
5560
}

src/parser/hover.rs

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -64,29 +64,31 @@ impl ParsedTree {
6464
return;
6565
}
6666

67-
if !identifier.contains('.') {
68-
let comments: Vec<MarkedString> = self
69-
.filter_nodes_from(n, NodeKind::is_userdefined)
70-
.into_iter()
71-
.filter(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error") == identifier)
72-
.filter_map(|n| self.find_preceding_comments(n.id(), content.as_ref()))
73-
.map(MarkedString::String)
74-
.collect();
75-
76-
v.extend(comments);
77-
return;
78-
}
79-
80-
// Safety: identifier contains a .
81-
let (parent_identifier, remaining) = identifier.split_once('.').unwrap();
82-
let child_node = self
83-
.filter_nodes_from(n, NodeKind::is_userdefined)
84-
.into_iter()
85-
.find(|n| n.utf8_text(content.as_ref()).expect("utf8-parse error") == parent_identifier)
86-
.and_then(|n| n.parent());
87-
88-
if let Some(inner) = child_node {
89-
self.hover_impl(remaining, inner, v, content);
67+
match identifier.split_once('.') {
68+
Some((parent, child)) => {
69+
let child_node = self
70+
.filter_nodes_from(n, NodeKind::is_userdefined)
71+
.into_iter()
72+
.find(|n| n.utf8_text(content.as_ref()).expect("utf8-parse error") == parent)
73+
.and_then(|n| n.parent());
74+
75+
if let Some(inner) = child_node {
76+
self.hover_impl(child, inner, v, content);
77+
}
78+
}
79+
None => {
80+
let comments: Vec<MarkedString> = self
81+
.filter_nodes_from(n, NodeKind::is_userdefined)
82+
.into_iter()
83+
.filter(|n| {
84+
n.utf8_text(content.as_ref()).expect("utf-8 parse error") == identifier
85+
})
86+
.filter_map(|n| self.find_preceding_comments(n.id(), content.as_ref()))
87+
.map(MarkedString::String)
88+
.collect();
89+
90+
v.extend(comments);
91+
}
9092
}
9193
}
9294
}

src/parser/input/test_rename.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ message Library {
2424

2525
service Myservice {
2626
rpc GetBook(Empty) returns (Book);
27+
rpc GetAuthor(Empty) returns (Book.Author)
2728
}

0 commit comments

Comments
 (0)