diff --git a/Cargo.lock b/Cargo.lock index 4d19c42..73957de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -512,7 +512,7 @@ dependencies = [ [[package]] name = "protols" -version = "0.7.1" +version = "0.8.0" dependencies = [ "async-lsp", "basic-toml", diff --git a/Cargo.toml b/Cargo.toml index e3d7687..4eedd42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index c2c9d6a..9fdf348 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ - ✅ Go to Definition - ✅ Hover Information - ✅ Rename Symbols +- ✅ Find references ## 🚀 Getting Started @@ -23,7 +24,7 @@ #### 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 @@ -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! 🎉 diff --git a/src/lsp.rs b/src/lsp.rs index a8c45b5..bad2e66 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -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, @@ -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() }, @@ -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 { @@ -267,6 +268,43 @@ impl LanguageServer for ProtoLanguageServer { Box::pin(async move { Ok(response) }) } + fn references( + &mut self, + param: ReferenceParams, + ) -> BoxFuture<'static, Result>, 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, diff --git a/src/parser/input/test_reference.proto b/src/parser/input/test_reference.proto new file mode 100644 index 0000000..f655cd9 --- /dev/null +++ b/src/parser/input/test_reference.proto @@ -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) +} diff --git a/src/parser/input/test_rename.proto b/src/parser/input/test_rename.proto index d7e5b16..7a01a58 100644 --- a/src/parser/input/test_rename.proto +++ b/src/parser/input/test_rename.proto @@ -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 { diff --git a/src/parser/rename.rs b/src/parser/rename.rs index c206dd2..fbce333 100644 --- a/src/parser/rename.rs +++ b/src/parser/rename.rs @@ -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}; @@ -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> { + ) -> 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 reference_tree( + &self, + pos: &Position, + content: impl AsRef<[u8]>, + ) -> Option<(Vec, 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, @@ -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}"); @@ -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"); @@ -103,6 +133,20 @@ impl ParsedTree { }) .collect() } + + pub fn reference_field(&self, id: &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") == 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)] @@ -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(); diff --git a/src/parser/snapshots/protols__parser__rename__test__reference-2.snap b/src/parser/snapshots/protols__parser__rename__test__reference-2.snap new file mode 100644 index 0000000..e7d5b9e --- /dev/null +++ b/src/parser/snapshots/protols__parser__rename__test__reference-2.snap @@ -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 diff --git a/src/parser/snapshots/protols__parser__rename__test__reference-3.snap b/src/parser/snapshots/protols__parser__rename__test__reference-3.snap new file mode 100644 index 0000000..cb88ea3 --- /dev/null +++ b/src/parser/snapshots/protols__parser__rename__test__reference-3.snap @@ -0,0 +1,5 @@ +--- +source: src/parser/rename.rs +expression: reference_fn(&pos_non_ref) +--- +[] diff --git a/src/parser/snapshots/protols__parser__rename__test__reference.snap b/src/parser/snapshots/protols__parser__rename__test__reference.snap new file mode 100644 index 0000000..90b752d --- /dev/null +++ b/src/parser/snapshots/protols__parser__rename__test__reference.snap @@ -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 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 bce7327..ba767a2 100644 --- a/src/parser/snapshots/protols__parser__rename__test__rename-2.snap +++ b/src/parser/snapshots/protols__parser__rename__test__rename-2.snap @@ -4,18 +4,18 @@ expression: "rename_fn(\"Writer\", &pos_author)" --- - range: start: - line: 21 + line: 23 character: 4 end: - line: 21 + line: 23 character: 15 newText: Book.Writer - range: start: - line: 26 + line: 29 character: 34 end: - line: 26 + line: 29 character: 45 newText: Book.Writer - range: diff --git a/src/parser/snapshots/protols__parser__rename__test__rename.snap b/src/parser/snapshots/protols__parser__rename__test__rename.snap index 1302a83..8f05c52 100644 --- a/src/parser/snapshots/protols__parser__rename__test__rename.snap +++ b/src/parser/snapshots/protols__parser__rename__test__rename.snap @@ -4,34 +4,34 @@ expression: "rename_fn(\"Kitab\", &pos_book)" --- - range: start: - line: 20 + line: 22 character: 13 end: - line: 20 + line: 22 character: 17 newText: Kitab - range: start: - line: 21 + line: 23 character: 4 end: - line: 21 + line: 23 character: 15 newText: Kitab.Author - range: start: - line: 25 + line: 28 character: 32 end: - line: 25 + line: 28 character: 36 newText: Kitab - range: start: - line: 26 + line: 29 character: 34 end: - line: 26 + line: 29 character: 45 newText: Kitab.Author - range: diff --git a/src/parser/tree.rs b/src/parser/tree.rs index 68cffc7..b4c4844 100644 --- a/src/parser/tree.rs +++ b/src/parser/tree.rs @@ -63,6 +63,16 @@ impl ParsedTree { .map(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error")) } + pub fn get_hoverable_node_text_at_position<'a>( + &'a self, + pos: &Position, + content: &'a [u8], + ) -> Option<&'a str> { + let n = self.get_node_at_position(pos)?; + self.get_actionable_node_text_at_position(pos, content) + .or(Some(n.kind())) + } + 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![]; diff --git a/src/workspace/hover.rs b/src/workspace/hover.rs index bd0913d..bdd6e9c 100644 --- a/src/workspace/hover.rs +++ b/src/workspace/hover.rs @@ -1,9 +1,115 @@ +use std::{collections::HashMap, sync::LazyLock}; + use async_lsp::lsp_types::MarkedString; use crate::{state::ProtoLanguageState, utils::split_identifier_package}; +static BUITIN_DOCS: LazyLock> = LazyLock::new(|| { + HashMap::from([ + ( + "int32", + r#"A 32-bit integer (varint encoding) + +Values of this type range between `-2147483648` and `2147483647`. +Beware that negative values are encoded as five bytes on the wire!"#, + ), + ( + "int64", + r#"A 64-bit integer (varint encoding) + +Values of this type range between `-9223372036854775808` and `9223372036854775807`. +Beware that negative values are encoded as ten bytes on the wire!"#, + ), + ( + "uint32", + r#"A 32-bit unsigned integer (varint encoding) + +Values of this type range between `0` and `4294967295`."#, + ), + ( + "uint64", + r#"A 64-bit unsigned integer (varint encoding) + +Values of this type range between `0` and `18446744073709551615`."#, + ), + ( + "sint32", + r#"A 32-bit integer (ZigZag encoding) + +Values of this type range between `-2147483648` and `2147483647`."#, + ), + ( + "sint64", + r#"A 64-bit integer (ZigZag encoding) + +Values of this type range between `-9223372036854775808` and `9223372036854775807`."#, + ), + ( + "fixed32", + r#"A 32-bit unsigned integer (4-byte encoding) + +Values of this type range between `0` and `4294967295`."#, + ), + ( + "fixed64", + r#"A 64-bit unsigned integer (8-byte encoding) + +Values of this type range between `0` and `18446744073709551615`."#, + ), + ( + "sfixed32", + r#"A 32-bit integer (4-byte encoding) + +Values of this type range between `-2147483648` and `2147483647`."#, + ), + ( + "sfixed64", + r#"A 64-bit integer (8-byte encoding) + +Values of this type range between `-9223372036854775808` and `9223372036854775807`."#, + ), + ( + "float", + "A single-precision floating point number (IEEE-745.2008 binary32).", + ), + ( + "double", + "A double-precision floating point number (IEEE-745.2008 binary64).", + ), + ( + "string", + r#"A string of text. + +Stores at most 4GB of text. Intended to be UTF-8 encoded Unicode; use `bytes` if you need other encodings."#, + ), + ( + "bytes", + r#"A blob of arbitrary bytes. + +Stores at most 4GB of binary data. Encoded as base64 in JSON."#, + ), + ( + "bool", + r#"A Boolean value: `true` or `false`. + +Encoded as a single byte: `0x00` or `0xff` (all non-zero bytes decode to `true`)."#, + ), + ( + "default", + r#"A magic option that specifies the field's default value. + +Unlike every other option on a field, this does not have a corresponding field in +`google.protobuf.FieldOptions`; it is implemented by compiler magic."#, + ), + ]) +}); + impl ProtoLanguageState { pub fn hover(&self, curr_package: &str, identifier: &str) -> Vec { + if let Some(docs) = BUITIN_DOCS.get(identifier) { + return vec![MarkedString::String(docs.to_string())]; + } + let (mut package, identifier) = split_identifier_package(identifier); if package.is_empty() { package = curr_package; @@ -40,6 +146,7 @@ mod test { state.upsert_file(&c_uri, c.to_owned()); assert_yaml_snapshot!(state.hover("com.workspace", "Author")); + assert_yaml_snapshot!(state.hover("com.workspace", "int64")); assert_yaml_snapshot!(state.hover("com.workspace", "Author.Address")); assert_yaml_snapshot!(state.hover("com.workspace", "com.utility.Foobar.Baz")); assert_yaml_snapshot!(state.hover("com.utility", "Baz")); diff --git a/src/workspace/rename.rs b/src/workspace/rename.rs index 65700bf..fb02cdd 100644 --- a/src/workspace/rename.rs +++ b/src/workspace/rename.rs @@ -1,7 +1,7 @@ use crate::utils::split_identifier_package; use std::collections::HashMap; -use async_lsp::lsp_types::{TextEdit, Url}; +use async_lsp::lsp_types::{Location, TextEdit, Url}; use crate::state::ProtoLanguageState; @@ -31,6 +31,32 @@ impl ProtoLanguageState { h }) } + + pub fn reference_fields( + &self, + current_package: &str, + identifier: &str, + ) -> Option> { + let (_, identifier) = split_identifier_package(identifier); + let r = self + .get_trees() + .into_iter() + .fold(Vec::::new(), |mut v, 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_owned(); + if current_package != package { + old = format!("{current_package}.{old}"); + } + v.extend(tree.reference_field(&old, content.as_str())); + v + }); + if r.is_empty() { + None + } else { + Some(r) + } + } } #[cfg(test)] @@ -62,4 +88,24 @@ mod test { )); assert_yaml_snapshot!(state.rename_fields("com.utility", "Foobar.Baz", "Foobar.Baaz")); } + + #[test] + fn test_reference() { + 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 = 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.reference_fields("com.workspace", "Author")); + assert_yaml_snapshot!(state.reference_fields("com.workspace", "Author.Address")); + assert_yaml_snapshot!(state.reference_fields("com.utility", "Foobar.Baz")); + } } diff --git a/src/workspace/snapshots/protols__workspace__hover__test__workspace_test_hover-2.snap b/src/workspace/snapshots/protols__workspace__hover__test__workspace_test_hover-2.snap index 55ec65d..1dbb2d6 100644 --- a/src/workspace/snapshots/protols__workspace__hover__test__workspace_test_hover-2.snap +++ b/src/workspace/snapshots/protols__workspace__hover__test__workspace_test_hover-2.snap @@ -1,5 +1,6 @@ --- source: src/workspace/hover.rs -expression: "state.hover(\"com.library\", \"Author.Address\")" +expression: "state.hover(\"com.workspace\", \"int64\")" +snapshot_kind: text --- -- Address is a Address +- "A 64-bit integer (varint encoding)\n\nValues of this type range between `-9223372036854775808` and `9223372036854775807`.\nBeware that negative values are encoded as ten bytes on the wire!" diff --git a/src/workspace/snapshots/protols__workspace__hover__test__workspace_test_hover-3.snap b/src/workspace/snapshots/protols__workspace__hover__test__workspace_test_hover-3.snap index 7e3ab87..00bbfa0 100644 --- a/src/workspace/snapshots/protols__workspace__hover__test__workspace_test_hover-3.snap +++ b/src/workspace/snapshots/protols__workspace__hover__test__workspace_test_hover-3.snap @@ -1,5 +1,6 @@ --- source: src/workspace/hover.rs -expression: "state.hover(\"com.library\", \"com.utility.Foobar.Baz\")" +expression: "state.hover(\"com.workspace\", \"Author.Address\")" +snapshot_kind: text --- -- What is baz? +- Address is a Address diff --git a/src/workspace/snapshots/protols__workspace__hover__test__workspace_test_hover-5.snap b/src/workspace/snapshots/protols__workspace__hover__test__workspace_test_hover-5.snap new file mode 100644 index 0000000..152acde --- /dev/null +++ b/src/workspace/snapshots/protols__workspace__hover__test__workspace_test_hover-5.snap @@ -0,0 +1,6 @@ +--- +source: src/workspace/hover.rs +expression: "state.hover(\"com.utility\", \"Baz\")" +snapshot_kind: text +--- +- What is baz? diff --git a/src/workspace/snapshots/protols__workspace__rename__test__reference-2.snap b/src/workspace/snapshots/protols__workspace__rename__test__reference-2.snap new file mode 100644 index 0000000..8ef1259 --- /dev/null +++ b/src/workspace/snapshots/protols__workspace__rename__test__reference-2.snap @@ -0,0 +1,12 @@ +--- +source: src/workspace/rename.rs +expression: "state.reference_fields(\"com.workspace\", \"Author.Address\")" +--- +- uri: "file://input/a.proto" + range: + start: + line: 9 + character: 3 + end: + line: 9 + character: 17 diff --git a/src/workspace/snapshots/protols__workspace__rename__test__reference-3.snap b/src/workspace/snapshots/protols__workspace__rename__test__reference-3.snap new file mode 100644 index 0000000..2a93dff --- /dev/null +++ b/src/workspace/snapshots/protols__workspace__rename__test__reference-3.snap @@ -0,0 +1,12 @@ +--- +source: src/workspace/rename.rs +expression: "state.reference_fields(\"com.utility\", \"Foobar.Baz\")" +--- +- uri: "file://input/a.proto" + range: + start: + line: 10 + character: 3 + end: + line: 10 + character: 25 diff --git a/src/workspace/snapshots/protols__workspace__rename__test__reference.snap b/src/workspace/snapshots/protols__workspace__rename__test__reference.snap new file mode 100644 index 0000000..115c3ad --- /dev/null +++ b/src/workspace/snapshots/protols__workspace__rename__test__reference.snap @@ -0,0 +1,12 @@ +--- +source: src/workspace/rename.rs +expression: "state.reference_fields(\"com.workspace\", \"Author\")" +--- +- uri: "file://input/a.proto" + range: + start: + line: 8 + character: 3 + end: + line: 8 + character: 9