A simple CPU rendered GUI IDE experience.
wip
| -rw-r--r-- | Cargo.toml | 21 | ||||
| -rw-r--r-- | ra-support | 560 | ||||
| -rw-r--r-- | src/bar.rs | 29 | ||||
| -rw-r--r-- | src/lsp.rs | 501 | ||||
| -rw-r--r-- | src/main.rs | 119 | ||||
| -rw-r--r-- | src/text.rs | 285 |
6 files changed, 1437 insertions, 78 deletions
@@ -30,6 +30,25 @@ regex = "1.11.3" tree-house = { version = "0.3.0", features = ["fixtures"] } helix-loader = { path = "../helix/helix-loader/" } helix-core = { path = "../helix/helix-core/" } +serde_json = "1.0.145" +serde = "1.0.228" +serde_derive = "1.0.228" +log = "0.4.28" +crossbeam = { version = "0.8.4", features = ["nightly", "crossbeam-channel"] } +lsp-server = { version = "0.7.9", path = "../rust-analyzer/lib/lsp-server" } +test-log = "0.2.18" +lsp-types = { path = "../helix/helix-lsp-types", package = "helix-lsp-types" } +env_logger = "0.11.8" +oneshot = { version = "0.1.11", default-features = false, features = [ + "async", + "std", +] } +url = "2.5.7" +anyhow = "1.0.100" +arc-swap = "1.7.1" +tokio = { version = "1.47.1", features = ["rt-multi-thread"] } +regex-cursor = "0.1.5" +papaya = "0.2.3" [build-dependencies] cc = "*" @@ -37,4 +56,4 @@ cc = "*" [profile.release] debug = 2 # overflow-checks = true -debug-assertions = true +# debug-assertions = true diff --git a/ra-support b/ra-support new file mode 100644 index 0000000..db00ec7 --- /dev/null +++ b/ra-support @@ -0,0 +1,560 @@ +InitializeResult { + capabilities: ServerCapabilities { + position_encoding: Some( + PositionEncodingKind( + "utf-8", + ), + ), + text_document_sync: Some( + Options( + TextDocumentSyncOptions { + open_close: Some( + true, + ), + change: Some( + Incremental, + ), + will_save: None, + will_save_wait_until: None, + save: Some( + SaveOptions( + SaveOptions { + include_text: None, + }, + ), + ), + }, + ), + ), + selection_range_provider: Some( + Simple( + true, + ), + ), + hover_provider: Some( + Simple( + true, + ), + ), + completion_provider: Some( + CompletionOptions { + resolve_provider: Some( + false, + ), + trigger_characters: Some( + [ + ":", + ".", + "'", + "(", + ], + ), + all_commit_characters: None, + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + completion_item: Some( + CompletionOptionsCompletionItem { + label_details_support: Some( + false, + ), + }, + ), + }, + ), + signature_help_provider: Some( + SignatureHelpOptions { + trigger_characters: Some( + [ + "(", + ",", + "<", + ], + ), + retrigger_characters: None, + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + }, + ), + definition_provider: Some( + Left( + true, + ), + ), + type_definition_provider: Some( + Simple( + true, + ), + ), + implementation_provider: Some( + Simple( + true, + ), + ), + references_provider: Some( + Left( + true, + ), + ), + document_highlight_provider: Some( + Left( + true, + ), + ), + document_symbol_provider: Some( + Left( + true, + ), + ), + workspace_symbol_provider: Some( + Left( + true, + ), + ), + code_action_provider: Some( + Simple( + true, + ), + ), + code_lens_provider: Some( + CodeLensOptions { + resolve_provider: Some( + true, + ), + }, + ), + document_formatting_provider: Some( + Left( + true, + ), + ), + document_range_formatting_provider: Some( + Left( + false, + ), + ), + document_on_type_formatting_provider: Some( + DocumentOnTypeFormattingOptions { + first_trigger_character: ".", + more_trigger_character: Some( + [ + "=", + "<", + ">", + "{", + "(", + "|", + "+", + ], + ), + }, + ), + rename_provider: Some( + Right( + RenameOptions { + prepare_provider: Some( + true, + ), + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + }, + ), + ), + document_link_provider: None, + color_provider: None, + folding_range_provider: Some( + Simple( + true, + ), + ), + declaration_provider: Some( + Simple( + true, + ), + ), + execute_command_provider: None, + workspace: Some( + WorkspaceServerCapabilities { + workspace_folders: Some( + WorkspaceFoldersServerCapabilities { + supported: Some( + true, + ), + change_notifications: Some( + Left( + true, + ), + ), + }, + ), + file_operations: Some( + WorkspaceFileOperationsServerCapabilities { + did_create: None, + will_create: None, + did_rename: None, + will_rename: Some( + FileOperationRegistrationOptions { + filters: [ + FileOperationFilter { + scheme: Some( + "file", + ), + pattern: FileOperationPattern { + glob: "**/*.rs", + matches: Some( + File, + ), + options: None, + }, + }, + FileOperationFilter { + scheme: Some( + "file", + ), + pattern: FileOperationPattern { + glob: "**", + matches: Some( + Folder, + ), + options: None, + }, + }, + ], + }, + ), + did_delete: None, + will_delete: None, + }, + ), + }, + ), + call_hierarchy_provider: Some( + Simple( + true, + ), + ), + semantic_tokens_provider: Some( + SemanticTokensOptions( + SemanticTokensOptions { + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + legend: SemanticTokensLegend { + token_types: [ + SemanticTokenType( + "comment", + ), + SemanticTokenType( + "decorator", + ), + SemanticTokenType( + "enumMember", + ), + SemanticTokenType( + "enum", + ), + SemanticTokenType( + "function", + ), + SemanticTokenType( + "interface", + ), + SemanticTokenType( + "keyword", + ), + SemanticTokenType( + "macro", + ), + SemanticTokenType( + "method", + ), + SemanticTokenType( + "namespace", + ), + SemanticTokenType( + "number", + ), + SemanticTokenType( + "operator", + ), + SemanticTokenType( + "parameter", + ), + SemanticTokenType( + "property", + ), + SemanticTokenType( + "string", + ), + SemanticTokenType( + "struct", + ), + SemanticTokenType( + "typeParameter", + ), + SemanticTokenType( + "variable", + ), + SemanticTokenType( + "type", + ), + SemanticTokenType( + "angle", + ), + SemanticTokenType( + "arithmetic", + ), + SemanticTokenType( + "attributeBracket", + ), + SemanticTokenType( + "attribute", + ), + SemanticTokenType( + "bitwise", + ), + SemanticTokenType( + "boolean", + ), + SemanticTokenType( + "brace", + ), + SemanticTokenType( + "bracket", + ), + SemanticTokenType( + "builtinAttribute", + ), + SemanticTokenType( + "builtinType", + ), + SemanticTokenType( + "character", + ), + SemanticTokenType( + "colon", + ), + SemanticTokenType( + "comma", + ), + SemanticTokenType( + "comparison", + ), + SemanticTokenType( + "constParameter", + ), + SemanticTokenType( + "const", + ), + SemanticTokenType( + "deriveHelper", + ), + SemanticTokenType( + "derive", + ), + SemanticTokenType( + "dot", + ), + SemanticTokenType( + "escapeSequence", + ), + SemanticTokenType( + "formatSpecifier", + ), + SemanticTokenType( + "generic", + ), + SemanticTokenType( + "invalidEscapeSequence", + ), + SemanticTokenType( + "label", + ), + SemanticTokenType( + "lifetime", + ), + SemanticTokenType( + "logical", + ), + SemanticTokenType( + "macroBang", + ), + SemanticTokenType( + "parenthesis", + ), + SemanticTokenType( + "procMacro", + ), + SemanticTokenType( + "punctuation", + ), + SemanticTokenType( + "selfKeyword", + ), + SemanticTokenType( + "selfTypeKeyword", + ), + SemanticTokenType( + "semicolon", + ), + SemanticTokenType( + "static", + ), + SemanticTokenType( + "toolModule", + ), + SemanticTokenType( + "typeAlias", + ), + SemanticTokenType( + "union", + ), + SemanticTokenType( + "unresolvedReference", + ), + ], + token_modifiers: [ + SemanticTokenModifier( + "async", + ), + SemanticTokenModifier( + "documentation", + ), + SemanticTokenModifier( + "declaration", + ), + SemanticTokenModifier( + "static", + ), + SemanticTokenModifier( + "defaultLibrary", + ), + SemanticTokenModifier( + "associated", + ), + SemanticTokenModifier( + "attribute", + ), + SemanticTokenModifier( + "callable", + ), + SemanticTokenModifier( + "constant", + ), + SemanticTokenModifier( + "consuming", + ), + SemanticTokenModifier( + "controlFlow", + ), + SemanticTokenModifier( + "crateRoot", + ), + SemanticTokenModifier( + "injected", + ), + SemanticTokenModifier( + "intraDocLink", + ), + SemanticTokenModifier( + "library", + ), + SemanticTokenModifier( + "macro", + ), + SemanticTokenModifier( + "mutable", + ), + SemanticTokenModifier( + "procMacro", + ), + SemanticTokenModifier( + "public", + ), + SemanticTokenModifier( + "reference", + ), + SemanticTokenModifier( + "trait", + ), + SemanticTokenModifier( + "unsafe", + ), + ], + }, + range: Some( + true, + ), + full: Some( + Delta { + delta: Some( + true, + ), + }, + ), + }, + ), + ), + moniker_provider: None, + linked_editing_range_provider: None, + inline_value_provider: None, + inlay_hint_provider: Some( + Right( + Options( + InlayHintOptions { + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + resolve_provider: Some( + false, + ), + }, + ), + ), + ), + diagnostic_provider: Some( + Options( + DiagnosticOptions { + identifier: Some( + "rust-analyzer", + ), + inter_file_dependencies: true, + workspace_diagnostics: false, + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + }, + ), + ), + experimental: Some( + Object { + "childModules": Bool(true), + "externalDocs": Bool(true), + "hoverRange": Bool(true), + "joinLines": Bool(true), + "matchingBrace": Bool(true), + "moveItem": Bool(true), + "onEnter": Bool(true), + "openCargoToml": Bool(true), + "parentModule": Bool(true), + "runnables": Object { + "kinds": Array [ + String("cargo"), + ], + }, + "ssr": Bool(true), + "workspaceSymbolScopeKindFiltering": Bool(true), + }, + ), + }, + server_info: Some( + ServerInfo { + name: "rust-analyzer", + version: Some( + "1.92.0-nightly (f6aa851 2025-10-07)", + ), + }, + ), +}
\ No newline at end of file @@ -2,8 +2,10 @@ use std::iter::{once, repeat}; use dsb::Cell; use dsb::cell::Style; +use lsp_types::WorkDoneProgress; use winit::keyboard::{Key, ModifiersState, NamedKey}; +use crate::lsp::Client; use crate::text::TextArea; pub struct Bar { @@ -20,6 +22,7 @@ impl Bar { fname: &str, state: &super::State, t: &TextArea, + lsp: Option<&Client>, ) { let row = &mut into[oy * w..oy * w + w]; row.fill(Cell { @@ -51,10 +54,28 @@ impl Bar { .iter_mut() .zip(fname.chars()) .for_each(|(x, y)| x.letter = Some(y)); - row.iter_mut() - .rev() - .zip(self.last_action.chars().rev()) - .for_each(|(x, y)| x.letter = Some(y)); + // lsp.map(|x| dbg!(x.progress)); + if let Some(x) = lsp + && let Some(m) = x + .progress + .iter(&x.progress.guard()) + .find_map(|x| match x.1 { + Some(WorkDoneProgress::Report(x)) => + x.message.clone(), + _ => None, + }) + { + dbg!(&m); + row.iter_mut() + .rev() + .zip(m.chars().rev()) + .for_each(|(x, y)| x.letter = Some(y)); + } else { + row.iter_mut() + .rev() + .zip(self.last_action.chars().rev()) + .for_each(|(x, y)| x.letter = Some(y)); + } } State::Procure(x, r) => { r.prompt() diff --git a/src/lsp.rs b/src/lsp.rs new file mode 100644 index 0000000..3db71e7 --- /dev/null +++ b/src/lsp.rs @@ -0,0 +1,501 @@ +use std::collections::HashMap; +use std::io::BufReader; +use std::path::Path; +use std::process::{Command, Stdio}; +use std::sync::atomic::AtomicI32; +use std::sync::atomic::Ordering::Relaxed; +use std::thread::{JoinHandle, sleep, spawn}; +use std::time::{Duration, Instant}; + +use Default::default; +use anyhow::Error; +use arc_swap::ArcSwap; +use crossbeam::channel::{ + Receiver, RecvError, SendError, Sender, unbounded, +}; +use log::{debug, error}; +use lsp_server::{ + Message, Notification as N, Request as Rq, Response as Re, +}; +use lsp_types::notification::{ + Cancel, DidOpenTextDocument, Notification, Progress, SetTrace, +}; +use lsp_types::request::{ + Initialize, Request, SemanticTokensFullRequest, WorkDoneProgressCreate, +}; +use lsp_types::*; +use parking_lot::Mutex; +use serde_json::json; + +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>)>, + pub progress: + &'static papaya::HashMap<ProgressToken, Option<WorkDoneProgress>>, + pub not_rx: Receiver<N>, + pub req_rx: Receiver<Rq>, + pub semantic_tokens: ( + &'static ArcSwap<Box<[SemanticToken]>>, + Mutex<Option<(tokio::task::JoinHandle<Result<(), Error>>, i32)>>, + ), +} + +impl Drop for Client { + fn drop(&mut self) { + panic!("please dont") + } +} + +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(), + })) + } + + #[must_use] + pub fn request<X: Request>( + &self, + y: &X::Params, + ) -> Result<(oneshot::Receiver<Re>, i32), SendError<Message>> { + let id = self.id.fetch_add(1, std::sync::atomic::Ordering::AcqRel); + self.tx.send(Message::Request(Rq { + 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"); + self.send_to.send((id, tx)).expect("oughtnt really fail"); + } + Ok((rx, 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 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 rq_semantic_tokens(&self, f: &Path) -> anyhow::Result<()> { + debug!("requested semantic tokens"); + let mut p = self.semantic_tokens.1.lock(); + if let Some((h, task)) = &*p { + if !h.is_finished() { + debug!("cancelled previous semantic tokens request"); + self.notify::<Cancel>(&CancelParams { id: task.into() })?; + h.abort(); + } + } + let (rx, id) = self.request::<SemanticTokensFullRequest>( + &SemanticTokensParams { + work_done_progress_params: default(), + partial_result_params: default(), + text_document: TextDocumentIdentifier::new( + url::Url::from_file_path(f).unwrap(), + ), + }, + )?; + let d = self.semantic_tokens.0; + let x = self.runtime.spawn(async move { + let x = rx.await?; + debug!("received semantic tokens"); + + let y = x.load::<SemanticTokensResult>()?; + match y { + SemanticTokensResult::Partial(_) => + panic!("i told the lsp i dont support this"), + SemanticTokensResult::Tokens(x) => + d.store(x.data.into_boxed_slice().into()), + }; + anyhow::Ok(()) + }); + *p = Some((x, id)); + + Ok(()) + } +} +pub fn run( + (tx, rx, iot): ( + Sender<Message>, + Receiver<Message>, + lsp_server::IoThreads, + ), + workspace: WorkspaceFolder, +) -> (Client, lsp_server::IoThreads, JoinHandle<()>) { + let now = Instant::now(); + let (req_tx, req_rx) = unbounded(); + let (not_tx, not_rx) = unbounded(); + let (_req_tx, _req_rx) = unbounded(); + let mut c = Client { + tx, + req_rx: _req_rx, + progress: Box::leak(Box::new(papaya::HashMap::new())), + runtime: tokio::runtime::Builder::new_multi_thread() + .thread_name("lsp runtime") + .build() + .unwrap(), + id: AtomicI32::new(0), + initialized: None, + semantic_tokens: ( + Box::leak(Box::new(ArcSwap::new( + vec![].into_boxed_slice().into(), + ))), + Mutex::new(None), + ), + send_to: req_tx, + not_rx, + }; + c.request::<Initialize>(&InitializeParams { + process_id: Some(std::process::id()), + + capabilities: ClientCapabilities { + window: Some(WindowClientCapabilities { + work_done_progress: Some(true), + ..default() + }), + text_document: Some(TextDocumentClientCapabilities { + 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 { + position_encodings: Some(vec![PositionEncodingKind::UTF8]), + ..default() + }), + ..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, + }, + "procMacro.attributes.enable": true, + "inlayHints.closureReturnTypeHints.enable": "with_block", + "inlayHints.closingBraceHints.minLines": 5, + "inlayHints.closureStyle": "rust_analyzer", + "showUnlinkedFileNotification": false, + "inlayHints.genericParameterHints.type.enable": true, + "inlayHints.rangeExclusiveHints.enable": true, + "inlayHints.closureCaptureHints.enable": true, + "inlayHints.expressionAdjustmentHints.hideOutsideUnsafe": true, + "inlayHints.expressionAdjustmentHints.enable": "never", + "inlayHints.expressionAdjustmentHints.mode": "prefer_prefix", + "semanticHighlighting.punctuation.separate.macro.bang": true, + "semanticHighlighting.punctuation.enable": true, + "semanticHighlighting.punctuation.specialization.enable": true, + }}), + trace: None, + workspace_folders: Some(vec![workspace]), + + ..default() + }) + .unwrap(); + let x = serde_json::from_value::<InitializeResult>( + rx.recv().unwrap().response().unwrap().result.unwrap(), + ) + .unwrap(); + assert_eq!( + x.capabilities.position_encoding, + Some(PositionEncodingKind::UTF8) + ); + c.initialized = Some(x); + c.notify::<lsp_types::notification::Initialized>( + &InitializedParams {}, + ) + .unwrap(); + c.notify::<SetTrace>(&SetTraceParams { + value: lsp_types::TraceValue::Verbose, + }) + .unwrap(); + let progress = c.progress; + log::info!("lsp took {:?} to initialize", now.elapsed()); + let h = spawn(move || { + let mut map = HashMap::new(); + loop { + crossbeam::select! { + recv(req_rx) -> x => match x { + Ok((x, y)) => { + debug!("received request {x}"); + assert!(map.insert(x, y).is_none()); + } + Err(RecvError) => return, + }, + recv(rx) -> x => match x { + Ok(Message::Request(rq @ Rq { 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) { + error!("couldnt receive request {e:?}"); + } + } + Ok(Message::Response(x)) => { + if let Some(s) = map.remove(&x.id.i32()) { + match s.send(x) { + Ok(()) => {} + Err(e) => { + error!( + "unable to respond to {:?}", + e.into_inner() + ); + } + } + } else { + error!("request {x:?} was dropped.") + } + } + Ok(Message::Notification(x @ N { method: "$/progress", .. })) => { + let ProgressParams {token,value:ProgressParamsValue::WorkDone(x) } = x.load::<Progress>().unwrap(); + progress.update(token, move |_| Some(x.clone()), &progress.guard()); + } + Ok(Message::Notification(notification)) => { + debug!("rx {notification:?}"); + not_tx + .send(notification) + .expect("why library drop this??? no drop!!"); + } + Err(RecvError) => return, + } + } + } + }); + (c, iot, h) +} + +pub fn x() { + let mut c = Command::new("/home/os/.cargo/bin/rust-analyzer") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + + log::info!("helol"); + + let (c, rx, iot) = run( + lsp_server::stdio::stdio_transport( + BufReader::new(c.stdout.take().unwrap()), + c.stdin.take().unwrap(), + ), + WorkspaceFolder { + uri: "file:///home/os/gracilaria".parse().unwrap(), + name: "gracilaria".into(), + }, + ); + let n = c.not_rx.clone(); + let r = c.req_rx.clone(); + let p = c.progress; + + // c.request::<SemanticTokensFullRequest>(&SemanticTokensParams { + // work_done_progress_params: default(), + // partial_result_params: default(), + // text_document: TextDocumentIdentifier::new( + // url::Url::from_file_path(Path::new( + // "/home/os/gracilaria/src/text.rs", + // )) + // .unwrap(), + // ), + // }) + // .unwrap(); + sleep(Duration::from_secs(40)); + c.open( + Path::new("/home/os/gracilaria/src/user.rs"), + "fn main() {}".into(), + ) + .unwrap(); + c.rq_semantic_tokens(Path::new("/home/os/gracilaria/src/user.rs")) + .unwrap(); + + dbg!(rx.writer.join().unwrap()).unwrap(); + + spawn(|| { + for elem in r { + match &*elem.method { + x if x == WorkDoneProgressCreate::METHOD => { + elem.load::<WorkDoneProgressCreate>().unwrap(); + } + _ => {} + } + } + }); + loop {} + drop(c); + + // let wait = c + // .request::<SemanticTokensFullRequest>(&SemanticTokensParams { + // work_done_progress_params: default(), + // partial_result_params: default(), + // text_document: TextDocumentIdentifier { + // uri: "file:///home/os/gracilaria/src/main.rs" + // .parse() + // .unwrap(), + // }, + // }) + // .unwrap(); + // spawn(|| { + // let x = wait.recv_eepy().unwrap(); + // println!( + // "found! {:#?}", + // x.extract::<SemanticTokensResult>().unwrap() + // ); + // }); +} + +trait RecvEepy<T>: Sized { + fn recv_eepy(self) -> Result<T, RecvError> { + self.recv_sleepy(100) + } + fn recv_sleepy(self, x: u64) -> Result<T, RecvError>; +} + +impl<T> RecvEepy<T> for oneshot::Receiver<T> { + fn recv_sleepy(self, x: u64) -> Result<T, RecvError> { + loop { + return match self.recv_timeout(Duration::from_millis(x)) { + Err(oneshot::RecvTimeoutError::Timeout) => continue, + Ok(x) => Ok(x), + Err(oneshot::RecvTimeoutError::Disconnected) => + Err(crossbeam::channel::RecvError), + }; + } + } +} + +trait Void<T> { + fn void(self) -> Result<T, ()>; +} +impl<T, E> Void<T> for Result<T, E> { + fn void(self) -> Result<T, ()> { + self.map_err(|_| ()) + } +} + +#[test] +fn x22() { + let (tx, rx) = std::sync::mpmc::channel::<u8>(); + + let rx2 = rx.clone(); + spawn(move || { + loop { + println!("t1 {}", rx.recv().unwrap()); + } + }); + spawn(move || { + loop { + println!("t2 {}", rx2.recv().unwrap()); + } + }); + spawn(move || { + for n in 0..20 { + tx.send(n).unwrap(); + } + }); + loop {} +} + +#[test] +fn x33() { + let y = serde_json::to_string(&SemanticTokensParams { + work_done_progress_params: default(), + partial_result_params: default(), + text_document: TextDocumentIdentifier::new( + url::Url::from_file_path(Path::new( + "/home/os/gracilaria/src/text.rs", + )) + .unwrap(), + ), + }) + .unwrap(); + println!("{y}"); + let y = serde_json::from_str::<SemanticTokensParams>(&y).unwrap(); +} diff --git a/src/main.rs b/src/main.rs index 7fa9356..349dd30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ // this looks pretty good though #![feature(tuple_trait, unboxed_closures, fn_traits)] #![feature( + mpmc_channel, const_cmp, const_default, import_trait_associated_functions, @@ -13,8 +14,10 @@ )] #![allow(incomplete_features, redundant_semicolons)] use std::convert::identity; +use std::io::BufReader; use std::num::NonZeroU32; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; use std::sync::LazyLock; use std::time::Instant; @@ -24,10 +27,16 @@ use diff_match_patch_rs::PatchInput; use dsb::cell::Style; use dsb::{Cell, F}; use fimg::Image; +use lsp_types::{ + SemanticTokensOptions, SemanticTokensServerCapabilities, + ServerCapabilities, TextDocumentIdentifier, + TextDocumentPositionParams, WorkspaceFolder, +}; use regex::Regex; use ropey::Rope; use rust_fsm::StateMachineImpl; use swash::{FontRef, Instance}; +use url::Url; use winit::event::{ ElementState, Event, MouseButton, MouseScrollDelta, WindowEvent, }; @@ -37,10 +46,12 @@ use winit::keyboard::{Key, ModifiersState, NamedKey, SmolStr}; use crate::bar::Bar; use crate::text::{Diff, TextArea}; mod bar; +mod lsp; mod text; mod winit_app; fn main() { - // text::man(); + env_logger::init(); + // lsp::x(); entry(EventLoop::new().unwrap()) } #[derive(Debug)] @@ -108,10 +119,13 @@ impl Hist { self.push_if_changed(x); } } - pub fn record(&mut self, _: &TextArea) { + pub fn record(&mut self, new: &TextArea) -> bool { // self.test_push(x); - self.last_edit = Instant::now(); - self.changed = true; + if new.rope != self.last.rope { + self.last_edit = Instant::now(); + self.changed = true; + } + new.rope != self.last.rope } } @@ -126,8 +140,10 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { let ls = 20.0; let mut text = TextArea::default(); - let mut origin = - std::env::args().nth(1).and_then(|x| PathBuf::try_from(x).ok()); + let mut origin = std::env::args() + .nth(1) + .and_then(|x| PathBuf::try_from(x).ok()) + .and_then(|x| x.canonicalize().ok()); let mut fonts = dsb::Fonts::new( F::FontRef(*FONT, &[(2003265652, 550.0)]), F::instance(*FONT, *BFONT), @@ -145,6 +161,48 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { text.insert(&std::fs::read_to_string(x).unwrap()); text.cursor = 0; }); + fn rooter(x: &Path) -> Option<PathBuf> { + for f in std::fs::read_dir(&x).unwrap().filter_map(Result::ok) { + if f.file_name() == "Cargo.toml" { + return Some(f.path().with_file_name("").to_path_buf()); + } + } + x.parent().and_then(rooter) + } + let workspace = origin + .as_ref() + .and_then(|x| rooter(&x.parent().unwrap())) + .and_then(|x| x.canonicalize().ok()); + let c = workspace.zip(origin.clone()).map(|(workspace, origin)| { + let mut c = Command::new("rust-analyzer") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); + + let (c, t, t2) = lsp::run( + lsp_server::stdio::stdio_transport( + BufReader::new(c.stdout.take().unwrap()), + c.stdin.take().unwrap(), + ), + WorkspaceFolder { + uri: Url::from_file_path(&workspace).unwrap(), + name: workspace + .file_name() + .unwrap() + .to_string_lossy() + .into_owned(), + }, + ); + c.open(&origin, std::fs::read_to_string(&origin).unwrap()) + .unwrap(); + ((c, origin), (t, t2)) + }); + let (lsp, t) = c.unzip(); + + // let mut hl_result = None; + let mut hist = Hist { history: vec![], redo_history: vec![], @@ -159,6 +217,19 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { .map(|x| x.metadata().unwrap().modified().unwrap()) }; } + macro_rules! change { + () => { + lsp.as_ref() + .map(|(x, origin)| { + x.edit(&origin, text.rope.to_string()).unwrap(); + x.rq_semantic_tokens(origin).unwrap(); + }) + .unwrap(); + }; + } + lsp.as_ref() + .map(|(x, origin)| x.rq_semantic_tokens(origin).unwrap()) + .unwrap(); let mut mtime = modify!(); macro_rules! save { () => {{ @@ -301,6 +372,16 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { } }, origin.as_deref(), + lsp.as_ref().and_then(|(x, _)| { match &x.initialized { + Some(lsp_types::InitializeResult { + capabilities: ServerCapabilities { + semantic_tokens_provider: + Some(SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions{ + legend,.. + })),.. + },.. + }) => Some(legend), _ => None, }}.map(|leg|(x.semantic_tokens.0.load(), leg)) + ), ); bar.write_to( @@ -314,6 +395,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { .unwrap_or("new buffer"), &state, &text, + lsp.as_ref().map(|x| &x.0) ); println!("cell="); @@ -376,16 +458,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { ) .as_chunks_unchecked_mut::<4>() }; - fimg::overlay::copy_rgb_bgr_( - i.flatten(), - unsafe { - std::slice::from_raw_parts_mut( - buffer.as_ptr() as *mut u8, - buffer.len() * 4, - ) - .as_chunks_unchecked_mut::<4>() - }, - ); + fimg::overlay::copy_rgb_bgr_(i.flatten(), x); // dbg!(now.elapsed()); buffer.present().unwrap(); } @@ -438,7 +511,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { if button == MouseButton::Left { unsafe { CLICKING = true }; } - match state.consume(Action::M(button)).unwrap() { + match dbg!(state.consume(Action::M(button)).unwrap() ){ Some(Do::MoveCursor) => { text.cursor = text.index_at(cursor_position); text.setc(); @@ -460,6 +533,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { None => {} _ => unreachable!(), } + window.request_redraw(); } Event::WindowEvent { event: @@ -486,6 +560,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { let rows = rows.floor() as usize; text.vo = text.vo.saturating_sub(rows); } + window.request_redraw(); } Event::WindowEvent { event: WindowEvent::ModifiersChanged(modifiers), @@ -542,17 +617,21 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { hist.test_push(&text); handle2(event.logical_key, &mut text); text.scroll_to_cursor(); - hist.record(&text); + if hist.record(&text) { + change!(); + } } Some(Do::Undo) => { hist.test_push(&text); hist.undo(&mut text); bar.last_action = "undid".to_string(); + change!(); } Some(Do::Redo) => { hist.test_push(&text); hist.redo(&mut text); bar.last_action = "redid".to_string(); + change!(); } Some(Do::Quit) => elwt.exit(), Some(Do::StartSelection) => { @@ -604,7 +683,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { hist.push_if_changed(&text); } Some(Do::OpenFile(x)) => { - origin = Some(PathBuf::try_from(&x).unwrap()); + origin = Some(PathBuf::from(&x)); text = TextArea::default(); text.insert( &std::fs::read_to_string(x).unwrap(), diff --git a/src/text.rs b/src/text.rs index 08c1cf2..1a66d11 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,8 +1,8 @@ use std::cmp::min; use std::fmt::{Debug, Display}; -use std::ops::{Not as _, Range}; +use std::ops::{Deref, Not as _, Range}; use std::path::Path; -use std::sync::LazyLock; +use std::sync::{Arc, LazyLock}; use atools::prelude::*; use diff_match_patch_rs::{DiffMatchPatch, Patches}; @@ -11,33 +11,125 @@ use dsb::cell::Style; use helix_core::Syntax; use helix_core::syntax::{HighlightEvent, Loader}; use implicit_fn::implicit_fn; +use log::error; +use lsp_types::{ + SemanticToken, SemanticTokensLegend, SemanticTokensServerCapabilities, +}; use ropey::{Rope, RopeSlice}; +use tree_house::fixtures; use winit::keyboard::{NamedKey, SmolStr}; + +use crate::MODIFIERS; +use crate::text::semantic::{MCOLORS, MODIFIED, MSTYLE}; macro_rules! theme { - ($($x:literal $color:literal),+ $(,)?) => { + ($n:literal $($x:literal $color:literal $($style:expr)?),+ $(,)?) => { #[rustfmt::skip] - const NAMES: [&str; 15] = [$($x),+]; + pub const NAMES: [&str; $n] = [$($x),+]; #[rustfmt::skip] - const COLORS: [[u8; 3]; NAMES.len()] = car::map!([$($color),+], |x| color(x.tail()) - );}; + pub const COLORS: [[u8; 3]; NAMES.len()] = car::map!([$($color),+], |x| color(x.tail())); + pub const STYLES: [u8; NAMES.len()] = [$( + ($($style, )? 0, ).0 + ),+]; + }; } -theme! { +theme! { 15 "attribute" b"#ffd173", - "comment" b"#5c6773", + "comment" b"#5c6773" Style::ITALIC, "constant" b"#DFBFFF", - "function" b"#FFD173", + "function" b"#FFD173" Style::ITALIC, "variable.builtin" b"#FFAD66", - "keyword" b"#FFAD66", + "keyword" b"#FFAD66" Style::ITALIC | Style::BOLD, "number" b"#dfbfff", "operator" b"#F29E74", "punctuation" b"#cccac2", "string" b"#D5FF80", - "tag" b"#DFBFFF", - "type" b"#73D0FF", + "tag" b"#5CCFE6" Style::ITALIC | Style::BOLD, + "type" b"#73D0FF" Style::ITALIC | Style::BOLD, "variable" b"#cccac2", "variable.parameter" b"#DFBFFF", "namespace" b"#73d0ff", } + +mod semantic { + use atools::prelude::*; + use dsb::cell::Style; + macro_rules! modified { + ($count:literal $($x:literal . $mod:literal $color:literal $($style:expr)?,)+ $(,)?) => { + pub const MODIFIED: [(&str, &str); $count] = [ + $(($x, $mod),)+ + ]; + pub const MCOLORS: [[u8;3]; MODIFIED.len()] = car::map!([$($color),+], |x| color(x.tail())); + pub const MSTYLE: [u8; MODIFIED.len()] = [$(($($style, )? 0, ).0 ,)+]; + }; + } + use super::color; + theme! { 19 + "comment" b"#5c6773" Style::ITALIC, + // "decorator" b"#cccac2", + // "enumMember" b"#cccac2", + "function" b"#FFD173" Style::ITALIC, + "interface" b"#5CCFE6", + "keyword" b"#FFAD66" Style::ITALIC | Style::BOLD, + "macro" b"#fbc351" Style::BOLD, + "method" b"#FFD173" Style::ITALIC, + // "namespace" b"#cccac2", + "number" b"#dfbfff", + "operator" b"#F29E74", + // "property" b"#cccac2", + "string" b"#D5FF80", + // "struct" b"#cccac2", + // "typeParameter" b"#cccac2", + "enum" b"#73b9ff" Style::ITALIC | Style::BOLD, + "builtinType" b"#73d0ff" Style::ITALIC, + // "type" b"#73d0ff" Style::ITALIC | Style::BOLD, + "typeAlias" b"#5ce6d8" Style::ITALIC | Style::BOLD, + "struct" b"#73d0ff" Style::ITALIC | Style::BOLD, + // "variable" b"#cccac2", + // "angle" b"#cccac2", + // "arithmetic" b"#cccac2", + // "attributeBracket" b"#cccac2", + "parameter" b"#DFBFFF", + "namespace" b"#73d0ff", + // "attributeBracket" b"#cccac2", + // "attribute" b"#cccac2", + // "bitwise" b"#cccac2", + // "boolean" b"#cccac2", + // "brace" b"#cccac2", + // "bracket" b"#cccac2", + // "builtinAttribute" b"#cccac2", + // "character" b"#cccac2", + // "colon" b"#cccac2", + // "comma" b"#cccac2", + // "comparison" b"#cccac2", + // "constParameter" b"#cccac2", + "const" b"#DFBFFF", + // "deriveHelper" b"#cccac2", + // "derive" b"#cccac2", + // "dot" b"#cccac2", + // "escapeSequence" b"#cccac2", + // "formatSpecifier" b"#cccac2", + // "generic" b"#cccac2", + // "invalidEscapeSequence" b"#cccac2", + // "label" b"#cccac2", + // "lifetime" b"#cccac2", + // "logical" b"#cccac2", + "macroBang" b"#f28f74", + // "parenthesis" b"#cccac2", + // "procMacro" b"#cccac2", + // "punctuation" b"#cccac2", + "selfKeyword" b"#FFAD66" Style::ITALIC | Style::BOLD, + "selfTypeKeyword" b"#FFAD66" Style::ITALIC | Style::BOLD, + // "semicolon" b"#cccac2", + // "static" b"#cccac2", + // "toolModule" b"#cccac2", + // "union" b"#cccac2", + // "unresolvedReference" b"#cccac2", + } + modified! { 2 + "function" . "library" b"#F28779", + "variable" . "mutable" b"#e6dab6", + } +} const fn of(x: &'static str) -> usize { let mut i = 0; while i < NAMES.len() { @@ -49,12 +141,6 @@ const fn of(x: &'static str) -> usize { panic!() } -const STYLES: [u8; NAMES.len()] = amap::amap_d! { - const { of("type") } | const { of("keyword") } | const { of("tag") } => Style::ITALIC | Style::BOLD, - const { of("comment") } | const { of("function") }=> Style::ITALIC, - -}; - const fn color(x: &[u8; 6]) -> [u8; 3] { car::map!( car::map!(x, |b| (b & 0xF) + 9 * (b >> 6)).chunked::<2>(), @@ -115,6 +201,7 @@ pub struct TextArea { } impl Debug for TextArea { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + String::new(); f.debug_struct("TextArea") .field("rope", &self.rope) .field("cursor", &self.cursor) @@ -168,7 +255,8 @@ impl TextArea { .unwrap_or_default()) .min(x.saturating_sub(self.line_number_offset() + 1)) }) - .unwrap_or(self.rope.len_chars()) + .unwrap_or(usize::MAX) + .min(self.rope.len_chars()) } pub fn insert_(&mut self, c: SmolStr) { @@ -413,36 +501,7 @@ impl TextArea { } } - pub fn slice<'c>( - &self, - (c, r): (usize, usize), - cell: &'c mut [Cell], - range: Range<usize>, - ) -> &'c mut [Cell] { - let [(x1, y1), (x2, y2)] = dbg!(self.position(range)); - &mut cell[y1 * c + x1..y2 * c + x2] - } - - #[implicit_fn] - pub fn write_to( - &mut self, - color: [u8; 3], - bg: [u8; 3], - (into, (w, _)): (&mut [Cell], (usize, usize)), - (ox, oy): (usize, usize), - selection: Option<Range<usize>>, - apply: impl FnOnce((usize, usize), &mut Self, &mut [Cell]), - path: Option<&Path>, - ) { - let (c, r) = (self.c, self.r); - let mut cells = vec![ - Cell { - style: Style { color, bg, flags: 0 }, - letter: None, - }; - (self.l().max(r) + r - 1) * c - ]; - + pub fn tree_sit<'c>(&self, path: Option<&Path>, cell: &'c mut [Cell]) { let language = path .and_then(|x| LOADER.language_for_filename(x)) .unwrap_or_else(|| LOADER.language_for_name("rust").unwrap()); @@ -471,8 +530,9 @@ impl TextArea { s as u32..e as u32, ); let mut at = 0; - let mut highlight_stack = Vec::with_capacity(8); + let (c, r) = (self.c, self.r); + let mut highlight_stack = Vec::with_capacity(8); loop { let (e, new_highlights) = h.advance(); if e == HighlightEvent::Refresh { @@ -498,16 +558,50 @@ impl TextArea { c, ); - cells.get_mut(y1 * c + x1..y2 * c + x2).map(|x| { + cell.get_mut(y1 * c + x1..y2 * c + x2).map(|x| { x.iter_mut().for_each(|x| { - x.style.flags = STYLES[h.idx()]; + x.style.flags |= STYLES[h.idx()]; x.style.color = COLORS[h.idx()]; }) }); } at = end; } - drop(h); + } + + pub fn slice<'c>( + &self, + (c, r): (usize, usize), + cell: &'c mut [Cell], + range: Range<usize>, + ) -> &'c mut [Cell] { + let [(x1, y1), (x2, y2)] = self.position(range); + &mut cell[y1 * c + x1..y2 * c + x2] + } + + #[implicit_fn] + pub fn write_to<'lsp>( + &mut self, + color: [u8; 3], + bg: [u8; 3], + (into, (w, _)): (&mut [Cell], (usize, usize)), + (ox, oy): (usize, usize), + selection: Option<Range<usize>>, + apply: impl FnOnce((usize, usize), &mut Self, &mut [Cell]), + path: Option<&Path>, + tokens: Option<( + arc_swap::Guard<Arc<Box<[SemanticToken]>>>, + &SemanticTokensLegend, + )>, + ) { + let (c, r) = (self.c, self.r); + let mut cells = vec![ + Cell { + style: Style { color, bg, flags: 0 }, + letter: None, + }; + (self.l().max(r) + r - 1) * c + ]; for (l, y) in self.rope.lines().zip(0..) { for (e, x) in l.chars().take(c).zip(0..) { @@ -516,6 +610,89 @@ impl TextArea { } } } + + if let Some((t, leg)) = tokens + && t.len() > 0 + { + let mut ln = 0; + let mut ch = 0; + for t in &**t { + ln += t.delta_line; + ch = match t.delta_line { + 1.. => t.delta_start, + 0 => ch + t.delta_start, + }; + let x: Result<(usize, usize), ropey::Error> = try { + let x1 = self.rope.try_byte_to_char( + self.rope.try_line_to_byte(ln as _)? + ch as usize, + )? - self.rope.try_line_to_char(ln as _)?; + let x2 = self.rope.try_byte_to_char( + self.rope.try_line_to_byte(ln as _)? + + ch as usize + + t.length as usize, + )? - self.rope.try_line_to_char(ln as _)?; + (x1, x2) + }; + let Ok((x1, x2)) = x else { + continue; + }; + + if ln as usize * c + x1 < self.vo * c { + continue; + } else if ln as usize * c + x1 > self.vo * c + r * c { + break; + } + let slice = cells + .get_mut(ln as usize * c + x1..ln as usize * c + x2) + .expect("good slice"); + let Some(tty) = leg.token_types.get(t.token_type as usize) + else { + error!( + "issue while loading semantic token {t:?}; \ + couldnt find in legend" + ); + continue; + }; + if let Some(f) = + semantic::NAMES.iter().position(|&x| x == tty.as_str()) + { + slice.iter_mut().for_each(|x| { + x.style.color = semantic::COLORS[f]; + x.style.flags |= semantic::STYLES[f]; + }); + } + // println!( + // "{tty:?}: {}", + // slice + // .iter() + // .flat_map(|x| x.letter) + // .collect::<String>() + // ); + let mut modi = t.token_modifiers_bitset; + while modi != 0 { + let bit = modi.trailing_zeros(); + + leg.token_modifiers + .get(bit as usize) + .and_then(|modi| { + MODIFIED.iter().position(|&(x, y)| { + (x == tty.as_str()) & (y == modi.as_str()) + }) + }) + .map(|i| { + slice.iter_mut().for_each(|x| { + x.style.color = MCOLORS[i]; + x.style.flags |= MSTYLE[i]; + }); + }); + + modi &= !(1 << bit); + } + } + } else { + self.tree_sit(path, &mut cells); + } + selection.map(|x| self.position(x)).map(|[(x1, y1), (x2, y2)]| { (y1 * c + x1..y2 * c + x2).for_each(|x| { cells @@ -673,6 +850,8 @@ impl TextArea { panic!() }; assert!(r.start < r.end); + dbg!(to, &r); + self.cursor = to; self.setc(); r |