Skip to content

Commit 15783de

Browse files
committed
add support for hover based on comments
1 parent ffa1cb3 commit 15783de

File tree

5 files changed

+140
-22
lines changed

5 files changed

+140
-22
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ tokio-util = { version = "0.7.11", features = ["compat"] }
1111
tower = "0.4.13"
1212
tracing = "0.1.40"
1313
tracing-subscriber = "0.3.18"
14-
tree-sitter-proto = { git = "https://github.com/mitchellh/tree-sitter-proto", rev = "42d82fa18f8afe59b5fc0b16c207ee4f84cb185f" }
15-
tree-sitter = "0.19.3"
14+
tree-sitter-proto = { git = "https://github.com/coder3101/tree-sitter-proto", branch = "main" }
15+
tree-sitter = "0.22.6"
1616
tracing-appender = "0.2.3"

src/lsp.rs

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use tracing::{debug, info};
55
use async_lsp::lsp_types::{
66
DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
77
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
8-
InitializeParams, InitializeResult, MarkedString, OneOf, ServerCapabilities, ServerInfo,
9-
TextDocumentSyncCapability, TextDocumentSyncKind,
8+
HoverProviderCapability, InitializeParams, InitializeResult, MarkedString, OneOf,
9+
ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind,
1010
};
1111
use async_lsp::{ErrorCode, LanguageServer, ResponseError};
1212
use futures::future::BoxFuture;
@@ -39,6 +39,7 @@ impl LanguageServer for ServerState {
3939
TextDocumentSyncKind::FULL,
4040
)),
4141
definition_provider: Some(OneOf::Left(true)),
42+
hover_provider: Some(HoverProviderCapability::Simple(true)),
4243
..ServerCapabilities::default()
4344
},
4445
server_info: Some(ServerInfo {
@@ -50,17 +51,46 @@ impl LanguageServer for ServerState {
5051
Box::pin(async move { Ok(response) })
5152
}
5253

53-
fn hover(&mut self, _: HoverParams) -> BoxFuture<'static, Result<Option<Hover>, Self::Error>> {
54-
let counter = self.counter;
55-
Box::pin(async move {
56-
tokio::time::sleep(Duration::from_secs(1)).await;
57-
Ok(Some(Hover {
58-
contents: HoverContents::Scalar(MarkedString::String(format!(
59-
"I am a hover text {counter}!"
60-
))),
54+
fn hover(
55+
&mut self,
56+
param: HoverParams,
57+
) -> BoxFuture<'static, Result<Option<Hover>, Self::Error>> {
58+
let uri = param.text_document_position_params.text_document.uri;
59+
let pos = param.text_document_position_params.position;
60+
61+
let Some(contents) = self.documents.get(&uri) else {
62+
return Box::pin(async move {
63+
Err(ResponseError::new(
64+
ErrorCode::INVALID_REQUEST,
65+
"uri was never opened",
66+
))
67+
});
68+
};
69+
70+
let Some(parsed) = self.parser.parse(contents.as_bytes()) else {
71+
return Box::pin(async move {
72+
Err(ResponseError::new(
73+
ErrorCode::REQUEST_FAILED,
74+
"ts failed to parse contents",
75+
))
76+
});
77+
};
78+
79+
let comments = parsed.hover(&pos, contents.as_bytes());
80+
info!("Found {} node comments in the document", comments.len());
81+
let response = match comments.len() {
82+
0 => None,
83+
1 => Some(Hover {
84+
contents: HoverContents::Scalar(comments[0].clone()),
85+
range: None,
86+
}),
87+
2.. => Some(Hover {
88+
contents: HoverContents::Array(comments),
6189
range: None,
62-
}))
63-
})
90+
}),
91+
};
92+
93+
Box::pin(async move { Ok(response) })
6494
}
6595

6696
fn definition(
@@ -88,7 +118,7 @@ impl LanguageServer for ServerState {
88118
});
89119
};
90120

91-
let locations = parsed.definition_for(&pos, &uri, contents.as_bytes());
121+
let locations = parsed.definition(&pos, &uri, contents.as_bytes());
92122
info!("Found {} matching nodes in the document", locations.len());
93123

94124
let response = match locations.len() {

src/parser.rs

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use async_lsp::lsp_types::{Location, Position, Range, Url};
1+
use async_lsp::lsp_types::{Location, MarkedString, Position, Range, Url};
22
use tracing::info;
33
use tree_sitter::{Node, Tree, TreeCursor};
44

@@ -15,7 +15,7 @@ pub struct ParsedTree {
1515
impl ProtoParser {
1616
pub fn new() -> Self {
1717
let mut parser = tree_sitter::Parser::new();
18-
if let Err(e) = parser.set_language(tree_sitter_proto::language()) {
18+
if let Err(e) = parser.set_language(&tree_sitter_proto::language()) {
1919
panic!("failed to set ts language parser {:?}", e);
2020
}
2121
Self { parser }
@@ -41,7 +41,11 @@ impl ParsedTree {
4141
.map(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error"))
4242
}
4343

44-
fn walk_and_collect_kinds<'a>(&self, cursor: &mut TreeCursor<'a>, kinds: &[&str]) -> Vec<Node<'a>> {
44+
fn walk_and_collect_kinds<'a>(
45+
&self,
46+
cursor: &mut TreeCursor<'a>,
47+
kinds: &[&str],
48+
) -> Vec<Node<'a>> {
4549
let mut v = vec![];
4650

4751
loop {
@@ -64,12 +68,69 @@ impl ParsedTree {
6468
v
6569
}
6670

71+
fn advance_cursor_to<'a>(&self, cursor: &mut TreeCursor<'a>, nid: usize) -> bool {
72+
loop {
73+
let node = cursor.node();
74+
if node.id() == nid {
75+
return true;
76+
}
77+
if cursor.goto_first_child() {
78+
if self.advance_cursor_to(cursor, nid) {
79+
return true;
80+
}
81+
cursor.goto_parent();
82+
}
83+
if !cursor.goto_next_sibling() {
84+
return false;
85+
}
86+
}
87+
}
88+
89+
fn find_preceeding_comments(&self, nid: usize, content: impl AsRef<[u8]>) -> Option<String> {
90+
let root = self.tree.root_node();
91+
let mut cursor = root.walk();
92+
93+
info!("Looking for node with id: {nid}");
94+
95+
self.advance_cursor_to(&mut cursor, nid);
96+
if !cursor.goto_parent() {
97+
return None;
98+
}
99+
100+
if !cursor.goto_previous_sibling() {
101+
return None;
102+
}
103+
104+
let mut comments = vec![];
105+
while cursor.node().kind() == "comment" {
106+
let node = cursor.node();
107+
let text = node
108+
.utf8_text(content.as_ref())
109+
.expect("utf-8 parser error")
110+
.trim()
111+
.trim_start_matches("//")
112+
.trim();
113+
114+
comments.push(text);
115+
116+
if !cursor.goto_previous_sibling() {
117+
break;
118+
}
119+
}
120+
return if comments.len() != 0 {
121+
comments.reverse();
122+
Some(comments.join("\n"))
123+
} else {
124+
None
125+
};
126+
}
127+
67128
pub fn find_childrens_by_kinds(&self, kinds: &[&str]) -> Vec<Node> {
68129
let mut cursor = self.tree.root_node().walk();
69130
self.walk_and_collect_kinds(&mut cursor, kinds)
70131
}
71132

72-
pub fn definition_for(
133+
pub fn definition(
73134
&self,
74135
pos: &Position,
75136
uri: &Url,
@@ -94,4 +155,19 @@ impl ParsedTree {
94155
None => vec![],
95156
}
96157
}
158+
159+
pub fn hover(&self, pos: &Position, content: impl AsRef<[u8]>) -> Vec<MarkedString> {
160+
let text = self.get_node_text_at_position(pos, content.as_ref());
161+
info!("Looking for hover response on: {:?}", text);
162+
match text {
163+
Some(text) => self
164+
.find_childrens_by_kinds(&["message_name", "enum_name", "service_name", "rpc_name"])
165+
.into_iter()
166+
.filter(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error") == text)
167+
.filter_map(|n| self.find_preceeding_comments(n.id(), content.as_ref()))
168+
.map(|s| MarkedString::String(s))
169+
.collect(),
170+
None => vec![],
171+
}
172+
}
97173
}

src/simple.proto

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ syntax = "proto3";
33
package com.book;
44

55
message Book {
6+
// This is a multi line comment on the field name
7+
// Of a message called Book
68
int64 isbn = 1;
79
string title = 2;
810
string author = 3;
911
}
1012

13+
// This is a comment on message
1114
message GetBookRequest {
15+
16+
// This is a sigle line comment on the field of a message
1217
int64 isbn = 1;
1318
}
1419

@@ -22,6 +27,8 @@ message GetBookViaAuthor {
2227

2328
// It is a BookService Implementation
2429
service BookService {
30+
// This is GetBook RPC that takes a book request
31+
// and returns a Book, simple and sweet
2532
rpc GetBook (GetBookRequest) returns (Book) {}
2633
rpc GetBooksViaAuthor (GetBookViaAuthor) returns (stream Book) {}
2734
rpc GetGreatestBook (stream GetBookRequest) returns (Book) {}
@@ -30,9 +37,14 @@ service BookService {
3037

3138
message BookStore {
3239
string name = 1;
40+
EnumSample sample = 3;
3341
map<int64, string> books = 2;
3442
}
3543

44+
// These are enum options representing some operation in the proto
45+
// these are meant to be ony called from one place,
46+
47+
// Note: Please set only to started or running
3648
enum EnumSample {
3749
option allow_alias = true;
3850
UNKNOWN = 0;

0 commit comments

Comments
 (0)