Skip to content

Commit

Permalink
feat: implement hover across workspace
Browse files Browse the repository at this point in the history
  • Loading branch information
coder3101 committed Aug 17, 2024
1 parent a553587 commit 0d3d6ab
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 81 deletions.
12 changes: 10 additions & 2 deletions sample/simple.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ message Book {
// Of a message called Book
int64 isbn = 1;
string title = 2;
string author = 3;
Author author = 3;
google.protobuf.Any data = 4;

// Author is a author of a book
message Author {
string name = 1;
int64 age = 2;
}
}

// This is a comment on message
Expand All @@ -22,7 +29,7 @@ message GotoBookRequest {
}

message GetBookViaAuthor {
string author = 1;
Book.Author author = 1;
}


Expand All @@ -31,6 +38,7 @@ service BookService {
// This is GetBook RPC that takes a book request
// and returns a Book, simple and sweet
rpc GetBook (GetBookRequest) returns (Book) {}
rpc GetBookAuthor (GetBookRequest) returns (Book.Author) {}
rpc GetBooksViaAuthor (GetBookViaAuthor) returns (stream Book) {}
rpc GetGreatestBook (stream GetBookRequest) returns (Book) {}
rpc GetBooks (stream GetBookRequest) returns (stream Book) {}
Expand Down
54 changes: 38 additions & 16 deletions src/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,26 +102,48 @@ impl LanguageServer for ServerState {
let uri = param.text_document_position_params.text_document.uri;
let pos = param.text_document_position_params.position;

let identifier;
let current_package_name;

match self.get_parsed_tree_and_content(&uri) {
Err(e) => Box::pin(async move { Err(e) }),
Err(e) => {
return Box::pin(async move { Err(e) });
}
Ok((tree, content)) => {
let comments = tree.hover(&pos, content.as_bytes());

let response = match comments.len() {
0 => None,
1 => Some(Hover {
contents: HoverContents::Scalar(comments[0].clone()),
range: None,
}),
2.. => Some(Hover {
contents: HoverContents::Array(comments),
range: None,
}),
};
identifier = tree
.get_actionable_node_text_at_position(&pos, content.as_ref())
.map(ToOwned::to_owned);

Box::pin(async move { Ok(response) })
current_package_name = tree
.get_package_name(content.as_ref())
.map(ToOwned::to_owned);
}
}
};

let Some(identifier) = identifier else {
error!(uri=%uri, "failed to get identifier");
return Box::pin(async move { Ok(None) });
};

let Some(current_package_name) = current_package_name else {
error!(uri=%uri, "failed to get package name");
return Box::pin(async move { Ok(None) });
};

let comments = self.registry_hover(current_package_name.as_ref(), identifier.as_ref());
let response = match comments.len() {
0 => None,
1 => Some(Hover {
contents: HoverContents::Scalar(comments[0].clone()),
range: None,
}),
2.. => Some(Hover {
contents: HoverContents::Array(comments),
range: None,
}),
};

Box::pin(async move { Ok(response) })
}
fn completion(
&mut self,
Expand Down
10 changes: 4 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use tracing::Level;

mod lsp;
mod parser;
mod registry;
mod server;
mod utils;

Expand Down Expand Up @@ -39,13 +40,10 @@ async fn main() {
.service(ServerState::new_router(client))
});

let mut dir = std::env::temp_dir();
dir.push("protols.log");
let dir = std::env::temp_dir();
eprintln!("Logs are being written to directory {:?}", dir);

eprintln!("Logs are being written to {:?}", dir);

let file_appender =
tracing_appender::rolling::daily(std::env::temp_dir().as_path(), "protols.log");
let file_appender = tracing_appender::rolling::daily(dir, "protols.log");
let (non_blocking, _gaurd) = tracing_appender::non_blocking(file_appender);

tracing_subscriber::fmt()
Expand Down
2 changes: 1 addition & 1 deletion src/parser/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ impl ParsedTree {

match text {
Some(text) => self
.filter_node(NodeKind::is_userdefined)
.filter_nodes(NodeKind::is_userdefined)
.into_iter()
.filter(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error") == text)
.map(|n| Location {
Expand Down
2 changes: 1 addition & 1 deletion src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use super::{nodekind::NodeKind, ParsedTree};
impl ParsedTree {
pub fn collect_parse_errors(&self) -> PublishDiagnosticsParams {
let diagnostics = self
.filter_node(NodeKind::is_error)
.filter_nodes(NodeKind::is_error)
.into_iter()
.map(|n| Diagnostic {
range: Range {
Expand Down
88 changes: 61 additions & 27 deletions src/parser/hover.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use async_lsp::lsp_types::{MarkedString, Position};
use tracing::info;
use async_lsp::lsp_types::MarkedString;
use tree_sitter::Node;

use crate::parser::nodekind::NodeKind;

Expand Down Expand Up @@ -47,44 +47,59 @@ impl ParsedTree {
}
}

pub fn hover(&self, pos: &Position, content: impl AsRef<[u8]>) -> Vec<MarkedString> {
let text = self.get_actionable_node_text_at_position(pos, content.as_ref());
info!("Looking for hover response on: {:?}", text);
pub fn hover(&self, identifier: &str, content: impl AsRef<[u8]>) -> Vec<MarkedString> {
let mut v = vec![];
self.hover_impl(identifier, self.tree.root_node(), &mut v, content);
v
}

fn hover_impl(
&self,
identifier: &str,
n: Node,
v: &mut Vec<MarkedString>,
content: impl AsRef<[u8]>,
) {
if identifier.is_empty() {
return;
}

match text {
Some(text) => self
.filter_node(NodeKind::is_actionable)
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") == text)
.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(),
None => vec![],
.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)
.map(|n| n.parent().unwrap()); // Safety: All userdefined types would have a parent

if let Some(inner) = child_node {
self.hover_impl(remaining, inner, v, content);
}
}
}

#[cfg(test)]
mod test {
use async_lsp::lsp_types::{MarkedString, Position, Url};
use async_lsp::lsp_types::{MarkedString, Url};

use crate::parser::ProtoParser;

#[test]
fn test_hover() {
let uri: Url = "file://foo.bar/p.proto".parse().unwrap();
let posbook = Position {
line: 5,
character: 9,
};
let posinvalid = Position {
line: 0,
character: 1,
};
let posauthor = Position {
line: 11,
character: 14,
};
let contents = r#"syntax = "proto3";
package com.book;
Expand All @@ -101,19 +116,28 @@ message Book {
string country = 2;
};
}
// Comic is a type of book but who cares
message Comic {
// Author of a comic is different from others
message Author {
string name = 1;
string country = 2;
};
}
"#;
let parsed = ProtoParser::new().parse(uri.clone(), contents);
assert!(parsed.is_some());
let tree = parsed.unwrap();
let res = tree.hover(&posbook, contents);
let res = tree.hover("Book", contents);

assert_eq!(res.len(), 1);
assert_eq!(res[0], MarkedString::String("A Book is book".to_owned()));

let res = tree.hover(&posinvalid, contents);
let res = tree.hover("", contents);
assert_eq!(res.len(), 0);

let res = tree.hover(&posauthor, contents);
let res = tree.hover("Book.Author", contents);
assert_eq!(res.len(), 1);
assert_eq!(
res[0],
Expand All @@ -125,5 +149,15 @@ Author has a name and a country where they were born"#
.to_owned()
)
);

let res = tree.hover("Comic.Author", contents);
assert_eq!(res.len(), 1);
assert_eq!(
res[0],
MarkedString::String("Author of a comic is different from others".to_owned())
);

let res = tree.hover("Author", contents);
assert_eq!(res.len(), 2);
}
}
33 changes: 20 additions & 13 deletions src/parser/nodekind.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use async_lsp::lsp_types::SymbolKind;
use tree_sitter::Node;

#[allow(unused)]
pub enum NodeKind {
Identifier,
Error,
Expand All @@ -24,34 +23,42 @@ impl NodeKind {
NodeKind::FieldName => "message_or_enum_type",
NodeKind::ServiceName => "service_name",
NodeKind::RpcName => "rpc_name",
NodeKind::PackageName => "package_name",
NodeKind::PackageName => "full_ident",
}
}

pub fn is_identifier(n: &Node) -> bool {
n.kind() == "identifier"
n.kind() == Self::Identifier.as_str()
}

pub fn is_error(n: &Node) -> bool {
n.kind() == "ERROR"
n.kind() == Self::Error.as_str()
}

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

pub fn is_userdefined(n: &Node) -> bool {
matches!(n.kind(), "message_name" | "enum_name")
n.kind() == Self::EnumName.as_str() || n.kind() == Self::MessageName.as_str()
}

pub fn is_actionable(n: &Node) -> bool {
matches!(
n.kind(),
"message_name" | "enum_name" | "message_or_enum_type" | "rpc_name" | "service_name"
)
n.kind() == Self::MessageName.as_str()
|| n.kind() == Self::EnumName.as_str()
|| n.kind() == Self::FieldName.as_str()
|| n.kind() == Self::PackageName.as_str()
|| n.kind() == Self::ServiceName.as_str()
|| n.kind() == Self::RpcName.as_str()
}

pub fn to_symbolkind(n: &Node) -> SymbolKind {
match n.kind() {
"message_name" => SymbolKind::STRUCT,
"enum_name" => SymbolKind::ENUM,
_ => SymbolKind::NULL,
if n.kind() == Self::MessageName.as_str() {
SymbolKind::STRUCT
} else if n.kind() == Self::EnumName.as_str() {
SymbolKind::ENUM
} else {
SymbolKind::NULL
}
}
}
2 changes: 1 addition & 1 deletion src/parser/rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl ParsedTree {
let mut changes = HashMap::new();

let diff: Vec<_> = self
.filter_node(NodeKind::is_identifier)
.filter_nodes(NodeKind::is_identifier)
.into_iter()
.filter(|n| n.utf8_text(content.as_ref()).unwrap() == old_text)
.map(|n| TextEdit {
Expand Down
Loading

0 comments on commit 0d3d6ab

Please sign in to comment.