qlue-ls 3.2.1

A language server for SPARQL
Documentation
mod iri;
mod quickfix;
pub(crate) mod same_subject;
mod select;
mod variable;
use crate::server::{
    Server,
    lsp::{
        CodeAction, CodeActionParams, CodeActionRequest, CodeActionResponse,
        diagnostic::Diagnostic,
        errors::{ErrorCode, LSPError},
    },
};
use futures::lock::Mutex;
use ll_sparql_parser::{SyntaxElement, ast::AstNode};
use ll_sparql_parser::{
    ast::{Iri, Var},
    syntax_kind::SyntaxKind,
};
use quickfix::get_quickfix;
use std::rc::Rc;

pub(crate) use quickfix::declare_prefix;
pub(crate) use quickfix::remove_prefix_declaration;

#[tracing::instrument(skip_all, fields(id = %request.get_id(), uri = %request.params.text_document.uri))]
pub(super) async fn handle_codeaction_request(
    server_rc: Rc<Mutex<Server>>,
    request: CodeActionRequest,
) -> Result<(), LSPError> {
    let mut server = server_rc.lock().await;
    let mut code_action_response = CodeActionResponse::new(request.get_id());
    code_action_response.add_code_actions(generate_code_actions(&mut server, &request.params)?);
    code_action_response.add_code_actions(generate_quickfixes(&mut server, request));
    server.send_message(code_action_response)
}

fn generate_quickfixes(server: &mut Server, request: CodeActionRequest) -> Vec<CodeAction> {
    request
        .params
        .context
        .diagnostics
        .into_iter()
        .filter_map(|diagnostic| {
            match get_quickfix(server, &request.params.text_document.uri, diagnostic) {
                Ok(code_action) => code_action,
                Err(err) => {
                    tracing::error!(
                        "Encountered Error while computing quickfix:\n{}\nDropping error!",
                        err.message
                    );
                    None
                }
            }
        })
        .collect()
}

fn generate_code_actions(
    server: &mut Server,
    params: &CodeActionParams,
) -> Result<Vec<CodeAction>, LSPError> {
    let document_uri = &params.text_document.uri;
    let document = server.state.get_document(document_uri)?;
    let root = server.state.get_cached_parse_tree(document_uri)?.tree;
    let range = params
        .range
        .to_byte_index_range(&document.text)
        .ok_or(LSPError::new(
            ErrorCode::InvalidParams,
            &format!("Range ({:?}) not inside document range", params.range),
        ))?;

    let selected_element: SyntaxElement = root.covering_element(range);
    let mut code_actions = vec![];
    if selected_element
        .parent()
        .and_then(Iri::cast)
        .is_some_and(|iri| iri.is_uncompressed())
    {
        code_actions.extend(iri::code_actions(server, document.uri.clone()));
    } else if let Some(var) = selected_element.parent().and_then(Var::cast) {
        code_actions.extend(variable::code_actions(var, &server.state, document));
    } else if matches!(
        selected_element.kind(),
        SyntaxKind::SelectQuery | SyntaxKind::SELECT | SyntaxKind::SubSelect
    ) {
        code_actions.extend(select::code_actions(
            selected_element,
            document,
            server.settings.format.tab_size.unwrap_or(2),
        ));
    }
    // NOTE: Global code actions
    if let Some(code_action) =
        same_subject::contract_all_triple_groups(document, root, &server.settings.format)?
    {
        code_actions.push(code_action);
    }

    Ok(code_actions)
}