use std::rc::Rc;
use futures::lock::Mutex;
use ll_sparql_parser::{
SyntaxNode,
ast::{AstNode, GroupGraphPattern, QueryUnit},
};
use text_size::TextSize;
use crate::server::{
Server,
lsp::{
JumpRequest, JumpResponse, JumpResult,
errors::{ErrorCode, LSPError},
textdocument::{Position, TextDocumentItem},
},
};
#[tracing::instrument(skip_all, fields(id = %request.get_id(), uri = %request.params.base.text_document.uri))]
pub(super) async fn handle_jump_request(
server_rc: Rc<Mutex<Server>>,
request: JumpRequest,
) -> Result<(), LSPError> {
let server = server_rc.lock().await;
let document_uri = &request.params.base.text_document.uri;
let document = server.state.get_document(document_uri)?;
let root = server.state.get_cached_parse_tree(document_uri)?.tree;
let cursor_offset = request
.params
.base
.position
.byte_index(&document.text)
.ok_or(LSPError::new(
ErrorCode::InvalidRequest,
"given position is not inside document",
))?;
let results = relevant_positions(
document,
root,
request.params.previous.is_some_and(|prev| prev),
);
let jump_position = if request.params.previous.is_some_and(|prev| prev) {
let last = results.last().cloned();
results
.into_iter()
.rev()
.find(|(offset, _, _)| offset < &cursor_offset)
.or(last)
} else {
let first = results.first().cloned();
results
.into_iter()
.find(|(offset, _, _)| offset > &cursor_offset)
.or(first)
}
.map(|(offset, before, after)| {
JumpResult::new(
Position::from_byte_index(offset, &document.text).unwrap(),
before,
after,
)
});
server.send_message(JumpResponse::new(request.get_id(), jump_position))?;
Ok(())
}
fn relevant_positions(
_document: &TextDocumentItem,
root: SyntaxNode,
jump_to_previous: bool,
) -> Vec<(TextSize, Option<&str>, Option<&str>)> {
let mut res = Vec::new();
if let Some(query_unit) = QueryUnit::cast(root) {
if let Some(offset) = query_unit.select_query().and_then(|sq| {
if jump_to_previous {
sq.where_clause().map(|wc| wc.syntax().text_range().start())
} else {
sq.select_clause().map(|sc| sc.syntax().text_range().end())
}
}) {
res.push((
offset,
(!jump_to_previous).then_some(" "),
jump_to_previous.then_some(" "),
));
}
for (offset, has_children) in query_unit
.syntax()
.descendants()
.filter_map(GroupGraphPattern::cast)
.filter_map(|ggp| {
ggp.syntax().last_child_or_token().map(|token| {
(
token.text_range().start(),
ggp.syntax().first_child().is_some(),
)
})
})
{
res.push((
offset,
has_children.then_some(" ").or(Some("\n ")),
Some("\n"),
));
}
if jump_to_previous {
if let Some(offset) = query_unit
.syntax()
.last_token()
.map(|token| token.text_range().end())
{
res.push((offset, None, None));
}
} else {
if let Some(offset) = query_unit.select_query().and_then(|sq| {
sq.soulution_modifier()
.map(|sm| sm.syntax().clone())
.or(sq.where_clause().map(|wc| wc.syntax().clone()))
.map(|node| node.text_range().end())
}) {
res.push((offset, Some("\n"), None));
}
}
}
res.sort_by(|a, b| a.0.cmp(&b.0));
res
}