mod example_support;
use std::collections::HashMap;
use std::sync::Arc;
use lspf::types::{
PrepareRenameResponse, RenameOptions, RenameParams, TextDocumentPositionParams, TextEdit,
WorkspaceEdit,
};
use lspf::{CancellationToken, LspError, Server, ServerContext};
struct State;
fn renameable(text: &str, word: &str) -> bool {
text.lines().any(|line| {
["type ", "fn "].into_iter().any(|prefix| {
line.strip_prefix(prefix)
.and_then(|rest| rest.split(['(', ' ', '=']).next())
== Some(word)
})
})
}
async fn prepare(
_: Arc<State>,
ctx: ServerContext,
params: TextDocumentPositionParams,
_: CancellationToken,
) -> Result<Option<PrepareRenameResponse>, LspError> {
let text = example_support::text(&ctx, ¶ms.text_document.uri)?;
let Some((word, range)) = example_support::word_at(&text, params.position) else {
return Ok(None);
};
Ok(
renameable(&text, &word).then_some(PrepareRenameResponse::RangeWithPlaceholder {
range,
placeholder: word,
}),
)
}
async fn rename(
_: Arc<State>,
ctx: ServerContext,
params: RenameParams,
_: CancellationToken,
) -> Result<Option<WorkspaceEdit>, LspError> {
let uri = params.text_document_position.text_document.uri;
let text = example_support::text(&ctx, &uri)?;
let Some((word, _)) = example_support::word_at(&text, params.text_document_position.position)
else {
return Ok(None);
};
if !renameable(&text, &word) {
return Ok(None);
}
let edits = example_support::word_ranges(&text, &word)
.into_iter()
.map(|range| TextEdit {
range,
new_text: params.new_name.clone(),
})
.collect();
Ok(Some(WorkspaceEdit {
changes: Some(HashMap::from([(uri, edits)])),
..WorkspaceEdit::default()
}))
}
#[tokio::main]
async fn main() -> lspf::Result<()> {
let server = Server::builder(State)
.feature(
lspf::features::rename(RenameOptions {
prepare_provider: None,
work_done_progress_options: Default::default(),
}),
rename,
)
.feature(lspf::features::prepare_rename(), prepare)
.build()
.expect("rename registrations are valid");
example_support::serve(server).await
}