use std::{collections::HashMap, thread::park_timeout};
use lady_deirdre::{lexis::ToSpan, sync::Trigger};
use log::{error, warn};
use lsp_types::{
error_codes::{REQUEST_CANCELLED, REQUEST_FAILED},
request::CodeActionRequest,
CodeAction,
CodeActionContext,
CodeActionKind,
CodeActionOrCommand,
Range,
TextEdit,
Uri,
WorkspaceEdit,
};
use crate::{
analysis::{ModuleError, ModuleRead},
server::{
diagnostics::DiagnosticData,
file::{LspModule, ANALYSIS_PRIORITY},
logger::LSP_SERVER_LOG,
rpc::{LspHandle, OutgoingEx, RpcId, RpcLatches},
tasks::{Task, TaskExecution, COOL_DOWN},
utils::span_to_range,
RpcSender,
},
};
pub(super) struct SendCodeAction {
pub(super) latches: RpcLatches,
pub(super) outgoing: RpcSender,
pub(super) module: LspModule,
}
impl Task for SendCodeAction {
const EXECUTION: TaskExecution = TaskExecution::ExecuteEach;
type Config = Self;
type Message = SendCodeActionMessage;
#[inline(always)]
fn init(config: Self::Config) -> Self {
config
}
fn handle(&mut self, message: Self::Message) -> bool {
loop {
if message.cancel.is_active() {
warn!(target: LSP_SERVER_LOG, "[{}] Send code action cancelled by the client.", message.uri.as_str());
self.outgoing.send_err_response(
&self.latches,
message.id,
REQUEST_CANCELLED,
"Send code action cancelled by the client.",
);
break;
}
let handle = LspHandle::new(&message.cancel);
let module_read_guard = match self.module.as_ref().read(&handle, ANALYSIS_PRIORITY) {
Ok(guard) => guard,
Err(ModuleError::Interrupted(_)) => {
if message.cancel.is_active() {
warn!(target: LSP_SERVER_LOG, "[{}] Send code action cancelled by the client.", message.uri.as_str());
self.outgoing.send_err_response(
&self.latches,
message.id,
REQUEST_CANCELLED,
"Send code action cancelled by the client.",
);
break;
}
warn!(target: LSP_SERVER_LOG, "[{}] Send code action interrupted.", message.uri.as_str());
park_timeout(COOL_DOWN);
continue;
}
Err(error) => {
error!(target: LSP_SERVER_LOG, "[{}] Send code action error. {error}", message.uri.as_str());
self.outgoing.send_err_response(
&self.latches,
message.id,
REQUEST_FAILED,
"Send code action error.",
);
break;
}
};
let module_text = module_read_guard.text();
let mut actions = Vec::new();
for diagnostic in message.context.diagnostics {
let Some(data) = &diagnostic.data else {
continue;
};
let quickfix = DiagnosticData::from(data).0;
let mut title = String::new();
let mut edits = Vec::new();
if let Some(text) = quickfix.set_text_to_origin {
if !title.is_empty() {
title.push_str(" and ");
}
title.push_str(&format!("rename to {text:?}"));
edits.push(TextEdit {
range: diagnostic.range,
new_text: text,
});
}
if let Some(implement_use_of) = quickfix.implement_use_of {
let content_origin = module_read_guard.content_origin();
if let Some(mut span) = content_origin.to_position_span(&module_text) {
if !title.is_empty() {
title.push_str(" and ");
}
title.push_str(&format!("import {implement_use_of:?} package"));
span.end = span.start;
let range = span_to_range(&span);
edits.push(TextEdit {
range,
new_text: format!("use {implement_use_of};\n\n"),
});
}
}
if !edits.is_empty() {
actions.push(CodeActionOrCommand::CodeAction(CodeAction {
title,
kind: Some(CodeActionKind::QUICKFIX),
edit: Some(WorkspaceEdit {
changes: Some(HashMap::from([(message.uri.clone(), edits)])),
..WorkspaceEdit::default()
}),
..CodeAction::default()
}));
}
}
self.outgoing.send_ok_response::<CodeActionRequest>(
&self.latches,
message.id,
match actions.is_empty() {
true => None,
false => Some(actions),
},
);
break;
}
true
}
#[inline(always)]
fn module(&self) -> &LspModule {
&self.module
}
}
pub(super) struct SendCodeActionMessage {
pub(super) id: RpcId,
pub(super) uri: Uri,
pub(super) cancel: Trigger,
#[allow(unused)]
pub(super) range: Range,
pub(super) context: CodeActionContext,
}