mod rename_decl;
mod rename_member;
mod rename_type;
use std::collections::HashMap;
use glua_code_analysis::{LuaCompilation, LuaSemanticDeclId, SemanticDeclLevel, SemanticModel};
use glua_parser::{
LuaAst, LuaAstNode, LuaComment, LuaDocTagParam, LuaLiteralExpr, LuaSyntaxKind, LuaSyntaxNode,
LuaSyntaxToken, LuaTokenKind,
};
use lsp_types::{
ClientCapabilities, OneOf, PrepareRenameResponse, RenameOptions, RenameParams,
ServerCapabilities, TextDocumentPositionParams, WorkspaceEdit,
};
use rename_decl::rename_decl_references;
use rename_member::rename_member_references;
use rename_type::rename_type_references;
use rowan::TokenAtOffset;
use tokio_util::sync::CancellationToken;
use crate::context::ServerContextSnapshot;
use super::RegisterCapabilities;
pub async fn on_rename_handler(
context: ServerContextSnapshot,
params: RenameParams,
cancel_token: CancellationToken,
) -> Option<WorkspaceEdit> {
let uri = params.text_document_position.text_document.uri;
let analysis = context.read_analysis(&cancel_token).await?;
let file_id = analysis.get_file_id(&uri)?;
let position = params.text_document_position.position;
rename(&analysis, file_id, position, params.new_name)
}
pub async fn on_prepare_rename_handler(
context: ServerContextSnapshot,
params: TextDocumentPositionParams,
cancel_token: CancellationToken,
) -> Option<PrepareRenameResponse> {
let uri = params.text_document.uri;
let analysis = context.read_analysis(&cancel_token).await?;
let file_id = analysis.get_file_id(&uri)?;
let position = params.position;
let semantic_model = analysis.compilation.get_semantic_model(file_id)?;
let root = semantic_model.get_root();
let document = semantic_model.get_document();
let position_offset =
document.get_offset(position.line as usize, position.character as usize)?;
if position_offset > root.syntax().text_range().end() {
return None;
}
let token = match root.syntax().token_at_offset(position_offset) {
TokenAtOffset::Single(token) => token,
TokenAtOffset::Between(left, right) => {
if left.kind() == LuaTokenKind::TkName.into()
|| left.kind() == LuaTokenKind::TkInt.into()
{
left
} else {
right
}
}
TokenAtOffset::None => {
return None;
}
};
if matches!(
token.kind().into(),
LuaTokenKind::TkName | LuaTokenKind::TkInt | LuaTokenKind::TkString
) {
let range = document.to_lsp_range(token.text_range())?;
let placeholder = token.text().to_string();
Some(PrepareRenameResponse::RangeWithPlaceholder { range, placeholder })
} else {
None
}
}
pub fn rename(
analysis: &glua_code_analysis::EmmyLuaAnalysis,
file_id: glua_code_analysis::FileId,
position: lsp_types::Position,
new_name: String,
) -> Option<WorkspaceEdit> {
let semantic_model = analysis.compilation.get_semantic_model(file_id)?;
let root = semantic_model.get_root();
let position_offset = {
let document = semantic_model.get_document();
document.get_offset(position.line as usize, position.character as usize)?
};
if position_offset > root.syntax().text_range().end() {
return None;
}
let token = match root.syntax().token_at_offset(position_offset) {
TokenAtOffset::Single(token) => token,
TokenAtOffset::Between(left, right) => {
if left.kind() == LuaTokenKind::TkName.into() {
left
} else {
right
}
}
TokenAtOffset::None => {
return None;
}
};
rename_references(&semantic_model, &analysis.compilation, token, new_name)
}
#[allow(clippy::mutable_key_type)]
fn rename_references(
semantic_model: &SemanticModel,
compilation: &LuaCompilation,
token: LuaSyntaxToken,
new_name: String,
) -> Option<WorkspaceEdit> {
let mut result = HashMap::new();
let semantic_decl = match get_target_node(token.clone()) {
Some(node) => semantic_model.find_decl(node.into(), SemanticDeclLevel::NoTrace),
None => semantic_model.find_decl(token.into(), SemanticDeclLevel::NoTrace),
}?;
match semantic_decl {
LuaSemanticDeclId::LuaDecl(decl_id) => {
rename_decl_references(semantic_model, compilation, decl_id, new_name, &mut result);
}
LuaSemanticDeclId::Member(member_id) => {
rename_member_references(
semantic_model,
compilation,
member_id,
new_name,
&mut result,
);
}
LuaSemanticDeclId::TypeDecl(type_decl_id) => {
rename_type_references(semantic_model, type_decl_id, new_name, &mut result);
}
_ => {}
}
let changes = result
.into_iter()
.filter(|(uri, _)| {
if let Some(file_id) = semantic_model.get_db().get_vfs().get_file_id(uri) {
!semantic_model.get_db().get_module_index().is_std(&file_id)
} else {
true
}
})
.map(|(uri, ranges)| {
let text_edits = ranges
.into_iter()
.map(|(range, new_text)| lsp_types::TextEdit { range, new_text })
.collect();
(uri, text_edits)
})
.collect();
Some(WorkspaceEdit {
changes: Some(changes),
document_changes: None,
change_annotations: None,
})
}
fn get_target_node(token: LuaSyntaxToken) -> Option<LuaSyntaxNode> {
let parent = token.parent()?;
match parent.kind().into() {
LuaSyntaxKind::LiteralExpr => {
let literal_expr = LuaLiteralExpr::cast(parent)?;
literal_expr.syntax().parent()
}
LuaSyntaxKind::DocTagParam => {
let doc_tag_param = LuaDocTagParam::cast(parent)?;
let name = doc_tag_param.get_name_token()?;
let name_text = name.get_name_text();
let comment = doc_tag_param.get_parent::<LuaComment>()?;
let owner = comment.get_owner()?;
match owner {
LuaAst::LuaLocalFuncStat(local_func_stat) => {
let closure_expr = local_func_stat.get_closure()?;
let param_list = closure_expr.get_params_list()?;
let param_name = param_list.get_params().find(|param| {
if let Some(name_token) = param.get_name_token() {
name_token.get_name_text() == name_text
} else {
false
}
})?;
Some(param_name.syntax().clone())
}
LuaAst::LuaFuncStat(func_stat) => {
let closure_expr = func_stat.get_closure()?;
let param_list = closure_expr.get_params_list()?;
let param_name = param_list.get_params().find(|param| {
if let Some(name_token) = param.get_name_token() {
name_token.get_name_text() == name_text
} else {
false
}
})?;
Some(param_name.syntax().clone())
}
_ => None,
}
}
_ => None,
}
}
pub struct RenameCapabilities;
impl RegisterCapabilities for RenameCapabilities {
fn register_capabilities(server_capabilities: &mut ServerCapabilities, _: &ClientCapabilities) {
server_capabilities.rename_provider = Some(OneOf::Right(RenameOptions {
prepare_provider: Some(true),
work_done_progress_options: Default::default(),
}));
}
}