use std::fmt::Debug; use std::path::{Path, PathBuf}; use std::sync::atomic::AtomicI32; use std::sync::atomic::Ordering::Relaxed; use Default::default; use crossbeam::channel::{Receiver, SendError, Sender}; use futures::FutureExt; use json_value_merge::Merge; use log::debug; use lsp_server::{ Message, Notification as N, Request as LRq, Response as Re, }; use lsp_types::notification::*; use lsp_types::request::*; use lsp_types::*; use rootcause::option_ext::OptionExt; use rust_analyzer::lsp::ext::*; use tokio::sync::oneshot; use ttools::*; use winit::platform::x11::ffi::BadImplementation; use crate::lsp::BehaviourAfter::{self, *}; use crate::lsp::init_opts::ra_config; use crate::lsp::{RequestError, Rq}; use crate::text::cursor::ceach; use crate::text::{RopeExt, SortTedits, TextArea}; #[derive(Debug)] pub struct Client { pub runtime: tokio::runtime::Runtime, pub tx: Sender, pub id: AtomicI32, pub initialized: Option, // pub pending: HashMap>, pub send_to: Sender<(i32, oneshot::Sender, BehaviourAfter)>, pub progress: &'static papaya::HashMap< ProgressToken, Option<(WorkDoneProgress, WorkDoneProgressBegin)>, >, pub diagnostics: &'static papaya::HashMap>, #[allow(dead_code)] // TODO: handle notifications from the server pub not_rx: Receiver, pub req_rx: Receiver, } impl Drop for Client { fn drop(&mut self) { panic!("please dont"); } } impl Client { pub fn open( &self, f: &Path, text: String, ) -> Result<(), SendError> { self.notify::(&DidOpenTextDocumentParams { text_document: TextDocumentItem { uri: url::Url::from_file_path(f).unwrap(), language_id: "rust".into(), version: 0, text, }, }) } pub fn close(&self, f: &Path) -> Result<(), SendError> { self.notify::(&DidCloseTextDocumentParams { text_document: f.tid(), }) } pub fn edit( &self, f: &Path, text: String, ) -> Result<(), SendError> { static V: AtomicI32 = AtomicI32::new(0); self.notify::( &DidChangeTextDocumentParams { text_document: VersionedTextDocumentIdentifier { uri: url::Url::from_file_path(f).unwrap(), version: V.fetch_add(1, Relaxed), }, content_changes: vec![TextDocumentContentChangeEvent { range: None, range_length: None, text, }], }, ) } pub fn resolve( &self, x: CompletionItem, ) -> Result> { self.request_immediate::(&x) } pub fn request_complete<'me>( &'me self, f: &Path, (x, y): (usize, usize), c: CompletionContext, ) -> impl Future< Output = Result< Option, RequestError, >, > + use<'me> { let (rx, _) = self .request_::(&CompletionParams { text_document_position: TextDocumentPositionParams { text_document: f.tid(), position: Position { line: y as _, character: x as _ }, }, work_done_progress_params: default(), partial_result_params: default(), context: Some(c), }) .unwrap(); rx } pub fn request_sig_help<'me>( &'me self, f: &Path, (x, y): (usize, usize), ) -> impl Future< Output = Result< Option, RequestError, >, > + use<'me> { self.request_::( &SignatureHelpParams { context: None, text_document_position_params: TextDocumentPositionParams { text_document: f.tid(), position: Position { line: y as _, character: x as _, }, }, work_done_progress_params: default(), }, ) .unwrap() .0 } pub fn _pull_all_diag( &self, _f: PathBuf, ) -> impl Future< Output = Result<(), RequestError>, > { let r = self .request::(&default()) .unwrap() .0; // log::info!("pulling diagnostics"); async move { let x = r.await?; log::info!("{x:?}"); // match x { // DocumentDiagnosticReportResult::Report( // DocumentDiagnosticReport::Full( // RelatedFullDocumentDiagnosticReport { // related_documents, // full_document_diagnostic_report: // FullDocumentDiagnosticReport { items, .. }, // }, // ), // ) => { // let l = self.diagnostics.guard(); // self.diagnostics.insert(f.tid().uri, items, &l); // for (uri, rel) in // related_documents.into_iter().flatten() // { // match rel { // DocumentDiagnosticReportKind::Full( // FullDocumentDiagnosticReport { // items, .. // }, // ) => { // self.diagnostics.insert(uri, items, &l); // } // DocumentDiagnosticReportKind::Unchanged(_) => { // } // } // } // log::info!("pulled diagnostics"); // } // _ => bail!("fuck that"), // }; Ok(()) } } pub fn _pull_diag( &self, f: PathBuf, previous: Option, ) -> impl Future< Output = Result< Option, RequestError, >, > { let p = DocumentDiagnosticParams { text_document: f.tid(), identifier: try { match self .initialized .as_ref()? .capabilities .diagnostic_provider .as_ref()? { DiagnosticServerCapabilities::RegistrationOptions( x, ) => x.diagnostic_options.identifier.clone()?, _ => None?, } }, previous_result_id: previous, work_done_progress_params: default(), partial_result_params: default(), }; let (r, _) = self .request::(&p) .unwrap(); log::info!("pulling diagnostics"); async move { let x = match r.await { Ok(x) => x, Err(RequestError::Cancelled(_, y)) if y.retrigger_request => { self.request::(&p,).unwrap().0.await? }, Err(e) => return Err(e), }; // dbg!(&x); match x.clone() { DocumentDiagnosticReportResult::Report( DocumentDiagnosticReport::Full( RelatedFullDocumentDiagnosticReport { related_documents, full_document_diagnostic_report: FullDocumentDiagnosticReport { items, result_id, }, }, ), ) => { let l = self.diagnostics.guard(); self.diagnostics.insert(f.tid().uri, items, &l); for (uri, rel) in related_documents.into_iter().flatten() { match rel { DocumentDiagnosticReportKind::Full( FullDocumentDiagnosticReport { items, .. }, ) => { self.diagnostics.insert(uri, items, &l); } DocumentDiagnosticReportKind::Unchanged(_) => { } } } log::info!("pulled diagnostics"); Ok(result_id) } _ => unimplemented!(), } } } pub fn document_highlights<'me>( &'me self, f: &Path, cursor: Position, ) -> impl Future< Output = Result< Vec, RequestError, >, > + use<'me> { let p = DocumentHighlightParams { text_document_position_params: TextDocumentPositionParams { text_document: f.tid(), position: cursor, }, work_done_progress_params: default(), partial_result_params: default(), }; self.request_::(&p) .unwrap() .0 .map(|x| x.map(|x| x.unwrap_or_default())) } pub fn document_symbols( &'static self, p: &Path, ) -> impl Future< Output = Result< Option, RequestError, >, > { self.request_::( &DocumentSymbolParams { text_document: p.tid(), work_done_progress_params: default(), partial_result_params: default(), }, ) .unwrap() .0 } pub fn workspace_symbols( &'static self, f: String, ) -> impl Future< Output = Result< Option, RequestError, >, > { self.request_::( &lsp_types::WorkspaceSymbolParams { query: f, search_scope: Some( lsp_types::WorkspaceSymbolSearchScope::Workspace, ), search_kind: Some( lsp_types::WorkspaceSymbolSearchKind::AllSymbols, ), ..Default::default() }, ) .unwrap() .0 } pub fn matching_brace_at( &self, f: &Path, x: Vec, ) -> Result< Vec>, RequestError, > { self.request_immediate::(&MatchingBraceParams { text_document: f.tid(), positions: x, }) } pub fn matching_brace<'a>( &'static self, f: &Path, t: &'a mut TextArea, ) { if let Ok(x) = self.matching_brace_at(f, t.cursor.positions(&t.rope)) { for (c, p) in t.cursor.inner.iter_mut().zip(x) { if let Some(p) = p { c.position = t.rope.l_position(p.1).unwrap(); } } } } pub fn legend(&self) -> Option<&SemanticTokensLegend> { match &self.initialized{Some(lsp_types::InitializeResult {capabilities: ServerCapabilities {semantic_tokens_provider:Some(SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions{legend,..})),..}, ..})=> {Some(legend)},_ => None,} } pub fn inlay( &'static self, f: &Path, t: &TextArea, ) -> impl Future< Output = Result< Vec, RequestError, >, > + use<> { self.request_::(&InlayHintParams { work_done_progress_params: default(), text_document: f.tid(), range: t.to_l_range(lower::saturating::math!{ t.rope.try_line_to_char(t.vo-t.r).unwrap_or(0)..t.rope.try_line_to_char(t.vo + t.r + t.r).unwrap_or(t.rope.len_chars()) }).unwrap() }).unwrap().0.map(|x| x.map(Option::unwrap_or_default)) // async { // if let Ok(z) = z.await { // let mut into = vec![]; // for lem in z.into_iter(){ // // if let Some(_) = lem.data { // into.push(self.request::(&lem).unwrap().0.await.unwrap()); // // } // } // // std::fs::write("inlay", serde_json::to_string_pretty(&into).unwrap()).unwrap(); // Ok(into) // } else { // panic!() // } // } } pub fn format( &'static self, f: &Path, ) -> impl Future< Output = Result>, RequestError>, > { self.request::( &DocumentFormattingParams { text_document: f.tid(), options: FormattingOptions { tab_size: 4, insert_spaces: false, properties: default(), trim_trailing_whitespace: Some(true), insert_final_newline: Some(true), trim_final_newlines: Some(false), }, work_done_progress_params: default(), }, ) .unwrap() .0 } pub fn rq_semantic_tokens( &'static self, to: &mut Rq< Box<[SemanticToken]>, Box<[SemanticToken]>, (), RequestError, >, f: &Path, ) -> Result<(), RequestError> { debug!("requested semantic tokens"); let Some(b"rs") = f.extension().map(|x| x.as_encoded_bytes()) else { return Ok(()); }; let (rx, _) = self.request::( &SemanticTokensParams { work_done_progress_params: default(), partial_result_params: default(), text_document: f.tid(), }, )?; let x = self.runtime.spawn(async move { let t = rx.await; let y = t?.unwrap(); debug!("received semantic tokens"); let r = match y { SemanticTokensResult::Partial(_) => panic!("i told the lsp i dont support this"), SemanticTokensResult::Tokens(x) => x.data.into_boxed_slice(), }; Ok(r) }); to.request(x); Ok(()) } pub fn enter<'a>( &self, f: &Path, t: &'a mut TextArea, ) -> rootcause::Result<()> { ceach!(t.cursor, |c| try bikeshed rootcause::Result<()> { let r = self .request_immediate::( &TextDocumentPositionParams { text_document: f.tid(), position: t.to_l_position(*c).unwrap(), }, )?; match r { None => t.enter(), Some(mut r) => { r.sort_tedits(); for f in r { t.apply_snippet_tedit(&f)?; } } } } => ?); Ok(()) } pub fn runnables( &'static self, t: &Path, c: Option, ) -> Result< impl Future, RequestError>> + use<>, SendError, > { self.request::(&RunnablesParams { text_document: t.tid(), position: c, }) .map(fst) } pub fn _child_modules( &'static self, p: Position, t: &Path, ) -> Result< impl Future< Output = Result< ::Result, RequestError, >, >, SendError, > { self.request::(&TextDocumentPositionParams { position: p, text_document: t.tid(), }) .map(fst) } pub fn go_to_implementations( &self, tdpp: TextDocumentPositionParams, ) -> Result< impl Future< Output = Result< ::Result, RequestError, >, >, SendError, > { self.request::(&GotoImplementationParams { text_document_position_params: tdpp, work_done_progress_params: default(), partial_result_params: default(), }) .map(fst) } pub fn go_to_references( &self, tdpp: TextDocumentPositionParams, ) -> Result< impl Future< Output = Result< Option>, RequestError, >, >, SendError, > { self.request::(&ReferenceParams { text_document_position: tdpp, work_done_progress_params: default(), partial_result_params: default(), context: ReferenceContext { include_declaration: false }, }) .map(fst) } pub fn _update_config(&self, with: serde_json::Value) { let mut x = ra_config(); x.merge(&with); self.notify::( &DidChangeConfigurationParams { settings: x }, ) .unwrap(); } pub fn find_function( &self, at: TextDocumentPositionParams, ) -> Result< impl Future< Output = Result< Option, RequestError, >, >, SendError, > { self.request::(&CallHierarchyPrepareParams { text_document_position_params: at, work_done_progress_params: default(), }) .map(fst) .map(|x| x.map(|x| x.map(|x| x.and_then(|mut x| x.try_remove(0))))) } pub async fn callers( &self, at: TextDocumentPositionParams, ) -> rootcause::Result> { let calls = self .request::( &CallHierarchyIncomingCallsParams { item: self .find_function(at)? .await? .context("no chi")?, work_done_progress_params: default(), partial_result_params: default(), }, )? .0 .await? .context("Couldnt find incoming calls")?; Ok(calls) } pub async fn calling( &self, at: TextDocumentPositionParams, ) -> rootcause::Result> { let calls = self .request::( &CallHierarchyOutgoingCallsParams { item: self .find_function(at)? .await? .context("no chi")?, work_done_progress_params: default(), partial_result_params: default(), }, )? .0 .await? .context("Couldnt find incoming calls")?; Ok(calls) } } pub trait PathURI { fn tid(&self) -> TextDocumentIdentifier; } impl PathURI for Path { fn tid(&self) -> TextDocumentIdentifier { TextDocumentIdentifier { uri: Url::from_file_path(self).expect("ok"), } } } pub macro tdpp($e:expr) { TextDocumentPositionParams { text_document: $e.origin.as_ref().unwrap().tid(), position: $e.text.to_l_position(*$e.text.cursor.first()).unwrap(), } }