diff --git a/Cargo.lock b/Cargo.lock index 077c67e..297ca84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -472,7 +472,7 @@ dependencies = [ [[package]] name = "protols" -version = "0.4.0" +version = "0.5.0" dependencies = [ "async-lsp", "futures", diff --git a/Cargo.toml b/Cargo.toml index 4c8f272..2e4d717 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/sample/simple.proto b/sample/simple.proto index ee51325..9cc8717 100644 --- a/sample/simple.proto +++ b/sample/simple.proto @@ -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 diff --git a/src/lsp.rs b/src/lsp.rs index 36be1b6..e96d43d 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fs::read_to_string; use std::ops::ControlFlow; use std::sync::mpsc; @@ -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; @@ -187,7 +187,7 @@ impl LanguageServer for ProtoLanguageServer { "enum", "oneof", "repeated", "reserved", "to", ]; - let mut keywords: Vec = keywords + let mut completions: Vec = keywords .into_iter() .map(|w| CompletionItem { label: w.to_string(), @@ -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( @@ -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) }) } diff --git a/src/nodekind.rs b/src/nodekind.rs index 83a7d94..0205ff0 100644 --- a/src/nodekind.rs +++ b/src/nodekind.rs @@ -5,6 +5,7 @@ pub enum NodeKind { Identifier, Error, MessageName, + Message, EnumName, FieldName, ServiceName, @@ -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", @@ -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() } diff --git a/src/parser/definition.rs b/src/parser/definition.rs index e1f6749..97027c5 100644 --- a/src/parser/definition.rs +++ b/src/parser/definition.rs @@ -22,34 +22,39 @@ impl ParsedTree { return; } - if !identifier.contains('.') { - let locations: Vec = 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 = 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); + } } } } diff --git a/src/parser/hover.rs b/src/parser/hover.rs index 738abaa..110b7f5 100644 --- a/src/parser/hover.rs +++ b/src/parser/hover.rs @@ -64,29 +64,31 @@ impl ParsedTree { return; } - if !identifier.contains('.') { - let comments: Vec = 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 = 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); + } } } } diff --git a/src/parser/input/test_rename.proto b/src/parser/input/test_rename.proto index a10bef9..d7e5b16 100644 --- a/src/parser/input/test_rename.proto +++ b/src/parser/input/test_rename.proto @@ -24,4 +24,5 @@ message Library { service Myservice { rpc GetBook(Empty) returns (Book); + rpc GetAuthor(Empty) returns (Book.Author) } diff --git a/src/parser/rename.rs b/src/parser/rename.rs index e61f231..c206dd2 100644 --- a/src/parser/rename.rs +++ b/src/parser/rename.rs @@ -1,6 +1,5 @@ -use std::collections::HashMap; - -use async_lsp::lsp_types::{Position, Range, TextEdit, WorkspaceEdit}; +use async_lsp::lsp_types::{Position, Range, TextEdit}; +use tree_sitter::Node; use crate::{nodekind::NodeKind, utils::ts_to_lsp_position}; @@ -22,41 +21,87 @@ impl ParsedTree { }) } - pub fn rename( + fn rename_within( + &self, + n: Node<'_>, + identifier: &str, + new_identifier: &str, + content: impl AsRef<[u8]>, + ) -> Option> { + 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 rename_tree( &self, pos: &Position, - new_text: &str, + new_name: &str, content: impl AsRef<[u8]>, - ) -> Option { - let old_text = self - .get_node_text_at_position(pos, content.as_ref()) - .unwrap_or_default(); + ) -> Option<(Vec, String, String)> { + let rename_range = self.can_rename(pos)?; - let mut changes = HashMap::new(); + let mut v = vec![TextEdit { + range: rename_range, + new_text: new_name.to_owned(), + }]; - let diff: Vec<_> = self - .filter_nodes(NodeKind::is_identifier) - .into_iter() - .filter(|n| n.utf8_text(content.as_ref()).unwrap() == old_text) - .map(|n| TextEdit { - new_text: new_text.to_string(), - range: Range { - start: ts_to_lsp_position(&n.start_position()), - end: ts_to_lsp_position(&n.end_position()), - }, - }) - .collect(); + 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(); + let mut ntext = new_name.to_owned(); + + 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 diff.is_empty() { - return None; + otext = format!("{id}.{otext}"); + ntext = format!("{id}.{ntext}"); + + i += 1 } - changes.insert(self.uri.clone(), diff); + Some((v, otext, ntext)) + } - Some(WorkspaceEdit { - changes: Some(changes), - ..Default::default() - }) + pub fn rename_field( + &self, + old_identifier: &str, + new_identifier: &str, + content: impl AsRef<[u8]>, + ) -> Vec { + 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) + }) + .map(|n| { + let text = n.utf8_text(content.as_ref()).expect("utf-8 parse error"); + TextEdit { + new_text: text.replace(old_identifier, new_identifier), + range: Range { + start: ts_to_lsp_position(&n.start_position()), + end: ts_to_lsp_position(&n.end_position()), + }, + } + }) + .collect() } } @@ -70,17 +115,17 @@ mod test { #[test] fn test_rename() { let uri: Url = "file://foo/bar.proto".parse().unwrap(); - let pos_book_rename = Position { + let pos_book = Position { line: 5, character: 9, }; - let pos_author_rename = Position { - line: 21, - character: 10, + let pos_author = Position { + line: 11, + character: 14, }; - let pos_non_renamble = Position { - line: 24, - character: 4, + let pos_non_rename = Position { + line: 21, + character: 5, }; let contents = include_str!("input/test_rename.proto"); @@ -88,9 +133,19 @@ mod test { assert!(parsed.is_some()); let tree = parsed.unwrap(); - assert_yaml_snapshot!(tree.rename(&pos_book_rename, "Kitab", contents)); - assert_yaml_snapshot!(tree.rename(&pos_author_rename, "Writer", contents)); - assert_yaml_snapshot!(tree.rename(&pos_non_renamble, "Doesn't matter", contents)); + let rename_fn = |nt: &str, pos: &Position| { + if let Some(k) = tree.rename_tree(pos, nt, contents) { + let mut v = tree.rename_field(&k.1, &k.2, contents); + v.extend(k.0); + v + } else { + vec![] + } + }; + + assert_yaml_snapshot!(rename_fn("Kitab", &pos_book)); + assert_yaml_snapshot!(rename_fn("Writer", &pos_author)); + assert_yaml_snapshot!(rename_fn("xyx", &pos_non_rename)); } #[test] diff --git a/src/parser/snapshots/protols__parser__rename__test__rename-2.snap b/src/parser/snapshots/protols__parser__rename__test__rename-2.snap index a0eebe6..bce7327 100644 --- a/src/parser/snapshots/protols__parser__rename__test__rename-2.snap +++ b/src/parser/snapshots/protols__parser__rename__test__rename-2.snap @@ -1,30 +1,36 @@ --- source: src/parser/rename.rs -expression: "tree.rename(&pos_author_rename, \"Writer\", contents)" +expression: "rename_fn(\"Writer\", &pos_author)" --- -changes: - "file://foo/bar.proto": - - range: - start: - line: 11 - character: 12 - end: - line: 11 - character: 18 - newText: Writer - - range: - start: - line: 15 - character: 4 - end: - line: 15 - character: 10 - newText: Writer - - range: - start: - line: 21 - character: 9 - end: - line: 21 - character: 15 - newText: Writer +- range: + start: + line: 21 + character: 4 + end: + line: 21 + character: 15 + newText: Book.Writer +- range: + start: + line: 26 + character: 34 + end: + line: 26 + character: 45 + newText: Book.Writer +- range: + start: + line: 11 + character: 12 + end: + line: 11 + character: 18 + newText: Writer +- range: + start: + line: 15 + character: 4 + end: + line: 15 + character: 10 + newText: Writer diff --git a/src/parser/snapshots/protols__parser__rename__test__rename-3.snap b/src/parser/snapshots/protols__parser__rename__test__rename-3.snap index 9fe1587..50e431e 100644 --- a/src/parser/snapshots/protols__parser__rename__test__rename-3.snap +++ b/src/parser/snapshots/protols__parser__rename__test__rename-3.snap @@ -1,5 +1,5 @@ --- source: src/parser/rename.rs -expression: "tree.rename(&pos_non_renamble, \"Doesn't matter\", contents)" +expression: "rename_fn(\"xyx\", &pos_non_rename)" --- -~ +[] diff --git a/src/parser/snapshots/protols__parser__rename__test__rename.snap b/src/parser/snapshots/protols__parser__rename__test__rename.snap index 91465d7..1302a83 100644 --- a/src/parser/snapshots/protols__parser__rename__test__rename.snap +++ b/src/parser/snapshots/protols__parser__rename__test__rename.snap @@ -1,38 +1,44 @@ --- source: src/parser/rename.rs -expression: "tree.rename(&pos_book_rename, \"Kitab\", contents)" +expression: "rename_fn(\"Kitab\", &pos_book)" --- -changes: - "file://foo/bar.proto": - - range: - start: - line: 5 - character: 8 - end: - line: 5 - character: 12 - newText: Kitab - - range: - start: - line: 20 - character: 13 - end: - line: 20 - character: 17 - newText: Kitab - - range: - start: - line: 21 - character: 4 - end: - line: 21 - character: 8 - newText: Kitab - - range: - start: - line: 25 - character: 32 - end: - line: 25 - character: 36 - newText: Kitab +- range: + start: + line: 20 + character: 13 + end: + line: 20 + character: 17 + newText: Kitab +- range: + start: + line: 21 + character: 4 + end: + line: 21 + character: 15 + newText: Kitab.Author +- range: + start: + line: 25 + character: 32 + end: + line: 25 + character: 36 + newText: Kitab +- range: + start: + line: 26 + character: 34 + end: + line: 26 + character: 45 + newText: Kitab.Author +- range: + start: + line: 5 + character: 8 + end: + line: 5 + character: 12 + newText: Kitab diff --git a/src/parser/snapshots/protols__parser__rename__test__rename_fields-2.snap b/src/parser/snapshots/protols__parser__rename__test__rename_fields-2.snap new file mode 100644 index 0000000..3cf596e --- /dev/null +++ b/src/parser/snapshots/protols__parser__rename__test__rename_fields-2.snap @@ -0,0 +1,12 @@ +--- +source: src/parser/rename.rs +expression: "tree.rename_fields(\"Book.Author\", \"Writer\", contents)" +--- +- range: + start: + line: 21 + character: 4 + end: + line: 21 + character: 15 + newText: Book.Writer diff --git a/src/parser/snapshots/protols__parser__rename__test__rename_fields-3.snap b/src/parser/snapshots/protols__parser__rename__test__rename_fields-3.snap new file mode 100644 index 0000000..02ded32 --- /dev/null +++ b/src/parser/snapshots/protols__parser__rename__test__rename_fields-3.snap @@ -0,0 +1,5 @@ +--- +source: src/parser/rename.rs +expression: "tree.rename_fields(\"xyz.abc\", \"Doesn't matter\", contents)" +--- +[] diff --git a/src/parser/snapshots/protols__parser__rename__test__rename_fields.snap b/src/parser/snapshots/protols__parser__rename__test__rename_fields.snap new file mode 100644 index 0000000..0d62b54 --- /dev/null +++ b/src/parser/snapshots/protols__parser__rename__test__rename_fields.snap @@ -0,0 +1,28 @@ +--- +source: src/parser/rename.rs +expression: "tree.rename_fields(\"Book\", \"Kitab\", contents)" +--- +- range: + start: + line: 20 + character: 13 + end: + line: 20 + character: 17 + newText: Kitab +- range: + start: + line: 21 + character: 4 + end: + line: 21 + character: 15 + newText: Kitab.Author +- range: + start: + line: 25 + character: 32 + end: + line: 25 + character: 36 + newText: Kitab diff --git a/src/parser/tree.rs b/src/parser/tree.rs index fc82d40..68cffc7 100644 --- a/src/parser/tree.rs +++ b/src/parser/tree.rs @@ -54,15 +54,6 @@ impl ParsedTree { } } - pub fn get_node_text_at_position<'a>( - &'a self, - pos: &Position, - content: &'a [u8], - ) -> Option<&'a str> { - self.get_node_at_position(pos) - .map(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error")) - } - pub fn get_actionable_node_text_at_position<'a>( &'a self, pos: &Position, @@ -72,6 +63,26 @@ impl ParsedTree { .map(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error")) } + pub fn get_ancestor_nodes_at_position<'a>(&'a self, pos: &Position) -> Vec> { + let Some(mut n) = self.get_actionable_node_at_position(pos) else { + return vec![]; + }; + + let mut nodes = vec![]; + while let Some(p) = n.parent() { + if NodeKind::is_message(&p) { + for i in 0..p.child_count() { + let t = p.child(i).unwrap(); + if NodeKind::is_message_name(&t) { + nodes.push(t); + } + } + } + n = p; + } + nodes + } + pub fn get_actionable_node_at_position<'a>(&'a self, pos: &Position) -> Option> { self.get_node_at_position(pos) .map(|n| { @@ -118,13 +129,8 @@ impl ParsedTree { mod test { use async_lsp::lsp_types::Url; use insta::assert_yaml_snapshot; - use tree_sitter::Node; - - use crate::parser::ProtoParser; - fn is_message(n: &Node) -> bool { - n.kind() == "message_name" - } + use crate::{nodekind::NodeKind, parser::ProtoParser}; #[test] fn test_filter() { @@ -134,7 +140,7 @@ mod test { assert!(parsed.is_some()); let tree = parsed.unwrap(); - let nodes = tree.filter_nodes(is_message); + let nodes = tree.filter_nodes(NodeKind::is_message_name); assert_eq!(nodes.len(), 2); diff --git a/src/state.rs b/src/state.rs index 983eec2..fc20510 100644 --- a/src/state.rs +++ b/src/state.rs @@ -47,6 +47,15 @@ impl ProtoLanguageState { self.trees.read().expect("poison").get(uri).cloned() } + pub fn get_trees(&self) -> Vec { + self.trees + .read() + .expect("poison") + .values() + .map(ToOwned::to_owned) + .collect() + } + pub fn get_trees_for_package(&self, package: &str) -> Vec { self.trees .read() diff --git a/src/workspace/input/a.proto b/src/workspace/input/a.proto index a982e4a..49df280 100644 --- a/src/workspace/input/a.proto +++ b/src/workspace/input/a.proto @@ -8,6 +8,6 @@ import "c.proto"; message Book { Author author = 1; Author.Address foo = 2; - com.utility.FooBar.Baz z = 3; + com.utility.Foobar.Baz z = 3; } diff --git a/src/workspace/mod.rs b/src/workspace/mod.rs index 9a26930..cca1e38 100644 --- a/src/workspace/mod.rs +++ b/src/workspace/mod.rs @@ -1,2 +1,3 @@ mod definition; mod hover; +mod rename; diff --git a/src/workspace/rename.rs b/src/workspace/rename.rs new file mode 100644 index 0000000..ab49f59 --- /dev/null +++ b/src/workspace/rename.rs @@ -0,0 +1,65 @@ +use crate::utils::split_identifier_package; +use std::collections::HashMap; + +use async_lsp::lsp_types::{TextEdit, Url}; + +use crate::state::ProtoLanguageState; + +impl ProtoLanguageState { + pub fn rename_fields( + &self, + current_package: &str, + identifier: &str, + new_text: &str, + ) -> HashMap> { + let (_, identifier) = split_identifier_package(identifier); + self.get_trees() + .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 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 v = tree.rename_field(&old, &new, content.as_str()); + if !v.is_empty() { + h.insert(tree.uri.clone(), v); + } + h + }) + } +} + +#[cfg(test)] +mod test { + use insta::assert_yaml_snapshot; + + use crate::state::ProtoLanguageState; + + #[test] + fn test_rename() { + let a_uri = "file://input/a.proto".parse().unwrap(); + let b_uri = "file://input/b.proto".parse().unwrap(); + let c_uri = "file://input/c.proto".parse().unwrap(); + + let a = include_str!("input/a.proto"); + let b = include_str!("input/b.proto"); + let c = include_str!("input/c.proto"); + + let mut state = ProtoLanguageState::new(); + state.upsert_file(&a_uri, a.to_owned()); + state.upsert_file(&b_uri, b.to_owned()); + state.upsert_file(&c_uri, c.to_owned()); + + assert_yaml_snapshot!(state.rename_fields("com.workspace", "Author", "Writer")); + assert_yaml_snapshot!(state.rename_fields( + "com.workspace", + "Author.Address", + "Author.Location" + )); + assert_yaml_snapshot!(state.rename_fields("com.utility", "Foobar.Baz", "Foobar.Baaz")); + } +} diff --git a/src/workspace/snapshots/protols__workspace__rename__test__rename-2.snap b/src/workspace/snapshots/protols__workspace__rename__test__rename-2.snap new file mode 100644 index 0000000..68638ac --- /dev/null +++ b/src/workspace/snapshots/protols__workspace__rename__test__rename-2.snap @@ -0,0 +1,13 @@ +--- +source: src/workspace/rename.rs +expression: "state.rename_fields(\"com.workspace\", \"Author.Address\", \"Author.Location\")" +--- +"file://input/a.proto": + - range: + start: + line: 9 + character: 3 + end: + line: 9 + character: 17 + newText: Author.Location diff --git a/src/workspace/snapshots/protols__workspace__rename__test__rename-3.snap b/src/workspace/snapshots/protols__workspace__rename__test__rename-3.snap new file mode 100644 index 0000000..35166b1 --- /dev/null +++ b/src/workspace/snapshots/protols__workspace__rename__test__rename-3.snap @@ -0,0 +1,13 @@ +--- +source: src/workspace/rename.rs +expression: "state.rename_fields(\"com.utility\", \"Foobar.Baz\", \"Foobar.Baaz\")" +--- +"file://input/a.proto": + - range: + start: + line: 10 + character: 3 + end: + line: 10 + character: 25 + newText: com.utility.Foobar.Baaz diff --git a/src/workspace/snapshots/protols__workspace__rename__test__rename.snap b/src/workspace/snapshots/protols__workspace__rename__test__rename.snap new file mode 100644 index 0000000..4caedd6 --- /dev/null +++ b/src/workspace/snapshots/protols__workspace__rename__test__rename.snap @@ -0,0 +1,21 @@ +--- +source: src/workspace/rename.rs +expression: "state.rename_fields(\"com.workspace\", \"Author\", \"Writer\")" +--- +"file://input/a.proto": + - range: + start: + line: 8 + character: 3 + end: + line: 8 + character: 9 + newText: Writer + - range: + start: + line: 9 + character: 3 + end: + line: 9 + character: 17 + newText: Writer.Address