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(¶ms.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(¶ms.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();
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),
}
}