Skip to content

Commit

Permalink
allow renaming all identifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
asharkhan3101 committed Jul 22, 2024
1 parent 9258568 commit 21317d3
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 39 deletions.
15 changes: 6 additions & 9 deletions src/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ use async_lsp::lsp_types::{
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
DidSaveTextDocumentParams, DocumentSymbolParams, DocumentSymbolResponse, GotoDefinitionParams,
GotoDefinitionResponse, Hover, HoverContents, HoverParams, HoverProviderCapability,
InitializeParams, InitializeResult, OneOf, PrepareRenameResponse,
RegularExpressionsClientCapabilities, RenameOptions, RenameParams, ServerCapabilities,
ServerInfo, TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind,
WorkspaceEdit,
InitializeParams, InitializeResult, OneOf, PrepareRenameResponse, RenameParams,
ServerCapabilities, ServerInfo, TextDocumentPositionParams, TextDocumentSyncCapability,
TextDocumentSyncKind, WorkspaceEdit,
};
use async_lsp::{LanguageClient, LanguageServer, ResponseError};
use futures::future::BoxFuture;
Expand Down Expand Up @@ -115,9 +114,7 @@ impl LanguageServer for ServerState {
match self.get_parsed_tree_and_content(&uri) {
Err(e) => Box::pin(async move { Err(e) }),
Ok((tree, _)) => {
let response = tree
.can_rename(&pos)
.map(|(r, _)| PrepareRenameResponse::Range(r));
let response = tree.can_rename(&pos).map(PrepareRenameResponse::Range);

Box::pin(async move { Ok(response) })
}
Expand All @@ -136,8 +133,8 @@ impl LanguageServer for ServerState {
match self.get_parsed_tree_and_content(&uri) {
Err(e) => Box::pin(async move { Err(e) }),
Ok((tree, content)) => {
let response = if let Some((_, kind)) = tree.can_rename(&pos) {
tree.rename_kind(&uri, &pos, kind, &new_name, content)
let response = if tree.can_rename(&pos).is_some() {
tree.rename(&uri, &pos, &new_name, content)
} else {
None
};
Expand Down
231 changes: 201 additions & 30 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@ pub struct ParsedTree {
tree: Tree,
}

// Adding any new kind to USER_DEFINED_KINDS must be accompanied with a change in DocumentSymbol
// handler match statement.
const USER_DEFINED_KINDS: &[&str] = &["message_name", "enum_name"];
const ACTIONABLE_KINDS: &[&str] = &["message_name", "enum_name", "rpc_name", "service_name"];
const ACTIONABLE_KINDS: &[&str] = &[
"message_name",
"enum_name",
"message_or_enum_type",
"rpc_name",
"service_name",
];

#[derive(Default)]
struct DocumentSymbolTreeBuilder {
Expand Down Expand Up @@ -271,40 +279,34 @@ impl ParsedTree {
}
}

pub fn can_rename(&self, pos: &Position) -> Option<(Range, &str)> {
pub fn can_rename(&self, pos: &Position) -> Option<Range> {
self.get_node_at_position(pos)
.filter(|n| n.kind() == "identifier")
.map(|n| n.parent().unwrap()) // Safety: Identifier must have a parent node
.filter(|n| ACTIONABLE_KINDS.contains(&n.kind()))
.map(|n| {
(
Range {
start: ts_to_lsp_position(&n.start_position()),
end: ts_to_lsp_position(&n.end_position()),
},
n.kind(),
)
.map(|n| Range {
start: ts_to_lsp_position(&n.start_position()),
end: ts_to_lsp_position(&n.end_position()),
})
}

pub fn rename_kind(
pub fn rename(
&self,
uri: &Url,
pos: &Position,
kind: &str,
new_text: &str,
content: impl AsRef<[u8]>,
) -> Option<WorkspaceEdit> {
let old_text = self
.get_node_text_at_position(pos, &content.as_ref())
.get_node_text_at_position(pos, content.as_ref())
.unwrap_or_default();

let mut changes = HashMap::new();

let mut cursor = self.tree.root_node().walk();
let diff = Self::walk_and_collect_kinds(&mut cursor, &[kind])
let diff: Vec<_> = Self::walk_and_collect_kinds(&mut cursor, &["identifier"])
.into_iter()
.filter(|n| n.utf8_text(&content.as_ref()).unwrap() == old_text)
.filter(|n| n.utf8_text(content.as_ref()).unwrap() == old_text)
.map(|n| TextEdit {
new_text: new_text.to_string(),
range: Range {
Expand All @@ -314,6 +316,10 @@ impl ParsedTree {
})
.collect();

if diff.is_empty() {
return None;
}

changes.insert(uri.clone(), diff);

Some(WorkspaceEdit {
Expand Down Expand Up @@ -347,8 +353,10 @@ impl ParsedTree {

#[cfg(test)]
mod test {

use async_lsp::lsp_types::{
DiagnosticSeverity, DocumentSymbol, MarkedString, Position, Range, SymbolKind, Url,
DiagnosticSeverity, DocumentSymbol, MarkedString, Position, Range, SymbolKind, TextEdit,
Url,
};

use super::ProtoParser;
Expand Down Expand Up @@ -446,8 +454,174 @@ message Book {
}

#[test]
fn test_rename_kind() {
todo!("implement me")
fn test_rename() {
let uri = "file://foo/bar.proto".parse().unwrap();
let pos_book_rename = Position {
line: 5,
character: 9,
};
let pos_author_rename = Position {
line: 21,
character: 10,
};
let pos_non_renamble = Position {
line: 24,
character: 4,
};
let contents = r#"syntax = "proto3";
package com.book;
// 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 Library {
repeated Book books = 1;
Book.Author collection = 2;
}
service Myservice {
rpc GetBook(Empty) returns (Book);
}
"#;

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

let res = tree.rename(&uri, &pos_book_rename, "Kitab", contents);
assert!(res.is_some());
let changes = res.unwrap().changes;
assert!(changes.is_some());
let changes = changes.unwrap();
assert!(changes.contains_key(&uri));
let edits = changes.get(&uri).unwrap();

assert_eq!(
*edits,
vec![
TextEdit {
range: Range {
start: Position {
line: 5,
character: 8,
},
end: Position {
line: 5,
character: 12,
},
},
new_text: "Kitab".to_string(),
},
TextEdit {
range: Range {
start: Position {
line: 20,
character: 13,
},
end: Position {
line: 20,
character: 17,
},
},
new_text: "Kitab".to_string(),
},
TextEdit {
range: Range {
start: Position {
line: 21,
character: 4,
},
end: Position {
line: 21,
character: 8,
},
},
new_text: "Kitab".to_string(),
},
TextEdit {
range: Range {
start: Position {
line: 25,
character: 32,
},
end: Position {
line: 25,
character: 36,
},
},
new_text: "Kitab".to_string(),
},
],
);

let res = tree.rename(&uri, &pos_author_rename, "Writer", contents);
assert!(res.is_some());
let changes = res.unwrap().changes;
assert!(changes.is_some());
let changes = changes.unwrap();
assert!(changes.contains_key(&uri));
let edits = changes.get(&uri).unwrap();

assert_eq!(
*edits,
vec![
TextEdit {
range: Range {
start: Position {
line: 11,
character: 12,
},
end: Position {
line: 11,
character: 18,
},
},
new_text: "Writer".to_string(),
},
TextEdit {
range: Range {
start: Position {
line: 15,
character: 4,
},
end: Position {
line: 15,
character: 10,
},
},
new_text: "Writer".to_string(),
},
TextEdit {
range: Range {
start: Position {
line: 21,
character: 9,
},
end: Position {
line: 21,
character: 15,
},
},
new_text: "Writer".to_string(),
},
],
);

let res = tree.rename(&uri, &pos_non_renamble, "Doesn't matter", contents);
assert!(res.is_none());
}

#[test]
Expand Down Expand Up @@ -485,19 +659,16 @@ message Book {
assert!(res.is_some());
assert_eq!(
res.unwrap(),
(
Range {
start: Position {
line: 5,
character: 8
},
end: Position {
line: 5,
character: 12
},
Range {
start: Position {
line: 5,
character: 8
},
"message_name"
)
end: Position {
line: 5,
character: 12
},
},
);

let res = tree.can_rename(&pos_non_rename);
Expand Down

0 comments on commit 21317d3

Please sign in to comment.