Skip to content

Commit 0578541

Browse files
fix: initial hover functionality (#46)
![image](https://github.com/github-language-server/github-lsp/assets/721090/d9edeaa3-f433-4203-a7bb-533c1563fafc) Here's `hover` triggered over an issue. Closes #43
1 parent ae4e1f8 commit 0578541

File tree

2 files changed

+142
-16
lines changed

2 files changed

+142
-16
lines changed

src/backend.rs

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ use octocrab::models::{Author, Repository};
44
use octocrab::params::State;
55
use octocrab::Octocrab;
66
use ropey::Rope;
7-
use tower_lsp::jsonrpc::Result;
7+
use tower_lsp::jsonrpc::{self, Result};
88
use tower_lsp::lsp_types::{
9-
CompletionItem, CompletionTextEdit, MessageType, Range, TextDocumentItem, TextEdit,
9+
CompletionItem, CompletionTextEdit, Hover, HoverContents, MarkupContent, MarkupKind,
10+
MessageType, Range, TextDocumentItem, TextEdit,
1011
};
1112
use tower_lsp::{lsp_types::Position, Client};
1213

@@ -31,6 +32,20 @@ pub struct Backend {
3132
impl Backend {
3233
const PER_PAGE: u8 = 100;
3334

35+
pub fn new(client: Client, octocrab: Octocrab, owner: String, repo: String) -> Backend {
36+
Backend {
37+
client,
38+
octocrab,
39+
owner,
40+
repo,
41+
document_map: DashMap::new(),
42+
repository_map: DashMap::new(),
43+
issue_map: DashMap::new(),
44+
member_map: DashMap::new(),
45+
wiki_map: DashMap::new(),
46+
}
47+
}
48+
3449
pub(crate) async fn initialize(&self) {
3550
self.initialize_issues().await;
3651
self.initialize_members().await;
@@ -39,6 +54,63 @@ impl Backend {
3954
self.initialize_wiki().await;
4055
}
4156

57+
pub async fn on_hover(&self, link: String) -> Result<Option<Hover>> {
58+
let mut text = String::new();
59+
//FIX: probably will cause issues for someone, maybe?
60+
if link.contains("github.com") {
61+
let link = link.replace("https://github.com/", "");
62+
let parts = link.split('/');
63+
let identifier = parts
64+
.last()
65+
.ok_or("No issue part in URL")
66+
.map_err(|_| jsonrpc::Error::method_not_found())?;
67+
if link.contains("issues") {
68+
let issue = self
69+
.issue_map
70+
.iter()
71+
.filter(|issue| issue.get_label().starts_with(&format!("#{} ", identifier)))
72+
.last()
73+
.ok_or("No issue")
74+
.map_err(|_| jsonrpc::Error::method_not_found())?;
75+
text = issue.get_detail().to_string();
76+
} else if link.contains("wiki") {
77+
text = format!("# Wiki article {}", identifier);
78+
} else if link.contains('/') {
79+
let repository = self
80+
.repository_map
81+
.iter()
82+
.filter(|repo| repo.get_label() == link)
83+
.last()
84+
.ok_or("No repo")
85+
.map_err(|_| jsonrpc::Error::method_not_found())?;
86+
text = repository.get_detail().to_string();
87+
} else {
88+
let users = octocrab::instance()
89+
.search()
90+
.users(identifier)
91+
.per_page(1)
92+
.page(0u32)
93+
.send()
94+
.await
95+
.map_err(|_| {
96+
tower_lsp::jsonrpc::Error::new(
97+
tower_lsp::jsonrpc::ErrorCode::MethodNotFound,
98+
)
99+
})?;
100+
let user = &users.items[0];
101+
text = format!("# User {}", user.login.to_owned());
102+
}
103+
}
104+
let hover = Hover {
105+
contents: HoverContents::Markup(MarkupContent {
106+
kind: MarkupKind::Markdown,
107+
value: text,
108+
}),
109+
range: None,
110+
};
111+
Ok(Some(hover))
112+
}
113+
42114
pub(crate) async fn search_issue_and_pr(
43115
&self,
44116
position: Position,
@@ -340,18 +412,4 @@ impl Backend {
340412
self.member_map.insert(member.login.to_owned(), member);
341413
});
342414
}
343-
344-
pub fn new(client: Client, octocrab: Octocrab, owner: String, repo: String) -> Backend {
345-
Backend {
346-
client,
347-
octocrab,
348-
owner,
349-
repo,
350-
document_map: DashMap::new(),
351-
repository_map: DashMap::new(),
352-
issue_map: DashMap::new(),
353-
member_map: DashMap::new(),
354-
wiki_map: DashMap::new(),
355-
}
356-
}
357415
}

src/lsp.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ impl LanguageServer for Backend {
3131

3232
file_operations: None,
3333
}),
34+
hover_provider: Some(HoverProviderCapability::Simple(true)),
3435
..ServerCapabilities::default()
3536
},
3637
})
@@ -174,4 +175,71 @@ impl LanguageServer for Backend {
174175
};
175176
Ok(completions.map(CompletionResponse::Array))
176177
}
178+
179+
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
180+
let uri = params.text_document_position_params.text_document.uri;
181+
let position = params.text_document_position_params.position;
182+
let rope = self
183+
.document_map
184+
.get(&uri.to_string())
185+
.ok_or(tower_lsp::jsonrpc::Error::invalid_request())?;
186+
187+
let line = rope
188+
.get_line(position.line as usize)
189+
.ok_or(tower_lsp::jsonrpc::Error::internal_error())?;
190+
let character_pos = position.character as usize;
191+
192+
//TODO: cleanup parsing; possible to clean up with treesitter? need to investigate
193+
let line = line.to_string();
194+
let line = line.trim();
195+
// scan backwards
196+
let mut start = character_pos;
197+
let mut start_search = start;
198+
start = usize::MAX;
199+
loop {
200+
// look for the start of the (..) link part
201+
if line.as_bytes()[start_search] == b'(' {
202+
start = start_search + 1; // skip (
203+
break;
204+
}
205+
if start_search == 0 {
206+
break;
207+
}
208+
start_search -= 1;
209+
}
210+
// scan forwards
211+
let mut end = character_pos;
212+
let mut end_search = end;
213+
end = usize::MIN;
214+
loop {
215+
// handle hover over the [..] part of a link
216+
if start == usize::MAX && line.as_bytes()[end_search] == b'(' {
217+
start = end_search + 1;
218+
}
219+
if line.as_bytes()[end_search] == b')' {
220+
end = end_search; // str[..] slice will exclude ), non inclusive
221+
break;
222+
}
223+
if end_search == line.len() - 1 {
224+
break;
225+
}
226+
end_search += 1;
227+
}
228+
229+
if start == usize::MAX || end == usize::MIN || start >= end {
230+
self.client
231+
.log_message(
232+
MessageType::ERROR,
233+
format!(
234+
"Hover search failed with invalid start {} end {} for line {}",
235+
start, end, line
236+
),
237+
)
238+
.await;
239+
return Ok(None);
240+
}
241+
let link: String = line[start..end].into();
242+
243+
self.on_hover(link).await
244+
}
177245
}

0 commit comments

Comments
 (0)