use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::server::lsp::{
LspMessage,
rpc::{RequestId, RequestMessageBase, ResponseMessageBase},
textdocument::{DocumentUri, Range, TextDocumentIdentifier, TextEdit},
};
use super::{diagnostic::Diagnostic, workspace::WorkspaceEdit};
#[derive(Debug, Deserialize)]
pub struct CodeActionRequest {
#[serde(flatten)]
pub base: RequestMessageBase,
pub params: CodeActionParams,
}
impl CodeActionRequest {
pub(crate) fn get_id(&self) -> &RequestId {
&self.base.id
}
}
impl LspMessage for CodeActionRequest {}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeActionParams {
pub text_document: TextDocumentIdentifier,
pub range: Range,
pub context: CodeActionContext,
}
#[derive(Debug, Serialize, Deserialize, Hash, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum CodeActionKind {
#[serde(rename = "")]
Empty,
QuickFix,
Refactor,
#[serde(rename = "refactor.extract")]
RefactorExtract,
#[serde(rename = "refactor.inline")]
RefactorInline,
#[serde(rename = "refactor.rewrite")]
RefactorRewrite,
Source,
#[serde(rename = "source.shortenUris")]
SourceShortenURIs,
#[serde(rename = "source.organizeImports")]
SourceOrganizeImports,
#[serde(rename = "source.fixAll")]
SourceFixAll,
}
#[derive(Debug, Deserialize)]
pub struct CodeActionContext {
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Debug, Serialize)]
pub struct CodeActionResponse {
#[serde(flatten)]
base: ResponseMessageBase,
result: Vec<CodeAction>,
}
impl LspMessage for CodeActionResponse {}
impl CodeActionResponse {
pub fn new(id: &RequestId) -> Self {
Self {
base: ResponseMessageBase::success(id),
result: vec![],
}
}
#[cfg(test)]
pub(crate) fn add_code_action(&mut self, code_action: CodeAction) {
self.result.push(code_action);
}
pub(crate) fn add_code_actions(&mut self, code_actions: Vec<CodeAction>) {
self.result.extend(code_actions);
}
}
#[derive(Debug, Serialize)]
pub struct CodeAction {
pub title: String,
pub edit: WorkspaceEdit,
#[serde(skip_serializing_if = "Option::is_none")]
pub kind: Option<CodeActionKind>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub diagnostics: Vec<Diagnostic>,
}
impl CodeAction {
pub fn new(title: &str, kind: Option<CodeActionKind>) -> Self {
Self {
title: title.to_string(),
kind,
edit: WorkspaceEdit {
changes: Some(HashMap::new()),
},
diagnostics: vec![],
}
}
pub(crate) fn add_edit(&mut self, document_uri: &DocumentUri, change: TextEdit) {
if let Some(changes) = self.edit.changes.as_mut() {
changes
.entry(document_uri.to_string())
.and_modify(|e| e.push(change.clone()))
.or_insert(vec![change]);
}
}
}
#[cfg(test)]
mod test {
use crate::server::lsp::{
CodeAction, CodeActionResponse, WorkspaceEdit,
rpc::RequestId,
textdocument::{Range, TextEdit},
};
use std::collections::HashMap;
#[test]
fn serialize() {
let mut code_action_response = CodeActionResponse::new(&RequestId::Integer(42));
let changes = Some(HashMap::from([(
"file:///test.rq".to_string(),
vec![TextEdit::new(Range::new(0, 0, 0, 0), "test")],
)]));
let code_action = CodeAction {
title: "test-action".to_string(),
kind: None,
diagnostics: vec![],
edit: WorkspaceEdit { changes },
};
code_action_response.add_code_action(code_action);
let serialized_response = serde_json::to_string(&code_action_response).unwrap();
assert_eq!(
serialized_response,
r#"{"jsonrpc":"2.0","id":42,"result":[{"title":"test-action","edit":{"changes":{"file:///test.rq":[{"range":{"start":{"line":0,"character":0},"end":{"line":0,"character":0}},"newText":"test"}]}}}]}"#
)
}
}