use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;
use tower_lsp_server::Client;
use tower_lsp_server::jsonrpc::Result;
use tower_lsp_server::ls_types::*;
use super::super::conversions::offset_to_position;
use super::super::helpers::get_document_and_config;
use crate::lsp::DocumentState;
use crate::{parser, range_utils};
pub(crate) async fn format_document(
client: &Client,
document_map: Arc<Mutex<HashMap<String, DocumentState>>>,
salsa_db: Arc<Mutex<crate::salsa::SalsaDb>>,
workspace_root: Arc<Mutex<Option<PathBuf>>>,
params: DocumentFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
let uri = params.text_document.uri;
log::debug!("format_document uri={}", *uri);
client
.log_message(
MessageType::INFO,
format!("Formatting request for {}", *uri),
)
.await;
let (text, config) = match get_document_and_config(
client,
&document_map,
&salsa_db,
&workspace_root,
&uri,
)
.await
{
Some(result) => result,
None => {
client
.log_message(MessageType::ERROR, format!("Document not found: {}", *uri))
.await;
return Ok(None);
}
};
let text_clone = text.clone();
let formatted = tokio::task::spawn_blocking(move || {
tokio::runtime::Runtime::new()
.expect("Failed to create runtime")
.block_on(crate::format_async(&text_clone, Some(config), None))
})
.await
.map_err(|_| tower_lsp_server::jsonrpc::Error::internal_error())?;
if formatted == text {
return Ok(None);
}
let end_position = offset_to_position(&text, text.len());
let range = Range {
start: Position {
line: 0,
character: 0,
},
end: end_position,
};
Ok(Some(vec![TextEdit {
range,
new_text: formatted,
}]))
}
pub(crate) async fn format_range(
client: &Client,
document_map: Arc<Mutex<HashMap<String, DocumentState>>>,
salsa_db: Arc<Mutex<crate::salsa::SalsaDb>>,
workspace_root: Arc<Mutex<Option<PathBuf>>>,
params: DocumentRangeFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
let uri = params.text_document.uri;
let range = params.range;
log::debug!(
"format_range uri={} start={:?} end={:?}",
*uri,
range.start,
range.end
);
client
.log_message(
MessageType::INFO,
format!(
"Range formatting request for {} (lines {}-{})",
*uri,
range.start.line + 1,
range.end.line + 1
),
)
.await;
let (text, config) = match get_document_and_config(
client,
&document_map,
&salsa_db,
&workspace_root,
&uri,
)
.await
{
Some(result) => result,
None => {
client
.log_message(MessageType::ERROR, format!("Document not found: {}", *uri))
.await;
return Ok(None);
}
};
let start_line = (range.start.line + 1) as usize;
let mut end_line = (range.end.line + 1) as usize;
if range.end.character == 0 && range.end.line > range.start.line {
end_line = range.end.line as usize;
}
let start_offset = super::super::conversions::position_to_offset(&text, range.start);
let end_offset = super::super::conversions::position_to_offset(&text, range.end);
client
.log_message(
MessageType::INFO,
format!(
"Range formatting selection bytes {:?}..{:?} (start {:?}, end {:?})",
start_offset, end_offset, range.start, range.end
),
)
.await;
let text_clone = text.clone();
let config_clone = config.clone();
let formatted = tokio::task::spawn_blocking(move || {
let tree = parser::parse(&text_clone, Some(config_clone.clone()));
let expanded_range =
range_utils::expand_line_range_to_blocks(&tree, &text_clone, start_line, end_line);
let output = tokio::runtime::Runtime::new()
.expect("Failed to create runtime")
.block_on(crate::format_async(
&text_clone,
Some(config_clone),
Some((start_line, end_line)),
));
(output, expanded_range)
})
.await
.map_err(|_| tower_lsp_server::jsonrpc::Error::internal_error())?;
let (formatted, expanded_range) = formatted;
if formatted.is_empty() || formatted == text {
return Ok(None);
}
if let Some((start_offset, end_offset)) = expanded_range {
client
.log_message(
MessageType::INFO,
format!(
"Range formatting expanded to byte range {}..{}",
start_offset, end_offset
),
)
.await;
}
let Some((start_offset, end_offset)) = expanded_range else {
return Ok(None);
};
let edit_range = Range {
start: offset_to_position(&text, start_offset),
end: offset_to_position(&text, end_offset.min(text.len())),
};
client
.log_message(
MessageType::INFO,
format!(
"Range formatting edit range {:?}..{:?}",
edit_range.start, edit_range.end
),
)
.await;
Ok(Some(vec![TextEdit {
range: edit_range,
new_text: formatted,
}]))
}