A simple CPU rendered GUI IDE experience.
Diffstat (limited to 'src/lsp/client.rs')
-rw-r--r--src/lsp/client.rs505
1 files changed, 505 insertions, 0 deletions
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"),
+ }
+ }
+}