Skip to content

Commit

Permalink
feat: add support for renaming across workspace (#26)
Browse files Browse the repository at this point in the history
Co-authored-by: mohammadkhan <[email protected]>
  • Loading branch information
coder3101 and asharkhan3101 authored Aug 24, 2024
1 parent 2e0b86e commit 9e0098d
Show file tree
Hide file tree
Showing 23 changed files with 456 additions and 183 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.4.0"
version = "0.5.0"
edition = "2021"
license = "MIT"
homepage = "https://github.com/coder3101/protols"
Expand Down
1 change: 1 addition & 0 deletions sample/simple.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";

package com.book;

// This is a book represeted by some comments that we like to address in the review
message Book {
// This is a multi line comment on the field name
// Of a message called Book
Expand Down
38 changes: 26 additions & 12 deletions src/lsp.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::fs::read_to_string;
use std::ops::ControlFlow;
use std::sync::mpsc;
Expand All @@ -11,11 +12,10 @@ use async_lsp::lsp_types::{
DocumentSymbolResponse, FileOperationFilter, FileOperationPattern, FileOperationPatternKind,
FileOperationRegistrationOptions, GotoDefinitionParams, GotoDefinitionResponse, Hover,
HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult, OneOf,
PrepareRenameResponse, ProgressParams, RenameFilesParams, RenameOptions,
RenameParams, ServerCapabilities, ServerInfo, TextDocumentPositionParams,
TextDocumentSyncCapability, TextDocumentSyncKind, Url, WorkspaceEdit,
WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
WorkspaceServerCapabilities,
PrepareRenameResponse, ProgressParams, RenameFilesParams, RenameOptions, RenameParams,
ServerCapabilities, ServerInfo, TextDocumentPositionParams, TextDocumentSyncCapability,
TextDocumentSyncKind, Url, WorkspaceEdit, WorkspaceFileOperationsServerCapabilities,
WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
};
use async_lsp::{LanguageClient, LanguageServer, ResponseError};
use futures::future::BoxFuture;
Expand Down Expand Up @@ -187,7 +187,7 @@ impl LanguageServer for ProtoLanguageServer {
"enum", "oneof", "repeated", "reserved", "to",
];

let mut keywords: Vec<CompletionItem> = keywords
let mut completions: Vec<CompletionItem> = keywords
.into_iter()
.map(|w| CompletionItem {
label: w.to_string(),
Expand All @@ -199,10 +199,10 @@ impl LanguageServer for ProtoLanguageServer {
if let Some(tree) = self.state.get_tree(&uri) {
let content = self.state.get_content(&uri);
if let Some(package_name) = tree.get_package_name(content.as_bytes()) {
keywords.extend(self.state.completion_items(package_name));
completions.extend(self.state.completion_items(package_name));
}
}
Box::pin(async move { Ok(Some(CompletionResponse::Array(keywords))) })
Box::pin(async move { Ok(Some(CompletionResponse::Array(completions))) })
}

fn prepare_rename(
Expand Down Expand Up @@ -238,12 +238,26 @@ impl LanguageServer for ProtoLanguageServer {

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

let response = if tree.can_rename(&pos).is_some() {
tree.rename(&pos, &new_name, content)
} else {
None
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((edit, otext, ntext)) = tree.rename_tree(&pos, &new_name, content.as_bytes())
else {
error!(uri=%uri, "failed to rename in a tree");
return Box::pin(async move { Ok(None) });
};

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

let response = Some(WorkspaceEdit {
changes: Some(h),
..Default::default()
});

Box::pin(async move { Ok(response) })
}

Expand Down
10 changes: 10 additions & 0 deletions src/nodekind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub enum NodeKind {
Identifier,
Error,
MessageName,
Message,
EnumName,
FieldName,
ServiceName,
Expand All @@ -19,6 +20,7 @@ impl NodeKind {
NodeKind::Identifier => "identifier",
NodeKind::Error => "ERROR",
NodeKind::MessageName => "message_name",
NodeKind::Message => "message",
NodeKind::EnumName => "enum_name",
NodeKind::FieldName => "message_or_enum_type",
NodeKind::ServiceName => "service_name",
Expand Down Expand Up @@ -47,6 +49,14 @@ impl NodeKind {
n.kind() == Self::MessageName.as_str()
}

pub fn is_message(n: &Node) -> bool {
n.kind() == Self::Message.as_str()
}

pub fn is_field_name(n: &Node) -> bool {
n.kind() == Self::FieldName.as_str()
}

pub fn is_userdefined(n: &Node) -> bool {
n.kind() == Self::EnumName.as_str() || n.kind() == Self::MessageName.as_str()
}
Expand Down
57 changes: 31 additions & 26 deletions src/parser/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,39 @@ impl ParsedTree {
return;
}

if !identifier.contains('.') {
let locations: Vec<Location> = self
.filter_nodes_from(n, NodeKind::is_userdefined)
.into_iter()
.filter(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error") == identifier)
.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();
match identifier.split_once('.') {
Some((parent_identifier, remaining)) => {
let child_node = self
.filter_nodes_from(n, NodeKind::is_userdefined)
.into_iter()
.find(|n| {
n.utf8_text(content.as_ref()).expect("utf8-parse error")
== parent_identifier
})
.and_then(|n| n.parent());

v.extend(locations);
return;
}

// Safety: identifier contains a .
let (parent_identifier, remaining) = identifier.split_once('.').unwrap();
let child_node = self
.filter_nodes_from(n, NodeKind::is_userdefined)
.into_iter()
.find(|n| n.utf8_text(content.as_ref()).expect("utf8-parse error") == parent_identifier)
.and_then(|n| n.parent());
if let Some(inner) = child_node {
self.definition_impl(remaining, inner, v, content);
}
}
None => {
let locations: Vec<Location> = self
.filter_nodes_from(n, NodeKind::is_userdefined)
.into_iter()
.filter(|n| {
n.utf8_text(content.as_ref()).expect("utf-8 parse error") == identifier
})
.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();

if let Some(inner) = child_node {
self.definition_impl(remaining, inner, v, content);
v.extend(locations);
}
}
}
}
Expand Down
48 changes: 25 additions & 23 deletions src/parser/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,29 +64,31 @@ impl ParsedTree {
return;
}

if !identifier.contains('.') {
let comments: Vec<MarkedString> = self
.filter_nodes_from(n, NodeKind::is_userdefined)
.into_iter()
.filter(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error") == identifier)
.filter_map(|n| self.find_preceding_comments(n.id(), content.as_ref()))
.map(MarkedString::String)
.collect();

v.extend(comments);
return;
}

// Safety: identifier contains a .
let (parent_identifier, remaining) = identifier.split_once('.').unwrap();
let child_node = self
.filter_nodes_from(n, NodeKind::is_userdefined)
.into_iter()
.find(|n| n.utf8_text(content.as_ref()).expect("utf8-parse error") == parent_identifier)
.and_then(|n| n.parent());

if let Some(inner) = child_node {
self.hover_impl(remaining, inner, v, content);
match identifier.split_once('.') {
Some((parent, child)) => {
let child_node = self
.filter_nodes_from(n, NodeKind::is_userdefined)
.into_iter()
.find(|n| n.utf8_text(content.as_ref()).expect("utf8-parse error") == parent)
.and_then(|n| n.parent());

if let Some(inner) = child_node {
self.hover_impl(child, inner, v, content);
}
}
None => {
let comments: Vec<MarkedString> = self
.filter_nodes_from(n, NodeKind::is_userdefined)
.into_iter()
.filter(|n| {
n.utf8_text(content.as_ref()).expect("utf-8 parse error") == identifier
})
.filter_map(|n| self.find_preceding_comments(n.id(), content.as_ref()))
.map(MarkedString::String)
.collect();

v.extend(comments);
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/parser/input/test_rename.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ message Library {

service Myservice {
rpc GetBook(Empty) returns (Book);
rpc GetAuthor(Empty) returns (Book.Author)
}
Loading

0 comments on commit 9e0098d

Please sign in to comment.