taplo-lsp 0.6.1

Language server for Taplo
Documentation
use crate::world::World;
use lsp_async_stub::rpc::Error;
use lsp_async_stub::util::LspExt;
use lsp_async_stub::{Context, Params};
use lsp_types::{DocumentLink, DocumentLinkParams, Url};
use taplo::dom::KeyOrIndex;
use taplo_common::environment::Environment;
use taplo_common::schema::ext::schema_ext_of;

#[tracing::instrument(skip_all)]
pub async fn links<E: Environment>(
    context: Context<World<E>>,
    params: Params<DocumentLinkParams>,
) -> Result<Option<Vec<DocumentLink>>, Error> {
    let p = params.required()?;

    let workspaces = context.workspaces.write().await;
    let ws = workspaces.by_document(&p.text_document.uri);

    if !ws.config.schema.enabled || !ws.config.schema.links {
        return Ok(None);
    }

    let doc = match ws.document(&p.text_document.uri) {
        Ok(d) => d,
        Err(error) => {
            tracing::debug!(%error, "failed to get document from workspace");
            return Ok(None);
        }
    };

    let mut links = Vec::new();

    if let Some(schema_association) = ws
        .schemas
        .associations()
        .association_for(&p.text_document.uri)
    {
        tracing::debug!(
            schema.url = %schema_association.url,
            schema.name = schema_association.meta["name"].as_str().unwrap_or(""),
            schema.source = schema_association.meta["source"].as_str().unwrap_or(""),
            "using schema"
        );

        for (keys, last_key, node) in doc.dom.flat_iter().filter_map(|(k, n)| {
            if let Some(KeyOrIndex::Key(last_key)) = k.iter().last().cloned() {
                Some((k, last_key, n))
            } else {
                None
            }
        }) {
            let value = match serde_json::to_value(&node) {
                Ok(v) => v,
                Err(error) => {
                    tracing::debug!(%error, "invalid TOML value");
                    continue;
                }
            };

            let schemas = match ws
                .schemas
                .schemas_at_path(&schema_association.url, &value, &keys)
                .await
            {
                Ok(s) => s,
                Err(error) => {
                    tracing::error!(?error, "failed to collect schemas");
                    continue;
                }
            };

            for (_, schema) in schemas {
                if let Some(key_link) = schema_ext_of(&schema)
                    .and_then(|e| e.links)
                    .and_then(|l| l.key)
                {
                    let url: Url = match key_link.parse() {
                        Ok(u) => u,
                        Err(error) => {
                            tracing::error!(%error, "invalid link");
                            continue;
                        }
                    };

                    links.extend(last_key.text_ranges().map(|range| DocumentLink {
                        range: doc.mapper.range(range).unwrap().into_lsp(),
                        target: Some(url.clone()),
                        tooltip: None,
                        data: None,
                    }));
                }
            }
        }
    }

    Ok(Some(links))
}