blues-lsp 0.1.3

LSP language server for the Bluespec SystemVerilog language
Documentation
use std::collections::HashMap;

use lsp_types::{OneOf, TextEdit};

use crate::{
    lsp::{
        error::LspResult,
        server::{path_to_uri, Server},
    },
    project::FileHandle,
    symbols::{
        node::{node_of_interest, selection_span},
        resolve::{definition_for, find_references_for},
    },
    syntax::{
        cst::{
            kind::TreeKind, tree::NodeRef, view::{Package, View}
        },
        location::{FileSpan, OriginTable, Pos, Span, ROOT_ORIGIN},
    },
};

pub fn rename_capabilities() -> lsp_types::OneOf<bool, lsp_types::RenameOptions> {
    lsp_types::OneOf::Right(lsp_types::RenameOptions {
        prepare_provider: Some(true),
        work_done_progress_options: Default::default(),
    })
}

pub fn prepare_rename(
    server: &mut Server,
    params: lsp_types::TextDocumentPositionParams,
) -> LspResult<Option<lsp_types::PrepareRenameResponse>> {
    let pkg = server.uri_to_pkg(&params.text_document.uri)?;
    let proj = &server.proj;
    let pos = Pos::from_lsp(params.position);

    let syntax = proj.syntax.get(proj, pkg);

    let Some(node) = node_of_interest(&syntax, pos) else {
        return Ok(None);
    };

    let Some(span) = selection_span(node.with_cst(&syntax.cst), &syntax.ctx.origins) else {
        return Ok(None);
    };

    Ok(Some(lsp_types::PrepareRenameResponse::Range(
        span.to_lsp(&syntax.ctx.origins),
    )))
}
pub fn rename(
    server: &mut Server,
    params: lsp_types::RenameParams,
) -> LspResult<Option<lsp_types::WorkspaceEdit>> {
    let pkg = server.uri_to_pkg(&params.text_document_position.text_document.uri)?;
    let proj = &server.proj;
    let pos = Pos::from_lsp(params.text_document_position.position);
    let new_name = params.new_name;

    let syntax = proj.syntax.get(proj, pkg);

    let Some(node) = node_of_interest(&syntax, pos) else {
        return Ok(None);
    };

    let Some(refs) = find_references_for(proj, pkg, node.with_cst(&syntax.cst), true, true) else {
        return Ok(None);
    };

    dbg!(&refs);

    let mut file_spans: HashMap<FileHandle, Vec<FileSpan>> = HashMap::new();

    dbg!(&file_spans);

    for (pkg, node) in refs {
        let file = proj.get_pkg(pkg).file;
        let spans = file_spans.entry(file).or_default();

        let syntax = proj.syntax.get(proj, pkg);
        let ot = &syntax.ctx.origins;
        let node = node.with_cst(&syntax.cst);

        let Some(span) = rename_span(node, ot) else {
            continue;
        };

        if span.origin != ROOT_ORIGIN {
            dbg!(span);
            continue;
        }

        let span = span.text_span;

        let file_contents = ot.root();

        // Check for overlapping edits, since one file might map to multiple packages.
        if dbg!(spans.iter().find(|s| span.overlaps(s, &file_contents))).is_some() {
            dbg!(span);
            continue;
        }

        spans.push(span);
    }

    let mut ops = file_spans
        .into_iter()
        .map(|(file, spans)| {
            let file_contents = proj.file_contents.get(proj, file);
            let edits = spans
                .into_iter()
                .map(|s| {
                    OneOf::Left(TextEdit {
                        range: s.to_lsp(&file_contents),
                        new_text: new_name.clone(),
                    })
                })
                .collect();
            let uri = server.file_to_uri(file)?;

            Ok(lsp_types::DocumentChangeOperation::Edit(
                lsp_types::TextDocumentEdit {
                    text_document: lsp_types::OptionalVersionedTextDocumentIdentifier {
                        uri,
                        version: None,
                    },
                    edits,
                },
            ))
        })
        .collect::<LspResult<Vec<_>>>()?;

    let node = node.with_cst(&syntax.cst);
    if let Some((pkg, node)) = definition_for(proj, pkg, node) {
        let syntax = proj.syntax.get(proj, pkg);
        let node = node.with_cst(&syntax.cst);
        if let Some(TreeKind::Package) = node.open_kind() {
            let file = proj.get_pkg(pkg).file;
            if let Some(path) = &proj.get_file(file).path {
                let ext = path.extension();
                let mut path = path.with_file_name(new_name);
                if let Some(ext) = ext {
                    path.set_extension(ext);
                }
                let new_uri = path_to_uri(&path)?;
                ops.push(lsp_types::DocumentChangeOperation::Op(
                    lsp_types::ResourceOp::Rename(lsp_types::RenameFile {
                        old_uri: server.file_to_uri(file)?,
                        new_uri,
                        options: None,
                        annotation_id: None,
                    }),
                ));
            }
        }
    }

    dbg!(&ops);

    Ok(Some(lsp_types::WorkspaceEdit {
        changes: None,
        document_changes: Some(lsp_types::DocumentChanges::Operations(ops)),
        change_annotations: None,
    }))
}

pub fn rename_span(node: NodeRef<'_>, ot: &OriginTable) -> Option<Span> {
    match node.open_kind() {
        Some(TreeKind::Package) => Package::cast(node).name().and_then(|n| n.syntax().span(ot)),
        _ => selection_span(node, ot),
    }
}