A simple CPU rendered GUI IDE experience.
separate lsp
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/edi.rs | 40 | ||||
| -rw-r--r-- | src/edi/st.rs | 6 | ||||
| -rw-r--r-- | src/lsp.rs | 1125 | ||||
| -rw-r--r-- | src/lsp/client.rs | 505 | ||||
| -rw-r--r-- | src/lsp/communication.rs | 227 | ||||
| -rw-r--r-- | src/lsp/init_opts.rs | 362 | ||||
| -rw-r--r-- | src/lsp/rq.rs | 171 | ||||
| -rw-r--r-- | src/main.rs | 2 |
9 files changed, 1297 insertions, 1142 deletions
@@ -69,6 +69,7 @@ rangemap = { version = "1.7.1", features = ["const_fn", "nightly", "serde1"] } itern = "0.1.1" kitty-rc = { version = "0.4.2", git = "https://github.com/bend-n/kitty-rc-rs" } smol_str = "0.3.6" +futures = "0.3.32" [profile.dev.package] rust-analyzer.opt-level = 3 fimg.opt-level = 3 @@ -425,14 +425,11 @@ impl Editor { } } let r = &l.runtime; - self.requests.inlay.poll( - |x, p| { - x.ok().or(p.1).inspect(|x| { - self.text.set_inlay(x); - }) - }, - r, - ); + self.requests.inlay.poll(|x, p| { + x.ok().or(p.1).inspect(|x| { + self.text.set_inlay(x); + }) + }); self.requests.document_highlights.poll_r(|x, _| x.ok(), r, w); self.requests.diag.poll_r(|x, _| x.ok().flatten(), r, w); if let CompletionState::Complete(rq) = &mut self.requests.complete @@ -1122,12 +1119,7 @@ impl Editor { } } } - Some(Do::SymbolsSelect) => { - let State::Symbols(Rq { result: Some(x), .. }) = - &self.state - else { - unreachable!() - }; + Some(Do::SymbolsSelect(x)) => { if let Some(Ok(x)) = x.sel() && let Err(e) = try bikeshed anyhow::Result<()> { let r = match x.at { @@ -1478,12 +1470,7 @@ impl Editor { } Some(CDo::Finish(x)) => { let sel = x.sel(&filter(&self.text)); - let sel = lsp - .runtime - .block_on( - lsp.resolve(sel.clone()).unwrap(), - ) - .unwrap(); + let sel = lsp.resolve(sel.clone()).unwrap(); let CompletionItem { text_edit: Some(CompletionTextEdit::Edit(ed)), @@ -1499,13 +1486,12 @@ impl Editor { self.text.apply_snippet(&ed).unwrap(); } _ => { - let (s, _) = - self.text.apply(&ed).unwrap(); - self.text - .cursor - .first_mut() - .position = - s + ed.new_text.chars().count(); + self.text.apply(&ed).unwrap(); + // self.text + // .cursor + // .first_mut() + // .position = + // s + ed.new_text.chars().count(); } } if let Some(mut additional_tedits) = diff --git a/src/edi/st.rs b/src/edi/st.rs index 5c7568f..0db1017 100644 --- a/src/edi/st.rs +++ b/src/edi/st.rs @@ -104,7 +104,7 @@ Symbols(Rq { result: Some(_x), request: _rq }) => { K(Key::Named(Tab) if shift()) => _ [SymbolsSelectNext], K(Key::Named(ArrowDown)) => _ [SymbolsSelectNext], K(Key::Named(ArrowUp | Tab)) => _ [SymbolsSelectPrev], - K(Key::Named(Enter)) => _ [SymbolsSelect], + K(Key::Named(Enter)) => Default [SymbolsSelect(Symbols => _x)], K(Key::Named(Escape)) => Default, }, Symbols(Rq::<Symbols, Option<SymbolsList>, (), AQErr> => _rq) => { @@ -158,8 +158,8 @@ Procure((t, InputRequest::RenameSymbol)) => K(Key::Named(Enter)) => Default [Ren Procure((t, a)) => K(k) => Procure((handle(k, t), a)), Procure((t, a)) => C(_) => Procure((t, a)), RequestBoolean(t) => { - K(Key::Character("n")) => Default [Boolean((BoolRequest, bool) => (t, true))], - K(Key::Character("y")) => Default [Boolean((t, false))], + K(Key::Character("y")) => Default [Boolean((BoolRequest, bool) => (t, true))], + K(Key::Character("n")) => Default [Boolean((t, false))], K(Key::Named(Escape)) => Default [Boolean((t, false))], K(_) => RequestBoolean(t), C(_) => _, @@ -1,661 +1,24 @@ -use std::backtrace::Backtrace; -use std::collections::HashMap; -use std::fmt::{Debug, Display}; -use std::marker::PhantomData; -use std::mem::forget; -use std::path::{Path, PathBuf}; +use std::fmt::Debug; use std::sync::Arc; use std::sync::atomic::AtomicI32; -use std::sync::atomic::Ordering::Relaxed; use std::task::Poll; use std::thread::spawn; use std::time::Instant; -use Default::default; -use anyhow::bail; -use crossbeam::channel::{ - Receiver, RecvError, SendError, Sender, unbounded, -}; -use log::{debug, error, trace}; -use lsp_server::{ - ErrorCode, Message, Notification as N, Request as LRq, Response as Re, - ResponseError, -}; +use crossbeam::channel::{Receiver, Sender, unbounded}; +use lsp_server::Message; use lsp_types::notification::*; use lsp_types::request::*; use lsp_types::*; -use rust_analyzer::lsp::ext::*; -use serde::{Deserialize, Serialize}; -use serde_json::json; use tokio::sync::oneshot; -use tokio::task; -use tokio_util::task::AbortOnDropHandle; use winit::window::Window; +mod client; +mod communication; +pub use client::*; +mod init_opts; +mod rq; +pub use 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<Message>, - pub id: AtomicI32, - pub initialized: Option<InitializeResult>, - // pub pending: HashMap<i32, oneshot::Sender<Re>>, - pub send_to: Sender<(i32, oneshot::Sender<Re>, BehaviourAfter)>, - pub progress: &'static papaya::HashMap< - ProgressToken, - Option<(WorkDoneProgress, WorkDoneProgressBegin)>, - >, - pub diagnostics: &'static papaya::HashMap<Url, Vec<Diagnostic>>, - #[allow(dead_code)] - // TODO: handle notifications from the server - pub not_rx: Receiver<N>, - pub req_rx: Receiver<LRq>, -} - -impl Drop for Client { - fn drop(&mut self) { - panic!("please dont") - } -} -#[derive(Serialize, Deserialize)] -pub enum RequestError<X> { - Rx(PhantomData<X>), - Failure(Re, #[serde(skip)] Option<Backtrace>), - Cancelled(Re, DiagnosticServerCancellationData), - Send(Message), -} -pub type AQErr = RequestError<LSPError>; -impl Request for LSPError { - type Params = (); - type Result = (); - const METHOD: &'static str = "<unknown method>"; -} -#[derive(Debug)] -pub struct LSPError {} -pub trait Anonymize<T> { - fn anonymize(self) -> Result<T, RequestError<LSPError>>; -} -impl<T, E> Anonymize<T> for Result<T, RequestError<E>> { - fn anonymize(self) -> Result<T, RequestError<LSPError>> { - self.map_err(|e| match e { - RequestError::Send(x) => RequestError::Send(x), - RequestError::Rx(_) => RequestError::Rx(PhantomData), - RequestError::Failure(r, b) => RequestError::Failure(r, b), - RequestError::Cancelled(r, d) => RequestError::Cancelled(r, d), - }) - } -} - -// impl<X> Debug for RequestError<X> {} -impl<X> From<oneshot::error::RecvError> for RequestError<X> { - fn from(_: oneshot::error::RecvError) -> Self { - Self::Rx(PhantomData) - } -} -impl<X: Request + Debug> std::error::Error for RequestError<X> { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None - } -} -impl<X> From<SendError<Message>> for RequestError<X> { - fn from(x: SendError<Message>) -> Self { - Self::Send(x.into_inner()) - } -} -impl<X: Request> Display for RequestError<X> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Send(x) => - write!(f, "{} failed; couldnt send {x:?}", X::METHOD), - Self::Rx(_) => - write!(f, "{} failed; couldnt get from thingy", X::METHOD), - Self::Failure(x, bt) => write!( - f, - "{} failed; returned badge :( {x:?} ({bt:?})", - X::METHOD - ), - Self::Cancelled(x, y) => - write!(f, "server cancelled {}. {x:?} {y:?}", X::METHOD), - } - } -} -impl Client { - pub fn notify<X: Notification>( - &self, - y: &X::Params, - ) -> Result<(), SendError<Message>> { - self.tx.send(Message::Notification(N { - method: X::METHOD.into(), - params: serde_json::to_value(y).unwrap(), - })) - } - pub fn cancel(&self, rid: i32) { - _ = self.notify::<Cancel>(&CancelParams { id: rid.into() }); - } - pub fn request_immediate<'me, X: Request>( - &'me self, - y: &X::Params, - ) -> Result<X::Result, RequestError<X>> { - self.runtime.block_on(self.request_::<X, { Nil }>(y)?.0) - } - - pub fn request<'me, X: Request>( - &'me self, - y: &X::Params, - ) -> Result< - ( - impl Future<Output = Result<X::Result, RequestError<X>>> - + use<'me, X>, - i32, - ), - SendError<Message>, - > { - self.request_::<X, { Redraw }>(y) - } - #[must_use] - fn request_<'me, X: Request, const THEN: BehaviourAfter>( - &'me self, - y: &X::Params, - ) -> Result< - ( - impl Future<Output = Result<X::Result, RequestError<X>>> - + use<'me, X, THEN>, - i32, - ), - SendError<Message>, - > { - let id = self.id.fetch_add(1, std::sync::atomic::Ordering::AcqRel); - self.tx.send(Message::Request(LRq { - id: id.into(), - method: X::METHOD.into(), - params: serde_json::to_value(y).unwrap(), - }))?; - let (tx, rx) = oneshot::channel(); - if self.initialized.is_some() { - debug!("sent request {} ({id})'s handler", X::METHOD); - self.send_to.send((id, tx, THEN)).expect("oughtnt really fail"); - } - Ok(( - async move { - let g = scopeguard::guard((), |()| { - self.cancel(id); - }); - - let mut x = rx.await?; - forget(g); - if let Some(ResponseError { code, ref mut data, .. }) = - x.error - { - if code == ErrorCode::ServerCancelled as i32 { - let e = serde_json::from_value( - data.take().unwrap_or_default(), - ); - Err(RequestError::Cancelled(x, e.expect("lsp??"))) - } else { - Err(RequestError::Failure( - x, - Some(Backtrace::capture()), - )) - } - } else { - Ok(serde_json::from_value::<X::Result>( - x.result.clone().unwrap_or_default(), - ) - .unwrap_or_else(|_| { - panic!( - "lsp failure for {x:?}\ndidnt follow spec \ - for {}\npossibly spec issue", - X::METHOD - ) - })) - } - }, - id, - )) - } - pub fn open( - &self, - f: &Path, - text: String, - ) -> Result<(), SendError<Message>> { - self.notify::<DidOpenTextDocument>(&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<Message>> { - self.notify::<DidCloseTextDocument>(&DidCloseTextDocumentParams { - text_document: f.tid(), - }) - } - pub fn edit( - &self, - f: &Path, - text: String, - ) -> Result<(), SendError<Message>> { - static V: AtomicI32 = AtomicI32::new(0); - self.notify::<lsp_types::notification::DidChangeTextDocument>( - &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< - impl Future< - Output = Result< - CompletionItem, - RequestError<ResolveCompletionItem>, - >, - >, - SendError<Message>, - > { - self.request::<ResolveCompletionItem>(&x).map(|x| x.0) - } - - pub fn request_complete<'me>( - &'me self, - f: &Path, - (x, y): (usize, usize), - c: CompletionContext, - ) -> impl Future< - Output = Result< - Option<CompletionResponse>, - RequestError<Completion>, - >, - > + use<'me> { - let (rx, _) = self - .request_::<Completion, { Redraw }>(&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<SignatureHelp>, - RequestError<SignatureHelpRequest>, - >, - > + use<'me> { - self.request_::<SignatureHelpRequest, { Redraw }>(&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 = anyhow::Result<()>> { - let r = self - .request::<lsp_request!("workspace/diagnostic")>(&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<String>, - ) -> impl Future<Output = anyhow::Result<Option<String>>> { - 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::<lsp_request!("textDocument/diagnostic")>(&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::<lsp_request!("textDocument/diagnostic")>(&p,).unwrap().0.await? - }, - Err(e) => return Err(e.into()), - }; - // 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) - } - _ => bail!("fuck that"), - } - } - } - pub fn document_highlights<'me>( - &'me self, - f: &Path, - cursor: Position, - ) -> impl Future< - Output = Result< - Vec<DocumentHighlight>, - RequestError<DocumentHighlightRequest>, - >, - > + 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_::<lsp_request!("textDocument/documentHighlight"), {Redraw}>(&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<DocumentSymbolResponse>, - RequestError<lsp_request!("textDocument/documentSymbol")>, - >, - > { - self.request_::<lsp_request!("textDocument/documentSymbol"), { Redraw }>( - &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<WorkspaceSymbolResponse>, - RequestError<lsp_request!("workspace/symbol")>, - >, - > { - self.request_::<lsp_request!("workspace/symbol"), {Redraw}>( - &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<Position>, - ) -> Result< - Vec<Option<(Position, Position)>>, - RequestError<MatchingBrace>, - > { - self.request_immediate::<MatchingBrace>(&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<InlayHint>, - RequestError<lsp_request!("textDocument/inlayHint")>, - >, - > + use<> { - self.request_::<lsp_request!("textDocument/inlayHint"), { Redraw }>(&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::<lsp_request!("inlayHint/resolve")>(&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<Option<Vec<TextEdit>>, RequestError<Formatting>>, - > { - self.request::<lsp_request!("textDocument/formatting")>( - &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<SemanticTokensFullRequest>, - >, - f: &Path, - ) -> anyhow::Result<()> { - debug!("requested semantic tokens"); - - let Some(b"rs") = f.extension().map(|x| x.as_encoded_bytes()) - else { - return Ok(()); - }; - let (rx, _) = self.request_::<SemanticTokensFullRequest, {Redraw}>( - &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) { - ceach!(t.cursor, |c| { - let r = self.request_immediate::<OnEnter>(&TextDocumentPositionParams { - text_document: f.tid(), - position: t.to_l_position(*c).unwrap(), - }) - .unwrap(); - match r { - None => t.enter(), - Some(mut r) => { - r.sort_tedits(); - for f in r { - t.apply_snippet_tedit(&f).unwrap(); - } - } - } - }); - } - pub fn runnables( - &'static self, - t: &Path, - c: Option<Position>, - ) -> Result< - impl Future<Output = Result<Vec<Runnable>, RequestError<Runnables>>> - + use<>, - SendError<Message>, - > { - self.request::<Runnables>(&RunnablesParams { - text_document: t.tid(), - position: c, - }) - .map(|(x, _)| x) - } -} pub fn run( (tx, rx): (Sender<Message>, Receiver<Message>), workspace: WorkspaceFolder, @@ -683,269 +46,7 @@ pub fn run( req_rx: _req_rx, not_rx, }; - _ = c - .request::<Initialize>(&InitializeParams { - process_id: Some(std::process::id()), - - capabilities: ClientCapabilities { - window: Some(WindowClientCapabilities { - work_done_progress: Some(true), - ..default() - }), - workspace: Some(WorkspaceClientCapabilities { - symbol: Some(WorkspaceSymbolClientCapabilities { - symbol_kind: Some(SymbolKindCapability { value_set: Some(SymbolKind::ALL.to_vec()) }), - tag_support: Some(TagSupport { value_set: SymbolTag::ALL.to_vec() }), - resolve_support: Some(WorkspaceSymbolResolveSupportCapability { - properties: vec!["location".into()], - }), - ..default() - }), - diagnostic: Some(DiagnosticWorkspaceClientCapabilities { refresh_support: Some(true) }), - inlay_hint: Some(InlayHintWorkspaceClientCapabilities { refresh_support: Some(true) }), - workspace_edit: Some( - WorkspaceEditClientCapabilities { document_changes: Some(true), - resource_operations: Some(vec![ResourceOperationKind::Create, ResourceOperationKind::Rename, ResourceOperationKind::Delete]), - failure_handling: Some(FailureHandlingKind::Abort), normalizes_line_endings: Some(false), - change_annotation_support: Some(ChangeAnnotationWorkspaceEditClientCapabilities { groups_on_label: Some(false) }) }, - ), - ..default() - }), - text_document: Some(TextDocumentClientCapabilities { - on_type_formatting: Some(DocumentOnTypeFormattingClientCapabilities { - dynamic_registration: Some(false), - }), - document_highlight: Some(default()), - formatting: Some(DynamicRegistrationClientCapabilities { dynamic_registration: Some(false) }), - inlay_hint: Some(InlayHintClientCapabilities { dynamic_registration: None, resolve_support: Some(InlayHintResolveClientCapabilities { - properties: vec!["textEdits".into(), "tooltip".into(), "label.tooltip".into(), "label.command".into()], }) - }), - document_symbol: Some(DocumentSymbolClientCapabilities { - tag_support: Some(TagSupport { value_set: SymbolTag::ALL.to_vec() }), - symbol_kind: Some(SymbolKindCapability { value_set: Some(SymbolKind::ALL.to_vec()) }), - hierarchical_document_symbol_support: Some(true), - ..default() - }), - definition: Some(GotoCapability { link_support: Some(true), ..default() }), - code_action: Some( - CodeActionClientCapabilities { - data_support: Some(true), - resolve_support: Some(CodeActionCapabilityResolveSupport { properties: vec!["edit".to_string()] }), - code_action_literal_support: Some(CodeActionLiteralSupport { code_action_kind: CodeActionKindLiteralSupport { value_set: [ - "", "Empty", "QuickFix", "Refactor", - "RefactorExtract", "RefactorInline", "RefactorRewrite", "Source", - "SourceOrganizeImports", "quickfix", "refactor", "refactor.extract", - "refactor.inline", "refactor.rewrite", "source", "source.organizeImports" - ].map(String::from).into()} }), - ..default() - } - ), - rename: Some(RenameClientCapabilities { prepare_support: Some(true), - prepare_support_default_behavior: Some(PrepareSupportDefaultBehavior::IDENTIFIER), honors_change_annotations: Some(false), - ..default() }), - hover: Some(HoverClientCapabilities { - dynamic_registration: None, - content_format: Some(vec![MarkupKind::PlainText, MarkupKind::Markdown]), - }), - diagnostic: Some(DiagnosticClientCapabilities { dynamic_registration: None, related_document_support: Some(true) }), - publish_diagnostics: Some(PublishDiagnosticsClientCapabilities { - related_information: Some(true), - code_description_support: Some(true), - data_support: Some(true), - ..default() - }), - signature_help: Some(SignatureHelpClientCapabilities { - dynamic_registration: None, signature_information: Some(SignatureInformationSettings { - documentation_format: Some(vec![ - MarkupKind::Markdown, - MarkupKind::PlainText, - ]), - parameter_information: Some(ParameterInformationSettings { - label_offset_support: Some(true) }), - active_parameter_support: Some(true), - }), context_support: Some(false) }), - completion: Some(CompletionClientCapabilities { - dynamic_registration: Some(false), - completion_item: Some(CompletionItemCapability { - snippet_support: Some(true), - commit_characters_support: Some(true), - documentation_format: Some(vec![ - MarkupKind::Markdown, - MarkupKind::PlainText, - ]), - deprecated_support: None, - preselect_support: None, - tag_support: Some(TagSupport { - value_set: vec![CompletionItemTag::DEPRECATED], - }), - - resolve_support: Some(CompletionItemCapabilityResolveSupport { - properties: vec![ - "additionalTextEdits".into(), - "documentation".into(), - ], - }), - insert_replace_support: Some(false), - insert_text_mode_support: Some(InsertTextModeSupport { - value_set: vec![InsertTextMode::AS_IS], - }), - label_details_support: Some(true), - ..default() - }), - completion_item_kind: Some(CompletionItemKindCapability { - value_set: Some(vec![ - CompletionItemKind::TEXT, - CompletionItemKind::METHOD, - CompletionItemKind::FUNCTION, - CompletionItemKind::CONSTRUCTOR, - CompletionItemKind::FIELD, - CompletionItemKind::VARIABLE, - CompletionItemKind::CLASS, - CompletionItemKind::INTERFACE, - CompletionItemKind::MODULE, - CompletionItemKind::PROPERTY, - CompletionItemKind::UNIT, - CompletionItemKind::VALUE, - CompletionItemKind::ENUM, - CompletionItemKind::KEYWORD, - CompletionItemKind::SNIPPET, - CompletionItemKind::COLOR, - CompletionItemKind::FILE, - CompletionItemKind::REFERENCE, - CompletionItemKind::FOLDER, - CompletionItemKind::ENUM_MEMBER, - CompletionItemKind::CONSTANT, - CompletionItemKind::STRUCT, - CompletionItemKind::EVENT, - CompletionItemKind::OPERATOR, - CompletionItemKind::TYPE_PARAMETER, - ]), - // value_set: Some(vec![CompletionItemKind::]), - }), - context_support: None, - insert_text_mode: Some(InsertTextMode::AS_IS), - completion_list: Some(CompletionListCapability { item_defaults: None }), - }), - semantic_tokens: Some(SemanticTokensClientCapabilities { - dynamic_registration: Some(false), - requests: SemanticTokensClientCapabilitiesRequests { - range: Some(true), - full: Some(lsp_types::SemanticTokensFullOptions::Bool(true)), - }, - token_modifiers: [ - "associated", - "attribute", - "callable", - "constant", - "consuming", - "controlFlow", - "crateRoot", - "injected", - "intraDocLink", - "library", - "macro", - "mutable", - "procMacro", - "public", - "reference", - "trait", - "unsafe", - ] - .map(|x| x.into()) - .to_vec(), - overlapping_token_support: Some(true), - multiline_token_support: Some(true), - server_cancel_support: Some(false), - augments_syntax_tokens: Some(false), - ..default() - }), - ..default() - }), - - general: Some(GeneralClientCapabilities { - markdown: Some(MarkdownClientCapabilities { - version: Some("1.0.0".into()), - parser: "markdown".into(), - allowed_tags: Some(vec![]), - }), - position_encodings: Some(vec![PositionEncodingKind::UTF8]), - ..default() - }), - experimental: Some(json! {{ - "matchingBrace": true, - "snippetTextEdit": true, - "colorDiagnosticOutput": true, - "codeActionGroup": true, - "serverStatusNotification": true, - "hoverActions": true, - "workspaceSymbolScopeKindFiltering": true, - "onEnter": true, - "localDocs": true, - }}), - ..default() - }, - client_info: Some(ClientInfo { - name: "gracilaria".into(), - version: Some(env!("CARGO_PKG_VERSION").into()), - }), - initialization_options: Some(json! {{ - "cargo": { - "buildScripts": { "enable": true } - }, - "procMacro": { - "enable": true, - "attributes": { "enable": true } - }, - "hover": { - "documentation": { - "keywords": { "enable": false }, - }, - }, - "inlayHints": { - "closureReturnTypeHints": { "enable": "with_block" }, - "closingBraceHints": { "minLines": 5 }, - "closureStyle": "rust_analyzer", - "genericParameterHints": { "type": { "enable": true } }, - "rangeExclusiveHints": { "enable": true }, - "closureCaptureHints": { "enable": true }, - }, - "typing": { "triggerChars": ".=<>{(+" }, - "assist": { "preferSelf": true }, - "checkOnSave": true, - "diagnostics": { "enable": true }, - "semanticHighlighting": { - "punctuation": { - "separate": { - "macroBang": true - }, - "specialization": { "enable": true }, - "enable": true - } - }, - "workspace": { - "symbol": { - "search": { "limit": 1024 } - } - }, - "showUnlinkedFileNotification": false, - "completion": { - "fullFunctionSignatures": { "enable": true, }, - "autoIter": { "enable": false, }, - "autoImport": { "enable": true, }, - "termSearch": { "enable": true, }, - "autoself": { "enable": true, }, - "privateEditable": { "enable": true }, - }, - "imports": { - "granularity": "group", - }, - }}), - trace: None, - workspace_folders: Some(vec![workspace]), - - ..default() - }) - .unwrap(); + _ = c.request::<Initialize>(&init_opts::get(workspace)).unwrap(); let x = serde_json::from_value::<InitializeResult>( rx.recv().unwrap().response().unwrap().result.unwrap(), ) @@ -966,104 +67,11 @@ pub fn run( let progress = c.progress; let d = c.diagnostics; log::info!("lsp took {:?} to initialize", now.elapsed()); + let h = spawn(move || { - let mut map = HashMap::new(); - let w = window_rx.blocking_recv().unwrap(); - loop { - crossbeam::select! { - recv(req_rx) -> x => match x { - Ok((x, y, and)) => { - debug!("received request {x}"); - assert!(map.insert(x, (y, Instant::now(), and)).is_none()); - } - Err(RecvError) => return, - }, - recv(rx) -> x => match x { - Ok(Message::Request(rq @ LRq { method: "window/workDoneProgress/create", .. })) => { - match rq.load::<WorkDoneProgressCreate>() { - Ok((_, x)) => { - let g = progress.guard(); - progress.insert(x.token, None, &g); - }, - Err(lsp_server::ExtractError::MethodMismatch(..)) => {}, - e => { - error!("{e:?}"); - } - }; - } - Ok(Message::Request(x)) => { - if let Err(e) = _req_tx.send(x) { - let m = e.to_string(); - error!("couldnt receive request {m}: {:?}", e.into_inner()); - } - } - Ok(Message::Response(x)) => { - if let Some(e) = &x.error { - if e.code == ErrorCode::RequestCanceled as i32 {} - else if e.code == ErrorCode::ServerCancelled as i32 { - if let Some((s, _, t)) = map.remove(&x.id.i32()) { - log::info!("request {} cancelled", x.id); - _ = s.send(x); - if t == Redraw { w.request_redraw() } - } - } else { - if let Some((s, _, t)) = map.remove(&x.id.i32()) { - _ = s.send(x.clone()); - if t == Redraw { w.request_redraw() } - trace!("received error from lsp for response {x:?}"); - } else { - error!("received error from lsp for response {x:?}"); - } - } - } - else if let Some((s, took, t)) = map.remove(&x.id.i32()) { - log::info!("request {} took {:?}", x.id, took.elapsed()); - match s.send(x) { - Ok(()) => {} - Err(e) => { - error!( - "unable to respond to {e:?}", - ); - } - } - // w.request_redraw(); - if t == Redraw { w.request_redraw() } - } else { - error!("request {x:?} was dropped.") - } - } - Ok(Message::Notification(rq @ N { method: "textDocument/publishDiagnostics", .. })) => { - debug!("got diagnostics"); - match rq.load::<PublishDiagnostics>() { - Ok(x) => { - d.insert(x.uri, x.diagnostics, &d.guard()); - w.request_redraw(); - }, - e => error!("{e:?}"), - } - }, - Ok(Message::Notification(x @ N { method: "$/progress", .. })) => { - let ProgressParams {token,value:ProgressParamsValue::WorkDone(x) } = x.load::<Progress>().unwrap(); - match x.clone() { - WorkDoneProgress::Begin(y) => { - progress.update(token, move |_| Some((x.clone(), y.clone())), &progress.guard()); - }, - WorkDoneProgress::Report(_) | WorkDoneProgress::End(_) => { - progress.update(token, move |v| Some((x.clone(), v.clone().expect("evil lsp").1)), &progress.guard()); - } - } - w.request_redraw(); - } - Ok(Message::Notification(notification)) => { - debug!("rx {notification:?}"); - not_tx - .send(notification) - .expect("why library drop this??? no drop!!"); - } - Err(RecvError) => return, - } - } - } + communication::handler( + window_rx, progress, _req_tx, d, not_tx, rx, req_rx, + ) }); (c, h, window_tx) } @@ -1073,7 +81,6 @@ pub enum BehaviourAfter { // Poll, ? how impl. Nil, } -pub use BehaviourAfter::*; // trait RecvEepy<T>: Sized { // fn recv_eepy(self) -> Result<T, RecvError> { // self.recv_sleepy(100) @@ -1135,107 +142,3 @@ impl<T, U, F: FnMut(T) -> U, Fu: Future<Output = T>> Map_<T, U, F> for Fu { Map(self, f) } } - -impl<R: Request> Debug for RequestError<R> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self, f) - } -} - -fn none<T>() -> Option<T> { - None -} -impl<T: Clone, R, D, E> Clone for Rq<T, R, D, E> { - fn clone(&self) -> Self { - Self { result: self.result.clone(), request: None } - } -} -#[derive(serde_derive::Serialize, serde_derive::Deserialize)] -pub struct Rq<T, R, D = (), E = RequestError<R>> { - #[serde(skip_serializing_if = "Option::is_none", default = "none")] - pub result: Option<T>, - #[serde(skip, default = "none")] - pub request: Option<(AbortOnDropHandle<Result<R, E>>, D)>, -} -impl<T: Debug, R, D: Debug, E> Debug for Rq<T, R, D, E> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct(&format!("Rq<{}>", std::any::type_name::<R>())) - .field("result", &self.result) - .field("request", &self.request) - .finish() - } -} - -pub type RqS<T, R: Request, D = ()> = Rq<T, R::Result, D, RequestError<R>>; -impl<T, R, D, E> Default for Rq<T, R, D, E> { - fn default() -> Self { - Self { result: None, request: None } - } -} -impl<T, R, E> Rq<T, R, (), E> { - pub fn new(f: task::JoinHandle<Result<R, E>>) -> Self { - Self { - request: Some((AbortOnDropHandle::new(f), ())), - result: None, - } - } - pub fn request(&mut self, f: task::JoinHandle<Result<R, E>>) { - self.request = Some((AbortOnDropHandle::new(f), ())); - } -} -impl<T, R, D, E> Rq<T, R, D, E> { - pub fn running(&self) -> bool { - matches!( - self, - Self { result: Some(_), .. } | Self { request: Some(_), .. }, - ) - } - pub fn poll( - &mut self, - f: impl FnOnce(Result<R, E>, (D, Option<T>)) -> Option<T>, - runtime: &tokio::runtime::Runtime, - ) -> bool { - if self.request.as_mut().is_some_and(|(x, _)| x.is_finished()) - && let Some((task, d)) = self.request.take() - { - let x = match runtime.block_on(task) { - Ok(x) => x, - Err(e) => { - log::error!( - "unexpected join error from request poll: {e}" - ); - return false; - } - }; - self.result = f(x, (d, self.result.take())); - true - } else { false } - } - - pub fn poll_r( - &mut self, - f: impl FnOnce(Result<R, E>, (D, Option<T>)) -> Option<T>, - runtime: &tokio::runtime::Runtime, - _w: Option<&Arc<dyn Window>>, - ) -> bool { - self.poll( - |x, y| { - f(x, y).inspect(|_| { - // w.map(|x| x.request_redraw()); - }) - }, - runtime, - ) - } -} - -pub trait PathURI { - fn tid(&self) -> TextDocumentIdentifier; -} -impl PathURI for Path { - fn tid(&self) -> TextDocumentIdentifier { - TextDocumentIdentifier { - uri: Url::from_file_path(self).expect("ok"), - } - } -} diff --git a/src/lsp/client.rs b/src/lsp/client.rs new file mode 100644 index 0000000..b6b5bea --- /dev/null +++ b/src/lsp/client.rs @@ -0,0 +1,505 @@ +use std::fmt::Debug; +use std::path::{Path, PathBuf}; +use std::sync::atomic::AtomicI32; +use std::sync::atomic::Ordering::Relaxed; + +use Default::default; +use anyhow::bail; +use crossbeam::channel::{Receiver, SendError, Sender}; +use futures::FutureExt; +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 rust_analyzer::lsp::ext::*; +use tokio::sync::oneshot; + +use crate::lsp::BehaviourAfter::{self, *}; +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<Message>, + pub id: AtomicI32, + pub initialized: Option<InitializeResult>, + // pub pending: HashMap<i32, oneshot::Sender<Re>>, + pub send_to: Sender<(i32, oneshot::Sender<Re>, BehaviourAfter)>, + pub progress: &'static papaya::HashMap< + ProgressToken, + Option<(WorkDoneProgress, WorkDoneProgressBegin)>, + >, + pub diagnostics: &'static papaya::HashMap<Url, Vec<Diagnostic>>, + #[allow(dead_code)] + // TODO: handle notifications from the server + pub not_rx: Receiver<N>, + pub req_rx: Receiver<LRq>, +} + +impl Drop for Client { + fn drop(&mut self) { + panic!("please dont"); + } +} + +impl Client { + pub fn open( + &self, + f: &Path, + text: String, + ) -> Result<(), SendError<Message>> { + self.notify::<DidOpenTextDocument>(&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<Message>> { + self.notify::<DidCloseTextDocument>(&DidCloseTextDocumentParams { + text_document: f.tid(), + }) + } + pub fn edit( + &self, + f: &Path, + text: String, + ) -> Result<(), SendError<Message>> { + static V: AtomicI32 = AtomicI32::new(0); + self.notify::<lsp_types::notification::DidChangeTextDocument>( + &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<CompletionItem, RequestError<ResolveCompletionItem>> { + self.request_immediate::<ResolveCompletionItem>(&x) + } + + pub fn request_complete<'me>( + &'me self, + f: &Path, + (x, y): (usize, usize), + c: CompletionContext, + ) -> impl Future< + Output = Result< + Option<CompletionResponse>, + RequestError<Completion>, + >, + > + use<'me> { + let (rx, _) = self + .request_::<Completion, { Redraw }>(&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<SignatureHelp>, + RequestError<SignatureHelpRequest>, + >, + > + use<'me> { + self.request_::<SignatureHelpRequest, { Redraw }>( + &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 = anyhow::Result<()>> { + let r = self + .request::<lsp_request!("workspace/diagnostic")>(&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<String>, + ) -> impl Future<Output = anyhow::Result<Option<String>>> { + 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::<lsp_request!("textDocument/diagnostic")>(&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::<lsp_request!("textDocument/diagnostic")>(&p,).unwrap().0.await? + }, + Err(e) => return Err(e.into()), + }; + // 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) + } + _ => bail!("fuck that"), + } + } + } + pub fn document_highlights<'me>( + &'me self, + f: &Path, + cursor: Position, + ) -> impl Future< + Output = Result< + Vec<DocumentHighlight>, + RequestError<DocumentHighlightRequest>, + >, + > + 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_::<lsp_request!("textDocument/documentHighlight"), {Redraw}>(&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<DocumentSymbolResponse>, + RequestError<lsp_request!("textDocument/documentSymbol")>, + >, + > { + self.request_::<lsp_request!("textDocument/documentSymbol"), { Redraw }>( + &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<WorkspaceSymbolResponse>, + RequestError<lsp_request!("workspace/symbol")>, + >, + > { + self.request_::<lsp_request!("workspace/symbol"), { Redraw }>( + &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<Position>, + ) -> Result< + Vec<Option<(Position, Position)>>, + RequestError<MatchingBrace>, + > { + self.request_immediate::<MatchingBrace>(&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<InlayHint>, + RequestError<lsp_request!("textDocument/inlayHint")>, + >, + > + use<> { + self.request_::<lsp_request!("textDocument/inlayHint"), { Redraw }>(&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::<lsp_request!("inlayHint/resolve")>(&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<Option<Vec<TextEdit>>, RequestError<Formatting>>, + > { + self.request::<lsp_request!("textDocument/formatting")>( + &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<SemanticTokensFullRequest>, + >, + f: &Path, + ) -> anyhow::Result<()> { + debug!("requested semantic tokens"); + + let Some(b"rs") = f.extension().map(|x| x.as_encoded_bytes()) + else { + return Ok(()); + }; + let (rx, _) = self + .request_::<SemanticTokensFullRequest, { Redraw }>( + &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) { + ceach!(t.cursor, |c| { + let r = self + .request_immediate::<OnEnter>( + &TextDocumentPositionParams { + text_document: f.tid(), + position: t.to_l_position(*c).unwrap(), + }, + ) + .unwrap(); + match r { + None => t.enter(), + Some(mut r) => { + r.sort_tedits(); + for f in r { + t.apply_snippet_tedit(&f).unwrap(); + } + } + } + }); + } + pub fn runnables( + &'static self, + t: &Path, + c: Option<Position>, + ) -> Result< + impl Future<Output = Result<Vec<Runnable>, RequestError<Runnables>>> + + use<>, + SendError<Message>, + > { + self.request::<Runnables>(&RunnablesParams { + text_document: t.tid(), + position: c, + }) + .map(|(x, _)| x) + } +} + +pub trait PathURI { + fn tid(&self) -> TextDocumentIdentifier; +} +impl PathURI for Path { + fn tid(&self) -> TextDocumentIdentifier { + TextDocumentIdentifier { + uri: Url::from_file_path(self).expect("ok"), + } + } +} diff --git a/src/lsp/communication.rs b/src/lsp/communication.rs new file mode 100644 index 0000000..8899f0f --- /dev/null +++ b/src/lsp/communication.rs @@ -0,0 +1,227 @@ +use std::backtrace::Backtrace; +use std::collections::HashMap; +use std::mem::forget; +use std::sync::Arc; +use std::time::Instant; + +use crossbeam::channel::{Receiver, RecvError, SendError, Sender}; +use log::{debug, error, trace}; +use lsp_server::{ + ErrorCode, Message, Notification as N, Request as LRq, Response as Re, + ResponseError, +}; +use lsp_types::notification::*; +use lsp_types::request::*; +use lsp_types::*; +use tokio::sync::oneshot; +use winit::window::Window; + +use crate::lsp::BehaviourAfter::{self, *}; +use crate::lsp::RequestError; +pub fn handler( + window_rx: oneshot::Receiver<Arc<dyn Window + 'static>>, + progress: &papaya::HashMap< + NumberOrString, + Option<(WorkDoneProgress, WorkDoneProgressBegin)>, + >, + _req_tx: Sender<LRq>, + d: &papaya::HashMap<Url, Vec<Diagnostic>>, + not_tx: Sender<N>, + rx: Receiver<Message>, + req_rx: Receiver<(i32, oneshot::Sender<Re>, BehaviourAfter)>, +) { + let mut map = HashMap::new(); + let w = window_rx.blocking_recv().unwrap(); + loop { + crossbeam::select! { + recv(req_rx) -> x => match x { + Ok((x, y, and)) => { + debug!("received request {x}"); + assert!(map.insert(x, (y, Instant::now(), and)).is_none()); + } + Err(RecvError) => return, + }, + recv(rx) -> x => match x { + Ok(Message::Request(rq @ LRq { method: "window/workDoneProgress/create", .. })) => { + match rq.load::<WorkDoneProgressCreate>() { + Ok((_, x)) => { + let g = progress.guard(); + progress.insert(x.token, None, &g); + }, + Err(lsp_server::ExtractError::MethodMismatch(..)) => {}, + e => { + error!("{e:?}"); + } + }; + } + Ok(Message::Request(x)) => { + if let Err(e) = _req_tx.send(x) { + let m = e.to_string(); + error!("couldnt receive request {m}: {:?}", e.into_inner()); + } + } + Ok(Message::Response(x)) => { + if let Some(e) = &x.error { + if e.code == ErrorCode::RequestCanceled as i32 {} + else if e.code == ErrorCode::ServerCancelled as i32 { + if let Some((s, _, t)) = map.remove(&x.id.i32()) { + log::info!("request {} cancelled", x.id); + _ = s.send(x); + if t == Redraw { w.request_redraw() } + } + } else { + if let Some((s, _, t)) = map.remove(&x.id.i32()) { + _ = s.send(x.clone()); + if t == Redraw { w.request_redraw() } + trace!("received error from lsp for response {x:?}"); + } else { + error!("received error from lsp for response {x:?}"); + } + } + } + else if let Some((s, took, t)) = map.remove(&x.id.i32()) { + log::info!("request {} took {:?}", x.id, took.elapsed()); + match s.send(x) { + Ok(()) => {} + Err(e) => { + error!( + "unable to respond to {e:?}", + ); + } + } + // w.request_redraw(); + if t == Redraw { w.request_redraw() } + } else { + error!("request {x:?} was dropped.") + } + } + Ok(Message::Notification(rq @ N { method: "textDocument/publishDiagnostics", .. })) => { + debug!("got diagnostics"); + match rq.load::<PublishDiagnostics>() { + Ok(x) => { + d.insert(x.uri, x.diagnostics, &d.guard()); + w.request_redraw(); + }, + e => error!("{e:?}"), + } + }, + Ok(Message::Notification(x @ N { method: "$/progress", .. })) => { + let ProgressParams {token,value:ProgressParamsValue::WorkDone(x) } = x.load::<Progress>().unwrap(); + match x.clone() { + WorkDoneProgress::Begin(y) => { + progress.update(token, move |_| Some((x.clone(), y.clone())), &progress.guard()); + }, + WorkDoneProgress::Report(_) | WorkDoneProgress::End(_) => { + progress.update(token, move |v| Some((x.clone(), v.clone().expect("evil lsp").1)), &progress.guard()); + } + } + w.request_redraw(); + } + Ok(Message::Notification(notification)) => { + debug!("rx {notification:?}"); + not_tx + .send(notification) + .expect("why library drop this??? no drop!!"); + } + Err(RecvError) => return, + } + } + } +} +impl super::Client { + pub fn notify<X: Notification>( + &self, + y: &X::Params, + ) -> Result<(), SendError<Message>> { + self.tx.send(Message::Notification(N { + method: X::METHOD.into(), + params: serde_json::to_value(y).unwrap(), + })) + } + pub fn cancel(&self, rid: i32) { + _ = self.notify::<Cancel>(&CancelParams { id: rid.into() }); + } + pub fn request_immediate<'me, X: Request>( + &'me self, + y: &X::Params, + ) -> Result<X::Result, RequestError<X>> { + self.runtime.block_on(self.request_::<X, { Nil }>(y)?.0) + } + + pub fn request<'me, X: Request>( + &'me self, + y: &X::Params, + ) -> Result< + ( + impl Future<Output = Result<X::Result, RequestError<X>>> + + use<'me, X>, + i32, + ), + SendError<Message>, + > { + self.request_::<X, { Redraw }>(y) + } + #[must_use] + pub(super) fn request_<'me, X: Request, const THEN: BehaviourAfter>( + &'me self, + y: &X::Params, + ) -> Result< + ( + impl Future<Output = Result<X::Result, RequestError<X>>> + + use<'me, X, THEN>, + i32, + ), + SendError<Message>, + > { + let id = self.id.fetch_add(1, std::sync::atomic::Ordering::AcqRel); + self.tx.send(Message::Request(LRq { + id: id.into(), + method: X::METHOD.into(), + params: serde_json::to_value(y).unwrap(), + }))?; + let (tx, rx) = oneshot::channel(); + if self.initialized.is_some() { + debug!("sent request {} ({id})'s handler", X::METHOD); + self.send_to + .send((id, tx, THEN)) + .expect("oughtnt really fail"); + } + Ok(( + async move { + let g = scopeguard::guard((), |()| { + self.cancel(id); + }); + + let mut x = rx.await?; + forget(g); + if let Some(ResponseError { code, ref mut data, .. }) = + x.error + { + if code == ErrorCode::ServerCancelled as i32 { + let e = serde_json::from_value( + data.take().unwrap_or_default(), + ); + Err(RequestError::Cancelled(x, e.expect("lsp??"))) + } else { + Err(RequestError::Failure( + x, + Some(Backtrace::capture()), + )) + } + } else { + Ok(serde_json::from_value::<X::Result>( + x.result.clone().unwrap_or_default(), + ) + .unwrap_or_else(|_| { + panic!( + "lsp failure for {x:?}\ndidnt follow spec \ + for {}\npossibly spec issue", + X::METHOD + ) + })) + } + }, + id, + )) + } +} diff --git a/src/lsp/init_opts.rs b/src/lsp/init_opts.rs new file mode 100644 index 0000000..19bd140 --- /dev/null +++ b/src/lsp/init_opts.rs @@ -0,0 +1,362 @@ +use Default::default; +use lsp_types::*; +use serde_json::json; +pub fn get(workspace: WorkspaceFolder) -> InitializeParams { + InitializeParams { + process_id: Some(std::process::id()), + + capabilities: ClientCapabilities { + window: Some(WindowClientCapabilities { + work_done_progress: Some(true), + ..default() + }), + workspace: Some(WorkspaceClientCapabilities { + symbol: Some(WorkspaceSymbolClientCapabilities { + symbol_kind: Some(SymbolKindCapability { + value_set: Some(SymbolKind::ALL.to_vec()), + }), + tag_support: Some(TagSupport { + value_set: SymbolTag::ALL.to_vec(), + }), + resolve_support: Some( + WorkspaceSymbolResolveSupportCapability { + properties: vec!["location".into()], + }, + ), + ..default() + }), + diagnostic: Some(DiagnosticWorkspaceClientCapabilities { + refresh_support: Some(true), + }), + inlay_hint: Some(InlayHintWorkspaceClientCapabilities { + refresh_support: Some(true), + }), + workspace_edit: Some(WorkspaceEditClientCapabilities { + document_changes: Some(true), + resource_operations: Some(vec![ + ResourceOperationKind::Create, + ResourceOperationKind::Rename, + ResourceOperationKind::Delete, + ]), + failure_handling: Some(FailureHandlingKind::Abort), + normalizes_line_endings: Some(false), + change_annotation_support: Some( + ChangeAnnotationWorkspaceEditClientCapabilities { + groups_on_label: Some(false), + }, + ), + }), + ..default() + }), + text_document: Some(TextDocumentClientCapabilities { + on_type_formatting: Some( + DocumentOnTypeFormattingClientCapabilities { + dynamic_registration: Some(false), + }, + ), + document_highlight: Some(default()), + formatting: Some(DynamicRegistrationClientCapabilities { + dynamic_registration: Some(false), + }), + inlay_hint: Some(InlayHintClientCapabilities { + dynamic_registration: None, + resolve_support: Some( + InlayHintResolveClientCapabilities { + properties: vec![ + "textEdits".into(), + "tooltip".into(), + "label.tooltip".into(), + "label.command".into(), + ], + }, + ), + }), + document_symbol: Some(DocumentSymbolClientCapabilities { + tag_support: Some(TagSupport { + value_set: SymbolTag::ALL.to_vec(), + }), + symbol_kind: Some(SymbolKindCapability { + value_set: Some(SymbolKind::ALL.to_vec()), + }), + hierarchical_document_symbol_support: Some(true), + ..default() + }), + definition: Some(GotoCapability { + link_support: Some(true), + ..default() + }), + code_action: Some(CodeActionClientCapabilities { + data_support: Some(true), + resolve_support: Some( + CodeActionCapabilityResolveSupport { + properties: vec!["edit".to_string()], + }, + ), + code_action_literal_support: Some( + CodeActionLiteralSupport { + code_action_kind: + CodeActionKindLiteralSupport { + value_set: [ + "", + "Empty", + "QuickFix", + "Refactor", + "RefactorExtract", + "RefactorInline", + "RefactorRewrite", + "Source", + "SourceOrganizeImports", + "quickfix", + "refactor", + "refactor.extract", + "refactor.inline", + "refactor.rewrite", + "source", + "source.organizeImports", + ] + .map(String::from) + .into(), + }, + }, + ), + ..default() + }), + rename: Some(RenameClientCapabilities { + prepare_support: Some(true), + prepare_support_default_behavior: Some( + PrepareSupportDefaultBehavior::IDENTIFIER, + ), + honors_change_annotations: Some(false), + ..default() + }), + hover: Some(HoverClientCapabilities { + dynamic_registration: None, + content_format: Some(vec![ + MarkupKind::PlainText, + MarkupKind::Markdown, + ]), + }), + diagnostic: Some(DiagnosticClientCapabilities { + dynamic_registration: None, + related_document_support: Some(true), + }), + publish_diagnostics: Some( + PublishDiagnosticsClientCapabilities { + related_information: Some(true), + code_description_support: Some(true), + data_support: Some(true), + ..default() + }, + ), + signature_help: Some(SignatureHelpClientCapabilities { + dynamic_registration: None, + signature_information: Some( + SignatureInformationSettings { + documentation_format: Some(vec![ + MarkupKind::Markdown, + MarkupKind::PlainText, + ]), + parameter_information: Some( + ParameterInformationSettings { + label_offset_support: Some(true), + }, + ), + active_parameter_support: Some(true), + }, + ), + context_support: Some(false), + }), + completion: Some(CompletionClientCapabilities { + dynamic_registration: Some(false), + completion_item: Some(CompletionItemCapability { + snippet_support: Some(true), + commit_characters_support: Some(true), + documentation_format: Some(vec![ + MarkupKind::Markdown, + MarkupKind::PlainText, + ]), + deprecated_support: None, + preselect_support: None, + tag_support: Some(TagSupport { + value_set: vec![CompletionItemTag::DEPRECATED], + }), + + resolve_support: Some( + CompletionItemCapabilityResolveSupport { + properties: vec![ + "additionalTextEdits".into(), + "documentation".into(), + ], + }, + ), + insert_replace_support: Some(false), + insert_text_mode_support: Some( + InsertTextModeSupport { + value_set: vec![InsertTextMode::AS_IS], + }, + ), + label_details_support: Some(true), + ..default() + }), + completion_item_kind: Some( + CompletionItemKindCapability { + value_set: Some(vec![ + CompletionItemKind::TEXT, + CompletionItemKind::METHOD, + CompletionItemKind::FUNCTION, + CompletionItemKind::CONSTRUCTOR, + CompletionItemKind::FIELD, + CompletionItemKind::VARIABLE, + CompletionItemKind::CLASS, + CompletionItemKind::INTERFACE, + CompletionItemKind::MODULE, + CompletionItemKind::PROPERTY, + CompletionItemKind::UNIT, + CompletionItemKind::VALUE, + CompletionItemKind::ENUM, + CompletionItemKind::KEYWORD, + CompletionItemKind::SNIPPET, + CompletionItemKind::COLOR, + CompletionItemKind::FILE, + CompletionItemKind::REFERENCE, + CompletionItemKind::FOLDER, + CompletionItemKind::ENUM_MEMBER, + CompletionItemKind::CONSTANT, + CompletionItemKind::STRUCT, + CompletionItemKind::EVENT, + CompletionItemKind::OPERATOR, + CompletionItemKind::TYPE_PARAMETER, + ]), + // value_set: Some(vec![CompletionItemKind::]), + }, + ), + context_support: None, + insert_text_mode: Some(InsertTextMode::AS_IS), + completion_list: Some(CompletionListCapability { + item_defaults: None, + }), + }), + semantic_tokens: Some(SemanticTokensClientCapabilities { + dynamic_registration: Some(false), + requests: SemanticTokensClientCapabilitiesRequests { + range: Some(true), + full: Some( + lsp_types::SemanticTokensFullOptions::Bool( + true, + ), + ), + }, + token_modifiers: [ + "associated", + "attribute", + "callable", + "constant", + "consuming", + "controlFlow", + "crateRoot", + "injected", + "intraDocLink", + "library", + "macro", + "mutable", + "procMacro", + "public", + "reference", + "trait", + "unsafe", + ] + .map(|x| x.into()) + .to_vec(), + overlapping_token_support: Some(true), + multiline_token_support: Some(true), + server_cancel_support: Some(false), + augments_syntax_tokens: Some(false), + ..default() + }), + ..default() + }), + + general: Some(GeneralClientCapabilities { + markdown: Some(MarkdownClientCapabilities { + version: Some("1.0.0".into()), + parser: "markdown".into(), + allowed_tags: Some(vec![]), + }), + position_encodings: Some(vec![PositionEncodingKind::UTF8]), + ..default() + }), + experimental: Some(json! {{ + "matchingBrace": true, + "snippetTextEdit": true, + "colorDiagnosticOutput": true, + "codeActionGroup": true, + "serverStatusNotification": true, + "hoverActions": true, + "workspaceSymbolScopeKindFiltering": true, + "onEnter": true, + "localDocs": true, + }}), + ..default() + }, + client_info: Some(ClientInfo { + name: "gracilaria".into(), + version: Some(env!("CARGO_PKG_VERSION").into()), + }), + initialization_options: Some(json! {{ + "cargo": { + "buildScripts": { "enable": true } + }, + "procMacro": { + "enable": true, + "attributes": { "enable": true } + }, + "hover": { + "documentation": { + "keywords": { "enable": false }, + }, + }, + "inlayHints": { + "closureReturnTypeHints": { "enable": "with_block" }, + "closingBraceHints": { "minLines": 5 }, + "closureStyle": "rust_analyzer", + "genericParameterHints": { "type": { "enable": true } }, + "rangeExclusiveHints": { "enable": true }, + "closureCaptureHints": { "enable": true }, + }, + "typing": { "triggerChars": ".=<>{(+" }, + "assist": { "preferSelf": true }, + "checkOnSave": true, + "diagnostics": { "enable": true }, + "semanticHighlighting": { + "punctuation": { + "separate": { + "macroBang": true + }, + "specialization": { "enable": true }, + "enable": true + } + }, + "workspace": { + "symbol": { + "search": { "limit": 1024 } + } + }, + "showUnlinkedFileNotification": false, + "completion": { + "fullFunctionSignatures": { "enable": true, }, + "autoIter": { "enable": false, }, + "autoImport": { "enable": true, }, + "termSearch": { "enable": true, }, + "autoself": { "enable": true, }, + "privateEditable": { "enable": true }, + }, + "imports": { + "granularity": "group", + }, + }}), + trace: None, + workspace_folders: Some(vec![workspace]), + + ..default() + } +} diff --git a/src/lsp/rq.rs b/src/lsp/rq.rs new file mode 100644 index 0000000..18c6616 --- /dev/null +++ b/src/lsp/rq.rs @@ -0,0 +1,171 @@ +use std::backtrace::Backtrace; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::sync::Arc; + +use crossbeam::channel::SendError; +use lsp_server::{Message, Response as Re}; +use lsp_types::DiagnosticServerCancellationData; +use lsp_types::request::Request; +use serde::{Deserialize, Serialize}; +use tokio::sync::oneshot; +use tokio::task; +use tokio_util::task::AbortOnDropHandle; +use winit::window::Window; + +#[derive(Serialize, Deserialize)] +pub enum RequestError<X> { + Rx(PhantomData<X>), + Failure(Re, #[serde(skip)] Option<Backtrace>), + Cancelled(Re, DiagnosticServerCancellationData), + Send(Message), +} +pub type AQErr = RequestError<LSPError>; +impl Request for LSPError { + type Params = (); + type Result = (); + const METHOD: &'static str = "<unknown method>"; +} +#[derive(Debug)] +pub struct LSPError {} +pub trait Anonymize<T> { + fn anonymize(self) -> Result<T, RequestError<LSPError>>; +} +impl<T, E> Anonymize<T> for Result<T, RequestError<E>> { + fn anonymize(self) -> Result<T, RequestError<LSPError>> { + self.map_err(|e| match e { + RequestError::Send(x) => RequestError::Send(x), + RequestError::Rx(_) => RequestError::Rx(PhantomData), + RequestError::Failure(r, b) => RequestError::Failure(r, b), + RequestError::Cancelled(r, d) => RequestError::Cancelled(r, d), + }) + } +} + +// impl<X> Debug for RequestError<X> {} +impl<X> From<oneshot::error::RecvError> for RequestError<X> { + fn from(_: oneshot::error::RecvError) -> Self { + Self::Rx(PhantomData) + } +} +impl<X: Request + Debug> std::error::Error for RequestError<X> { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} +impl<X> From<SendError<Message>> for RequestError<X> { + fn from(x: SendError<Message>) -> Self { + Self::Send(x.into_inner()) + } +} +impl<X: Request> std::fmt::Display for RequestError<X> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Send(x) => + write!(f, "{} failed; couldnt send {x:?}", X::METHOD), + Self::Rx(_) => + write!(f, "{} failed; couldnt get from thingy", X::METHOD), + Self::Failure(x, bt) => write!( + f, + "{} failed; returned badge :( {x:?} ({bt:?})", + X::METHOD + ), + Self::Cancelled(x, y) => + write!(f, "server cancelled {}. {x:?} {y:?}", X::METHOD), + } + } +} +impl<R: Request> Debug for RequestError<R> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self, f) + } +} + +fn none<T>() -> Option<T> { + None +} +impl<T: Clone, R, D, E> Clone for Rq<T, R, D, E> { + fn clone(&self) -> Self { + Self { result: self.result.clone(), request: None } + } +} +#[derive(serde_derive::Serialize, serde_derive::Deserialize)] +pub struct Rq<T, R, D = (), E = RequestError<R>> { + #[serde(skip_serializing_if = "Option::is_none", default = "none")] + pub result: Option<T>, + #[serde(skip, default = "none")] + pub request: Option<(AbortOnDropHandle<Result<R, E>>, D)>, +} +impl<T: Debug, R, D: Debug, E> Debug for Rq<T, R, D, E> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(&format!("Rq<{}>", std::any::type_name::<R>())) + .field("result", &self.result) + .field("request", &self.request) + .finish() + } +} + +pub type RqS<T, R: Request, D = ()> = Rq<T, R::Result, D, RequestError<R>>; +impl<T, R, D, E> Default for Rq<T, R, D, E> { + fn default() -> Self { + Self { result: None, request: None } + } +} +impl<T, R, E> Rq<T, R, (), E> { + pub fn new(f: task::JoinHandle<Result<R, E>>) -> Self { + Self { + request: Some((AbortOnDropHandle::new(f), ())), + result: None, + } + } + pub fn request(&mut self, f: task::JoinHandle<Result<R, E>>) { + self.request = Some((AbortOnDropHandle::new(f), ())); + } +} +impl<T, R, D, E> Rq<T, R, D, E> { + pub fn running(&self) -> bool { + matches!( + self, + Self { result: Some(_), .. } | Self { request: Some(_), .. }, + ) + } + pub fn poll( + &mut self, + f: impl FnOnce(Result<R, E>, (D, Option<T>)) -> Option<T>, + ) -> bool { + if self.request.as_mut().is_some_and(|(x, _)| x.is_finished()) + && let Some((task, _)) = self.request.as_mut() + && let Some(x) = futures::FutureExt::now_or_never(task) + { + let (_, d) = self.request.take().unwrap(); + self.result = f( + match x { + Ok(x) => x, + Err(e) => { + log::error!( + "unexpected join error from request poll: {e}" + ); + return false; + } + }, + (d, self.result.take()), + ); + true + } else { + false + } + } + + pub fn poll_r( + &mut self, + f: impl FnOnce(Result<R, E>, (D, Option<T>)) -> Option<T>, + _runtime: &tokio::runtime::Runtime, + _w: Option<&Arc<dyn Window>>, + ) -> bool { + self.poll(|x, y| { + f(x, y).inspect(|_| { + // w.map(|x| x.request_redraw()); + }) + }) + } +} diff --git a/src/main.rs b/src/main.rs index e70d96a..110005c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -146,7 +146,7 @@ pub(crate) fn entry(event_loop: EventLoop) { Some((.., c)) => c.take(), None => None, }; - let (fw, fh) = dsb::dims(&fonts.bold, ls); + let (_fw, _fh) = dsb::dims(&fonts.bold, ls); let title = ed.title(); let app = winit_app::WinitAppBuilder::with_init( move |elwt| { |