use {
crate::{
TRACER,
database::{DynPartition, PartitionKey, PartitionWriteContextRef},
partitions::{DocumentSymbols, TextDocumentPosition},
protocol::{
jsonrpc,
lsp::{
Range, TextDocumentPositionParams, WorkDoneProgressOptions,
WorkDoneProgressParams, WorkspaceEdit,
},
macros::lsp_enum,
},
record::LaburnumRecordRef,
scheduler::task::TaskContext,
},
opentelemetry::trace::FutureExt,
serde::{Deserialize, Serialize},
};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RenameParams {
#[serde(flatten)]
pub text_document_position: TextDocumentPositionParams,
pub new_name: String,
#[serde(flatten)]
pub work_done_progress_params: WorkDoneProgressParams,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RenameOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub prepare_provider: Option<bool>,
#[serde(flatten)]
pub work_done_progress_options: WorkDoneProgressOptions,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RenameClientCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub dynamic_registration: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prepare_support: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prepare_support_default_behavior: Option<PrepareSupportDefaultBehavior>,
#[serde(skip_serializing_if = "Option::is_none")]
pub honors_change_annotations: Option<bool>,
}
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(transparent)]
pub struct PrepareSupportDefaultBehavior(i32);
lsp_enum! {
impl PrepareSupportDefaultBehavior {
const IDENTIFIER = 1;
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
#[serde(rename_all = "camelCase")]
pub enum PrepareRenameResponse {
Range(Range),
RangeWithPlaceholder {
range: Range,
placeholder: String,
},
#[serde(rename_all = "camelCase")]
DefaultBehavior {
default_behavior: bool,
},
}
pub trait RenameService<
P: crate::database::storage::Partitions,
T: crate::protocol::lsp::LanguageServer<P>,
>: Send + Sync + 'static
{
fn rename(
&self,
params: RenameParams,
ctx: &mut TaskContext<P, T>,
_writer: &mut PartitionWriteContextRef<'_, P>,
) -> impl std::future::Future<Output = jsonrpc::Result<Option<WorkspaceEdit>>> + Send
{
let cx = otel::span!(^
"laburnum.lsp.rename",
"document.uri" = params.text_document_position.text_document.uri.to_string(),
"position.line" = params.text_document_position.position.line as i64,
"position.character" = params.text_document_position.position.character as i64,
"new_name" = params.new_name.to_string()
);
async move {
let uri = params.text_document_position.text_document.uri;
let position = params.text_document_position.position;
let new_name = params.new_name;
let source_key = {
let cache = ctx.source_cache();
let guard = cache.read();
match guard.latest_key(&uri) {
| Some(key) => key,
| None => return Ok(None),
}
};
use crate::source::line_ops::LineOps;
let encoding = ctx.position_encoding();
let source_cache = ctx.source_cache_reader();
let byte_offset = {
let source = match source_cache.get_source(source_key) {
Some(s) => s,
None => return Ok(None),
};
match source.line_col_to_byte(position.line, position.character, &encoding) {
Some(o) => o as u64,
None => return Ok(None),
}
};
let Some(symbol_ident) = ctx
.query_client()
.span_index_get::<TextDocumentPosition>(&uri, byte_offset)
.and_then(|record| {
record.as_text_document_position()
.map(|r| r.symbol_ident())
})
else {
return Ok(None);
};
let all_positions = ctx
.query_client()
.query_partition(TextDocumentPosition)
.sort_key_begins_with(crate::partitions::text_document_position::TextDocumentPositionSortKey::FilePrefix { source_key })
.execute()
.await;
let mut locations = Vec::new();
let matching_positions: Vec<_> = all_positions
.iter()
.filter_map(|record_ref| {
let pos = record_ref.as_text_document_position()?;
if pos.symbol_ident() != symbol_ident {
return None;
}
let kind = pos.kind();
if kind != crate::partitions::text_document_position::PositionKind::Ident
&& kind != crate::partitions::text_document_position::PositionKind::Reference
{
return None;
}
Some((pos.symbol_hash(), pos.position()))
})
.collect();
for (hash, position) in matching_positions {
if let Some(ident_range) = ctx
.query_client()
.get_by_hash::<DocumentSymbols>(hash)
.and_then(|r| r.as_document_symbol().map(|sym| sym.get_ident_range()))
{
let ident_len = ident_range.end.character - ident_range.start.character;
let range = crate::protocol::lsp::Range {
start: position,
end: crate::protocol::lsp::Position {
line: position.line,
character: position.character + ident_len,
},
};
locations.push(crate::protocol::lsp::Location {
uri: uri.clone(),
range,
});
}
}
if locations.is_empty() {
return Ok(None);
}
let mut changes = std::collections::HashMap::new();
for location in locations {
let edit = crate::protocol::lsp::TextEdit {
range: location.range,
new_text: new_name.clone(),
};
changes
.entry(location.uri)
.or_insert_with(Vec::new)
.push(edit);
}
Ok(Some(crate::protocol::lsp::WorkspaceEdit {
changes: Some(changes),
document_changes: None,
change_annotations: None,
}))
}.with_context(cx)
}
const RENAME_LANE: crate::scheduler::lanes::Lane =
crate::scheduler::lanes::DEFAULT_LANE;
fn prepare_rename(
&self,
params: TextDocumentPositionParams,
ctx: &mut TaskContext<P, T>,
writer: &mut PartitionWriteContextRef<'_, P>,
) -> impl std::future::Future<
Output = jsonrpc::Result<Option<PrepareRenameResponse>>,
> + Send {
let _ = writer;
let cx = otel::span!(^
"laburnum.lsp.prepare_rename",
"document.uri" = params.text_document.uri.to_string(),
"position.line" = params.position.line as i64,
"position.character" = params.position.character as i64
);
async move {
let uri = params.text_document.uri;
let position = params.position;
let source_key = {
let cache = ctx.source_cache();
let guard = cache.read();
match guard.latest_key(&uri) {
| Some(key) => key,
| None => return Ok(None),
}
};
use crate::source::line_ops::LineOps;
let encoding = ctx.position_encoding();
let source_cache = ctx.source_cache_reader();
let byte_offset = {
let source = match source_cache.get_source(source_key) {
| Some(s) => s,
| None => return Ok(None),
};
match source.line_col_to_byte(
position.line,
position.character,
&encoding,
) {
| Some(o) => o as u64,
| None => return Ok(None),
}
};
let symbol_hash = ctx
.query_client()
.span_index_get::<TextDocumentPosition>(&uri, byte_offset)
.and_then(|record| {
record.as_text_document_position().map(|r| r.symbol_hash())
});
if let Some(range) = symbol_hash.and_then(|hash| {
ctx
.query_client()
.get_by_hash::<DocumentSymbols>(hash)
.and_then(|r| {
r.as_document_symbol()
.and_then(|sym| sym.rename_range(&source_cache, &encoding))
})
}) {
Ok(Some(PrepareRenameResponse::Range(range)))
} else {
Ok(None)
}
}
.with_context(cx)
}
const PREPARE_RENAME_LANE: crate::scheduler::lanes::Lane =
crate::scheduler::lanes::DEFAULT_LANE;
}