//! Utilities for LSP-related boilerplate code. use std::{error::Error, ops::Range}; use lsp_server::Notification; use ra_db::Canceled; use ra_ide::LineIndex; use crate::{from_proto, global_state::GlobalState}; pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool { e.downcast_ref::().is_some() } pub(crate) fn notification_is( notification: &Notification, ) -> bool { notification.method == N::METHOD } #[derive(Debug, Eq, PartialEq)] pub(crate) enum Progress { Begin, Report, End, } impl Progress { pub(crate) fn percentage(done: usize, total: usize) -> f64 { (done as f64 / total.max(1) as f64) * 100.0 } } impl GlobalState { pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) { let message = message.into(); self.send_notification::( lsp_types::ShowMessageParams { typ, message }, ) } pub(crate) fn report_progress( &mut self, title: &str, state: Progress, message: Option, percentage: Option, ) { if !self.config.client_caps.work_done_progress { return; } let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title)); let work_done_progress = match state { Progress::Begin => { self.send_request::( lsp_types::WorkDoneProgressCreateParams { token: token.clone() }, |_, _| (), ); lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { title: title.into(), cancellable: None, message, percentage, }) } Progress::Report => { lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { cancellable: None, message, percentage, }) } Progress::End => { lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message }) } }; self.send_notification::(lsp_types::ProgressParams { token, value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress), }); } } pub(crate) fn apply_document_changes( old_text: &mut String, content_changes: Vec, ) { let mut line_index = LineIndex::new(old_text); // The changes we got must be applied sequentially, but can cross lines so we // have to keep our line index updated. // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we // remember the last valid line in the index and only rebuild it if needed. // The VFS will normalize the end of lines to `\n`. enum IndexValid { All, UpToLineExclusive(u64), } impl IndexValid { fn covers(&self, line: u64) -> bool { match *self { IndexValid::UpToLineExclusive(to) => to > line, _ => true, } } } let mut index_valid = IndexValid::All; for change in content_changes { match change.range { Some(range) => { if !index_valid.covers(range.end.line) { line_index = LineIndex::new(&old_text); } index_valid = IndexValid::UpToLineExclusive(range.start.line); let range = from_proto::text_range(&line_index, range); old_text.replace_range(Range::::from(range), &change.text); } None => { *old_text = change.text; index_valid = IndexValid::UpToLineExclusive(0); } } } } #[cfg(test)] mod tests { use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; use super::*; #[test] fn test_apply_document_changes() { macro_rules! c { [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => { vec![$(TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: $sl, character: $sc }, end: Position { line: $el, character: $ec }, }), range_length: None, text: String::from($text), }),+] }; } let mut text = String::new(); apply_document_changes(&mut text, vec![]); assert_eq!(text, ""); apply_document_changes( &mut text, vec![TextDocumentContentChangeEvent { range: None, range_length: None, text: String::from("the"), }], ); assert_eq!(text, "the"); apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]); assert_eq!(text, "the quick"); apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); assert_eq!(text, "quick foxes"); apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]); assert_eq!(text, "quick foxes\ndream"); apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]); assert_eq!(text, "quick foxes\nhave dream"); apply_document_changes( &mut text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"], ); assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); apply_document_changes( &mut text, c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], ); assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); assert_eq!(text, "the quick \nthey have quiet dreams\n"); text = String::from("❤️"); apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]); assert_eq!(text, "a❤️"); text = String::from("a\nb"); apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); assert_eq!(text, "adcb"); text = String::from("a\nb"); apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); assert_eq!(text, "ațc\ncb"); } }