A simple CPU rendered GUI IDE experience.
| -rw-r--r-- | Cargo.toml | 18 | ||||
| -rw-r--r-- | src/bar.rs | 9 | ||||
| -rw-r--r-- | src/lsp.rs | 547 | ||||
| -rw-r--r-- | src/main.rs | 330 | ||||
| -rw-r--r-- | src/sig.rs | 60 | ||||
| -rw-r--r-- | src/text.rs | 20 |
6 files changed, 618 insertions, 366 deletions
@@ -36,6 +36,7 @@ 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" } +rust-analyzer = { version = "*", path = "../rust-analyzer/crates/rust-analyzer" } test-log = "0.2.18" lsp-types = { path = "../helix/helix-lsp-types", package = "helix-lsp-types" } env_logger = "0.11.8" @@ -50,13 +51,24 @@ itertools = "0.14.0" pin-project = "1.1.10" replace_with = "0.1.8" nucleo = "0.5.0" +tokio-util = { version = "0.7.17", features = ["rt"] } +scopeguard = "1.2.0" -[build-dependencies] -cc = "*" +[profile.dev.package.rust-analyzer] +opt-level = 3 +debug-assertions = false + +[profile.dev.package.fimg] +opt-level = 3 +debug-assertions = false + +[profile.dev.package.nucleo] +opt-level = 3 +debug-assertions = false [profile.release] debug = 2 -# overflow-checks = true +overflow-checks = true # debug-assertions = true [profile.dev] @@ -60,8 +60,13 @@ impl Bar { .progress .iter(&x.progress.guard()) .find_map(|x| match x.1 { - Some(WorkDoneProgress::Report(x)) => - x.message.clone(), + Some((WorkDoneProgress::Report(x), b)) => + format!( + "{}: {}", + b.title, + x.message.clone().unwrap_or_default() + ) + .into(), _ => None, }) { @@ -1,10 +1,11 @@ use std::collections::HashMap; +use std::mem::forget; use std::path::Path; use std::sync::Arc; use std::sync::atomic::AtomicI32; use std::sync::atomic::Ordering::Relaxed; use std::task::Poll; -use std::thread::{JoinHandle, spawn}; +use std::thread::spawn; use std::time::Instant; use Default::default; @@ -15,7 +16,7 @@ use crossbeam::channel::{ }; use log::{debug, error}; use lsp_server::{ - Message, Notification as N, Request as Rq, Response as Re, + ErrorCode, Message, Notification as N, Request as LRq, Response as Re, }; use lsp_types::notification::*; use lsp_types::request::*; @@ -23,6 +24,7 @@ use lsp_types::*; use parking_lot::Mutex; use serde_json::json; use tokio::sync::oneshot; +use tokio_util::task::AbortOnDropHandle; use winit::window::Window; pub struct Client { pub runtime: tokio::runtime::Runtime, @@ -32,8 +34,10 @@ pub struct Client { 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 progress: &'static papaya::HashMap< + ProgressToken, + Option<(WorkDoneProgress, WorkDoneProgressBegin)>, + >, pub not_rx: Receiver<N>, // pub req_rx: Receiver<Rq>, pub semantic_tokens: ( @@ -58,21 +62,23 @@ impl Client { params: serde_json::to_value(y).unwrap(), })) } - + pub fn cancel(&self, rid: i32) { + _ = self.notify::<Cancel>(&CancelParams { id: rid.into() }); + } #[must_use] - pub fn request<X: Request>( - &self, + pub fn request<'me, X: Request>( + &'me self, y: &X::Params, ) -> Result< ( impl Future<Output = Result<X::Result, oneshot::error::RecvError>> - + use<X>, + + use<'me, X>, i32, ), SendError<Message>, > { let id = self.id.fetch_add(1, std::sync::atomic::Ordering::AcqRel); - self.tx.send(Message::Request(Rq { + self.tx.send(Message::Request(LRq { id: id.into(), method: X::METHOD.into(), params: serde_json::to_value(y).unwrap(), @@ -83,8 +89,13 @@ impl Client { self.send_to.send((id, tx)).expect("oughtnt really fail"); } Ok(( - async { + async move { + let g = scopeguard::guard((), |()| { + self.cancel(id); + }); + rx.await.map(|x| { + forget(g); serde_json::from_value::<X::Result>( x.result.unwrap_or_default(), ) @@ -139,8 +150,8 @@ impl Client { self.request::<ResolveCompletionItem>(&x).map(|x| x.0) } - pub fn request_complete( - &self, + pub fn request_complete<'me>( + &'me self, f: &Path, (x, y): (usize, usize), c: CompletionContext, @@ -149,7 +160,7 @@ impl Client { Option<CompletionResponse>, oneshot::error::RecvError, >, - > + use<> { + > + use<'me> { let (rx, _) = self .request::<Completion>(&CompletionParams { text_document_position: TextDocumentPositionParams { @@ -168,12 +179,36 @@ impl Client { rx } + pub fn request_sig_help<'me>( + &'me self, + f: &Path, + (x, y): (usize, usize), + ) -> impl Future< + Output = Result<Option<SignatureHelp>, oneshot::error::RecvError>, + > + use<'me> { + self.request::<SignatureHelpRequest>(&SignatureHelpParams { + context: None, + text_document_position_params: TextDocumentPositionParams { + text_document: { + TextDocumentIdentifier { + uri: Url::from_file_path(f).unwrap(), + } + }, + position: Position { line: y as _, character: x as _ }, + }, + work_done_progress_params: default(), + }) + .unwrap() + .0 + } + pub fn rq_semantic_tokens( - &self, + &'static self, f: &Path, w: Option<Arc<Window>>, ) -> anyhow::Result<()> { debug!("requested semantic tokens"); + let Some(b"rs") = f.extension().map(|x| x.as_encoded_bytes()) else { return Ok(()); @@ -181,8 +216,6 @@ impl Client { 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(); } } @@ -215,18 +248,9 @@ impl Client { } } pub fn run( - (tx, rx, iot): ( - Sender<Message>, - Receiver<Message>, - lsp_server::IoThreads, - ), + (tx, rx): (Sender<Message>, Receiver<Message>), workspace: WorkspaceFolder, -) -> ( - Client, - lsp_server::IoThreads, - JoinHandle<()>, - oneshot::Sender<Arc<Window>>, -) { +) -> (Client, std::thread::JoinHandle<()>, oneshot::Sender<Arc<Window>>) { let now = Instant::now(); let (req_tx, req_rx) = unbounded(); let (not_tx, not_rx) = unbounded(); @@ -251,189 +275,195 @@ pub fn run( send_to: req_tx, not_rx, }; - _ = c.request::<Initialize>(&InitializeParams { - process_id: Some(std::process::id()), + _ = 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 { - hover: Some(HoverClientCapabilities { - dynamic_registration: None, - content_format: Some(vec![ - MarkupKind::PlainText, - MarkupKind::Markdown, - ]), + capabilities: ClientCapabilities { + window: Some(WindowClientCapabilities { + work_done_progress: Some(true), + ..default() }), - 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], + + text_document: Some(TextDocumentClientCapabilities { + 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), }), + 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() }), - - 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] + 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::]), }), - label_details_support: Some(true), - ..default() + context_support: None, + insert_text_mode: Some(InsertTextMode::AS_IS), + completion_list: Some(CompletionListCapability { item_defaults: None }), }), - 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::]), + semantic_tokens: Some(SemanticTokensClientCapabilities { + dynamic_registration: Some(false), + requests: SemanticTokensClientCapabilitiesRequests { + range: Some(true), + full: Some(lsp_types::SemanticTokensFullOptions::Bool(true)), }, - ), - context_support: None, - insert_text_mode: Some(InsertTextMode::AS_IS), - completion_list: Some(CompletionListCapability { - item_defaults: None, + 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() }), - }), - 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![]), + 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() }), - position_encodings: Some(vec![PositionEncodingKind::UTF8]), + experimental: Some(json! {{ + "serverStatusNotification": true, + "hoverActions": true, + }}), ..default() + }, + client_info: Some(ClientInfo { + name: "gracilaria".into(), + version: Some(env!("CARGO_PKG_VERSION").into()), }), - experimental: Some(json! {{ - "serverStatusNotification": true, - "hoverActions": true, + initialization_options: Some(json! {{ + "cargo": { + "buildScripts": { "enable": true } + }, + "procMacro": { + "enable": true, + "attributes": { "enable": true } + }, + "inlayHints": { + "closureReturnTypeHints": { "enable": "with_block" }, + "closingBraceHints": { "minLines": 5 }, + "closureStyle": "rust_analyzer", + "genericParameterHints": { + "type": { "enable": true } }, + "rangeExclusiveHints": { "enable": true }, + "closureCaptureHints": { "enable": true }, + "expressionAdjustmentHints": { + "hideOutsideUnsafe": true, + "enable": "never", + "mode": "prefer_prefix" + } + }, + "semanticHighlighting": { + "punctuation": { + "separate": { + "macroBang": true + }, + "specialization": { "enable": true }, + "enable": true + } + }, + "showUnlinkedFileNotification": false, + "completion": { + "fullFunctionSignatures": { "enable": true, }, + "autoIter": { "enable": false, }, + "autoImport": { "enable": true, }, + "termSearch": { "enable": true, }, + "autoself": { "enable": true, }, + "privateEditable": { "enable": 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 } - }, - "inlayHints": { - "closureReturnTypeHints": { "enable": "with_block" }, - "closingBraceHints": { "minLines": 5 }, - "closureStyle": "rust_analyzer", - "genericParameterHints": { - "type": { "enable": true } }, - "rangeExclusiveHints": { "enable": true }, - "closureCaptureHints": { "enable": true }, - "expressionAdjustmentHints": { - "hideOutsideUnsafe": true, - "enable": "never", - "mode": "prefer_prefix" - } - }, - "semanticHighlighting": { - "punctuation": { - "separate": { - "macroBang": true - }, - "specialization": { "enable": true }, - "enable": true - } - }, - "showUnlinkedFileNotification": false, - "completion": { - "fullFunctionSignatures": { "enable": true, }, - "autoIter": { "enable": false, }, - "autoImport": { "enable": true, }, - "termSearch": { "enable": true, }, - "autoself": { "enable": true, }, - "privateEditable": { "enable": true }, - }, - }}), - trace: None, - workspace_folders: Some(vec![workspace]), + trace: None, + workspace_folders: Some(vec![workspace]), - ..default() - }) - .unwrap(); + ..default() + }) + .unwrap(); let x = serde_json::from_value::<InitializeResult>( rx.recv().unwrap().response().unwrap().result.unwrap(), ) @@ -466,7 +496,7 @@ CompletionItemKind::TYPE_PARAMETER] Err(RecvError) => return, }, recv(rx) -> x => match x { - Ok(Message::Request(rq @ Rq { method: "window/workDoneProgress/create", .. })) => { + Ok(Message::Request(rq @ LRq { method: "window/workDoneProgress/create", .. })) => { match rq.load::<WorkDoneProgressCreate>() { Ok((_, x)) => { let g = progress.guard(); @@ -484,14 +514,20 @@ CompletionItemKind::TYPE_PARAMETER] } } Ok(Message::Response(x)) => { - if let Some((s, took)) = map.remove(&x.id.i32()) { - debug!("request {} took {:?}", x.id, took.elapsed()); + if let Some(e) =& x.error { + if e.code == ErrorCode::RequestCanceled as i32 {} + else { + error!("received error from lsp for response {x:?}"); + } + + } + else if let Some((s, took)) = 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:?}", - ); } } @@ -501,7 +537,14 @@ CompletionItemKind::TYPE_PARAMETER] } 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()); + 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)) => { @@ -515,7 +558,7 @@ CompletionItemKind::TYPE_PARAMETER] } } }); - (c, iot, h, window_tx) + (c, h, window_tx) } // trait RecvEepy<T>: Sized { @@ -614,3 +657,103 @@ impl<T, U, F: FnMut(T) -> U, Fu: Future<Output = T>> Map_<T, U, F> for Fu { Map(self, f) } } +use tokio::task; +#[derive(Debug)] +pub enum OnceOff<T> { + Waiting(task::JoinHandle<Result<T, oneshot::error::RecvError>>), + Waited(T), +} +impl<T> OnceOff<T> { + pub fn poll( + &mut self, + r: &tokio::runtime::Runtime, + ) -> anyhow::Result<()> { + match self { + OnceOff::Waiting(join_handle) if join_handle.is_finished() => { + *self = Self::Waited(r.block_on(join_handle)??); + } + _ => {} + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct Rq<T, R: Request, D = ()> { + pub result: Option<T>, + pub request: Option<( + AbortOnDropHandle<Result<R::Result, oneshot::error::RecvError>>, + D, + )>, +} +impl<T, R: Request, D> Default for Rq<T, R, D> { + fn default() -> Self { + Self { result: None, request: None } + } +} +impl<T, R: Request> Rq<T, R, ()> { + pub fn new( + f: task::JoinHandle<Result<R::Result, oneshot::error::RecvError>>, + ) -> Self { + Self { + request: Some((AbortOnDropHandle::new(f), ())), + result: None, + } + } + pub fn request( + &mut self, + f: task::JoinHandle<Result<R::Result, oneshot::error::RecvError>>, + ) { + self.request = Some((AbortOnDropHandle::new(f), ())); + } +} +impl<T, R: Request, D> Rq<T, R, D> { + pub fn running(&self) -> bool { + matches!( + self, + Self { result: Some(_), .. } | Self { request: Some(_), .. }, + ) + } + pub fn poll( + &mut self, + f: impl FnOnce( + Result<R::Result, oneshot::error::RecvError>, + D, + ) -> Option<T>, + runtime: &tokio::runtime::Runtime, + ) { + 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; + } + }; + self.result = f(x, d); + } + } +} + +pub trait RedrawAfter { + fn redraw_after<T, F: Future<Output = T>>( + &self, + f: F, + ) -> impl Future<Output = T> + use<Self, T, F>; +} +impl RedrawAfter for Arc<Window> { + fn redraw_after<T, F: Future<Output = T>>( + &self, + f: F, + ) -> impl Future<Output = T> + use<T, F> { + let w: Arc<Window> = self.clone(); + f.map(move |x| { + w.request_redraw(); + x + }) + } +} diff --git a/src/main.rs b/src/main.rs index a368b4c..dcb619a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,13 +27,10 @@ )] #![allow(incomplete_features, redundant_semicolons)] use std::borrow::Cow; -use std::io::BufReader; +use std::fs::read_dir; use std::num::NonZeroU32; use std::path::{Path, PathBuf}; -use std::pin::Pin; -use std::process::{Command, Stdio}; -use std::sync::{Arc, LazyLock, OnceLock}; -use std::thread; +use std::sync::{Arc, LazyLock}; use std::time::Instant; use Default::default; @@ -42,16 +39,20 @@ use atools::prelude::AASAdd; use diff_match_patch_rs::PatchInput; use diff_match_patch_rs::traits::DType; use dsb::cell::Style; -use dsb::{Cell, F}; +use dsb::{Cell, F, Fonts}; use fimg::{Image, OverlayAt}; -use lsp_types::request::HoverRequest; +use lsp::{OnceOff, Rq}; +use lsp_server::Connection; +use lsp_types::request::{HoverRequest, Request, SignatureHelpRequest}; use lsp_types::*; use parking_lot::Mutex; use regex::Regex; use ropey::Rope; use rust_fsm::StateMachine; use swash::{FontRef, Instance}; -use tokio::task::{JoinHandle, spawn_blocking}; +use tokio::runtime::Runtime; +use tokio::task::{JoinError, JoinHandle, spawn_blocking}; +use tokio_util::task::AbortOnDropHandle; use url::Url; use winit::event::{ ElementState, Event, MouseButton, MouseScrollDelta, WindowEvent, @@ -59,15 +60,17 @@ use winit::event::{ use winit::event_loop::{ControlFlow, EventLoop}; use winit::keyboard::{Key, ModifiersState, NamedKey, SmolStr}; use winit::platform::wayland::WindowAttributesExtWayland; -use winit::window::{Icon, Window}; +use winit::window::Icon; use crate::bar::Bar; use crate::hov::Hovr; +use crate::lsp::RedrawAfter; use crate::text::{Diff, TextArea, is_word}; mod bar; pub mod com; pub mod hov; mod lsp; +mod sig; mod sni; mod text; mod winit_app; @@ -197,18 +200,24 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { .and_then(|x| x.canonicalize().ok()); let c = workspace.as_ref().zip(origin.clone()).map( |(workspace, origin)| { - let mut c = Command::new("rust-analyzer") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .spawn() + // let mut c = Command::new("rust-analyzer") + // .stdin(Stdio::piped()) + // .stdout(Stdio::piped()) + // .stderr(Stdio::inherit()) + // .spawn() + // .unwrap(); + let (a, b) = Connection::memory(); + std::thread::Builder::new() + .name("Rust Analyzer".into()) + .stack_size(1024 * 1024 * 8) + .spawn(|| rust_analyzer::bin::run_server(b)) .unwrap(); - - let (c, t, t2, changed) = lsp::run( - lsp_server::stdio::stdio_transport( - BufReader::new(c.stdout.take().unwrap()), - c.stdin.take().unwrap(), - ), + let (c, t2, changed) = lsp::run( + (a.sender, a.receiver), + // 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 @@ -220,7 +229,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { ); c.open(&origin, std::fs::read_to_string(&origin).unwrap()) .unwrap(); - (&*Box::leak(Box::new(c)), (t, t2), changed) + (&*Box::leak(Box::new(c)), (t2), changed) }, ); let (lsp, t, mut w) = match c { @@ -229,11 +238,13 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { }; macro_rules! lsp { () => { - lsp.as_ref().zip(origin.as_deref()) + lsp.zip(origin.as_deref()) }; } let hovering = &*Box::leak(Box::new(Mutex::new(None::<hov::Hovr>))); let mut complete = CompletionState::None; + let mut sig_help = + Rq::<SignatureHelp, SignatureHelpRequest, ()>::default(); // let mut complete = None::<(CompletionResponse, (usize, usize))>; // let mut complete_ = None::<( // JoinHandle< @@ -322,23 +333,12 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { state.consume(Action::Changed).unwrap(); window.request_redraw(); } - if let CompletionState::Complete(o, x)= &mut complete && - x.as_ref().is_some_and(|(x, _)|x.is_finished()) && - let Some((task, c)) = x.take() && let Some(ref l) = lsp{ - // if text.cursor() ==* c_ { - *o = l.runtime.block_on(task).ok().and_then(Result::ok).flatten().map(|x| Complete { -r:x,start:c,selection:0,vo:0, - } ); - if let Some(x) = o { - std::fs::write("complete_", serde_json::to_string_pretty(&x.r).unwrap()).unwrap(); - // println!("resolved") - } - // println!("{complete:#?}"); - // } else { - // println!("abort {c_:?}"); - // x.abort(); - // } + if let CompletionState::Complete(rq)= &mut complete && let Some(ref l) = lsp{ + rq.poll(|f, c| { + f.ok().flatten().map(|x| {Complete {r:x,start:c,selection:0,vo:0,}}) + }, &l.runtime); } + lsp.map(|c| sig_help.poll(|x, ()| x.ok().flatten(), &c.runtime)); match event { Event::AboutToWait => {} Event::WindowEvent { @@ -471,36 +471,22 @@ r:x,start:c,selection:0,vo:0, i.as_mut(),(0,0) ) }; - if let CompletionState::Complete(Some(ref x,),_) = complete { - let c = com::s(x, 40,&filter(&text)); - if c.len() == 0 { - complete.consume(CompletionAction::NoResult).unwrap(); - } else { - let ppem = 20.0; + let place_around_cursor = |(_x, _y): (usize, usize), fonts: &mut Fonts,mut i:Image<&mut [u8], 3> ,c: &[Cell],columns:usize, ppem_:f32,ls_:f32, ox:f32, oy: f32, toy: f32| { let met = FONT.metrics(&[]); let fac = ppem / met.units_per_em as f32; - let (_x, _y) = text.cursor(); - let _x = _x + text.line_number_offset()+1; - - // let [(_x, _y), (_x2, _)] = text.position(text.cursor); - // let [_x, _x2] = [_x, _x2].add(text.line_number_offset()+1); - let _y = _y.wrapping_sub(text.vo); - // if !(cursor_position.1 == _y && (_x..=_x2).contains(&cursor_position.0)) { - // return; - // } let position = ( - ((_x) as f32 * fw).round() as usize, - ((_y as f32 as f32) * (fh + ls * fac)).round() as usize, + (((_x) as f32 * fw).round() + ox) as usize, + (((_y) as f32 * (fh + ls * fac)).round() + oy) as usize, ); - - - - let ls = 10.0; - let mut r = c.len()/40; - let (w, h) = dsb::size(&fonts.regular, ppem, ls, (40, r)); - let top = position.1.checked_sub(h).unwrap_or((((_y + 1) as f32) * (fh + ls * fac)).round() as usize,); - let (_, y) = dsb::fit(&fonts.regular, ppem, ls, (window.inner_size().width as _ /* - left */,( window.inner_size().height as usize - top ) )); + assert!(position.0 < 8000 && position.1 < 8000); + let ppem = ppem_; + let ls = ls_; + let mut r = c.len()/columns; + let (w, h) = dsb::size(&fonts.regular, ppem, ls, (columns, r)); + let is_above = position.1.checked_sub(h).is_some(); + let top = position.1.checked_sub(h).unwrap_or(((((_y + 1) as f32) * (fh + ls * fac)).round() + toy) as usize); + let (_, y) = dsb::fit(&fonts.regular, ppem, ls, (window.inner_size().width as _ /* - left */,(window.inner_size().height as usize - top) )); r = r.min(y); let left = @@ -508,66 +494,97 @@ r:x,start:c,selection:0,vo:0, window.inner_size().width as usize- w as usize } else { position.0 }; - let (w, h) = dsb::size(&fonts.regular, ppem, ls, (40, r)); - // let mut i2 = Image::build(w as _, h as _).fill(BG); + let (w, h) = dsb::size(&fonts.regular, ppem, ls, (columns, r)); unsafe{ dsb::render( &c, - (40, 0), + (columns, 0), ppem, - &mut fonts, + fonts, ls, - true, - i.as_mut(), + true, + i.copy(), (left as _, top as _) )}; - // dbg!(w, h, i2.width(), i2.height(), window.inner_size(), i.width(),i.height()); - // unsafe { i.overlay_at(&i2.as_ref(), left as u32, top as u32) }; - i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w as _,h as _, [0;3]); - } - } + (is_above, left, top, w, h) + }; hovering.lock().as_ref().map(|x| x.span.clone().map(|sp| { - let met = FONT.metrics(&[]); - let fac = ppem / met.units_per_em as f32; let [(_x, _y), (_x2, _)] = text.position(sp); let [_x, _x2] = [_x, _x2].add(text.line_number_offset()+1); let _y = _y.wrapping_sub(text.vo); if !(cursor_position.1 == _y && (_x..=_x2).contains(&cursor_position.0)) { return; } - let position = ( - ((_x) as f32 * fw).round() as usize, - ((_y as f32 as f32) * (fh + ls * fac)).round() as usize, - ); - let ppem = 18.0; - let ls = 10.0; - let mut r = x.item.l().min(15); - let (w, h) = dsb::size(&fonts.regular, ppem, ls, (x.item.c, r)); - let top = position.1.checked_sub(h).unwrap_or((((_y + 1) as f32) * (fh + ls * fac)).round() as usize,); - let (_, y) = dsb::fit(&fonts.regular, ppem, ls, (window.inner_size().width as _ /* - left */,( window.inner_size().height as usize - top ) )); - r = r.min(y); + let r = x.item.l().min(15); let c = x.item.displayable(r); - let left = - if position.0 + w as usize > window.inner_size().width as usize { - window.inner_size().width as usize- w as usize - } else { position.0 }; - - let (w, h) = dsb::size(&fonts.regular, ppem, ls, (x.item.c, r)); - // let mut i2 = Image::build(w as _, h as _).fill(BG); - unsafe{ dsb::render( - &c, - (x.item.c, 0), - ppem, + let (_,left, top, w, h) = place_around_cursor( + (_x, _y), &mut fonts, - ls, - true, i.as_mut(), - (left as _, top as _) - )}; - // dbg!(w, h, i2.width(), i2.height(), window.inner_size(), i.width(),i.height()); - // unsafe { i.overlay_at(&i2.as_ref(), left as u32, top as u32) }; + c, x.item.c, + 18.0, 10.0, 0., 0., 0. + ); i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w as _,h as _, [0;3]); })); + let com = match complete { + CompletionState::Complete(Rq{ result: Some(ref x,),..}) => { + let c = com::s(x, 40,&filter(&text)); + if c.len() == 0 { + complete.consume(CompletionAction::NoResult).unwrap(); None + } else { Some(c) }}, + _ => None, + }; + 'out: {if let Rq{result: Some(ref x), .. } = sig_help { + let (sig, p) = sig::active(x); + let c = sig::sig((sig, p), 40); + let (_x, _y) = text.cursor(); + let _x = _x + text.line_number_offset()+1; + let Some(_y) = _y.checked_sub(text.vo) else { break 'out }; + let (is_above,left, top, w, mut h) = place_around_cursor((_x, _y), &mut fonts, i.as_mut(), &c, 40, ppem, ls, 0., 0., 0.); + i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w as _,h as _, [0;3]); + + let com = com.map(|c| { + let (is_above_,left, top, w_, h_) = place_around_cursor( + (_x, _y), + &mut fonts, + i.as_mut(), + &c, 40, ppem, ls, 0., -(h as f32), if is_above { 0.0 } else { h as f32 } + ); + i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w_ as _,h_ as _, [0;3]); + if is_above { // completion below, we need to push the docs, if any, below only below us, if the sig help is still above. + h = h_; + } else { + h+=h_; + } + (is_above_, left, top, w_, h_) + }); + { + let ppem = 15.0; + let ls = 10.0; + let (fw, _) = dsb::dims(&FONT, ppem); + let cols = (w as f32 / fw).floor() as usize; + sig::doc(sig, cols) .map(|cells| { + let cells = cells.displayable(cells.l().min(15)); + let (_,left_, top_, w_, h_) = place_around_cursor((_x, _y), + &mut fonts, i.as_mut(), cells, cols, ppem, ls, + 0., -(h as f32), if is_above { com.filter(|x| !x.0).map(|(is, l, t, w, h)| h).unwrap_or_default() as f32 } else { h as f32 }); + i.r#box((left_.saturating_sub(1) as _, top_.saturating_sub(1) as _), w as _,h_ as _, [0;3]); + }); + } + } else if let Some(c) = com { + let ppem = 20.0; + let (_x, _y) = text.cursor(); + let _x = _x + text.line_number_offset()+1; + let _y = _y.wrapping_sub(text.vo); + let (_,left, top, w, h) = place_around_cursor( + (_x, _y), + &mut fonts, + i.as_mut(), + &c, 40, ppem, ls, 0., 0., 0. + ); + i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w as _,h as _, [0;3]); + } + } let met = FONT.metrics(&[]); let fac = ppem / met.units_per_em as f32; // if x.view_o == Some(x.cells.row) || x.view_o.is_none() { @@ -658,12 +675,12 @@ r:x,start:c,selection:0,vo:0, // assert_eq!(hover, text.index_at(cursor_position)); let (x, y) =text.xy(hover); let text = text.clone(); - { + 'out: { let mut l = hovering.lock(); if let Some(Hovr{ span: Some(span),..}) = &*l { let [(_x, _y), (_x2, _)] = text.position(span.clone()); let [_x, _x2] = [_x, _x2].add(text.line_number_offset()+1); - let _y = _y - text.vo; + let Some(_y) = _y.checked_sub(text.vo) else { break 'out }; if cursor_position.1 == _y && (_x.._x2).contains(&cursor_position.0) { return } else { @@ -844,34 +861,41 @@ RUNNING.remove(&hover,&RUNNING.guard()); } text.scroll_to_cursor(); - if cb4 != text.cursor && let CompletionState::Complete(Some(c), t)= &mut complete - && ((text.cursor < c.start) || (!is_word(text.at_())&& (text.at_() != '.' || text.at_() != ':')) ) { - if let Some((x, _)) = t.take() { - x.abort(); - } + if cb4 != text.cursor && let CompletionState::Complete(Rq{ result: Some(c),.. })= &mut complete + && ((text.cursor < c.start) || (!is_word(text.at_())&& (text.at_() != '.' || text.at_() != ':')) ) { complete = CompletionState::None; } - + if sig_help.running() && cb4 != text.cursor && let Some((lsp, path)) = lsp!() { + sig_help.request(lsp.runtime.spawn(window.redraw_after(lsp.request_sig_help(path, text.cursor())))); + } if hist.record(&text) { change!(); } lsp!().map(|(lsp, o)|{ let window = window.clone(); + match event.logical_key.as_ref() { + Key::Character(y) + if let Some(x) = &lsp.initialized + && let Some(x) = &x.capabilities.signature_help_provider + && let Some(x) = &x.trigger_characters && x.contains(&y.to_string()) => { + sig_help.request(lsp.runtime.spawn(window.redraw_after(lsp.request_sig_help(o, text.cursor())))); + }, + _ => {} + } match complete.consume(CompletionAction::K(event.logical_key.as_ref())).unwrap(){ Some(CDo::Request(ctx)) => { - let x = lsp.request_complete(o, text.cursor(), ctx); - let h = lsp.runtime.spawn(async move { - x.await.inspect(|_| window.request_redraw()) - }); - let CompletionState::Complete(c, x) = &mut complete else { panic!()}; + let h = AbortOnDropHandle::new(lsp.runtime.spawn( + window.redraw_after(lsp.request_complete(o, text.cursor(), ctx)) + )); + let CompletionState::Complete(Rq{ request : x, result: c, }) = &mut complete else { panic!()}; *x = Some((h,c.as_ref().map(|x|x.start).or(x.as_ref().map(|x|x.1)).unwrap_or(text.cursor))); } Some(CDo::SelectNext) => { - let CompletionState::Complete(Some(c), _) = &mut complete else { panic!()}; + let CompletionState::Complete(Rq{ result: Some(c), .. }) = &mut complete else { panic!()}; c.next(&filter(&text)); } Some(CDo::SelectPrevious) => { - let CompletionState::Complete(Some(c), _) = &mut complete else { panic!()}; + let CompletionState::Complete(Rq{ result: Some(c), .. }) = &mut complete else { panic!()}; c.back(&filter(&text)); } Some(CDo::Finish(x)) => { @@ -899,11 +923,9 @@ RUNNING.remove(&hover,&RUNNING.guard()); } if hist.record(&text) { change!();} + sig_help = Rq::new(lsp.runtime.spawn(window.redraw_after(lsp.request_sig_help(o, text.cursor())))); } - Some(CDo::Abort(())) => {} - None => {return}, - _ => panic!(), }; }); @@ -970,12 +992,11 @@ RUNNING.remove(&hover,&RUNNING.guard()); text.insert(&clipp::paste()); hist.push_if_changed(&text); } - Some(Do::OpenFile(x)) => { - origin = Some(PathBuf::from(&x)); + Some(Do::OpenFile(x)) => { let _: anyhow::Result<()> = try { + origin = Some(PathBuf::from(&x).canonicalize()?); text = TextArea::default(); - text.insert( - &std::fs::read_to_string(x).unwrap(), - ); + let new = std::fs::read_to_string(x)?; + text.insert(&new); text.cursor = 0; hist = Hist { history: vec![], @@ -984,6 +1005,16 @@ RUNNING.remove(&hover,&RUNNING.guard()); last_edit: Instant::now(), changed: false, }; + complete = CompletionState::None; + mtime = modify!(); + + lsp!().map(|(x, origin)| { + x.semantic_tokens.0.store(Arc::new(vec![].into())); + x.open(&origin,new).unwrap(); + x.rq_semantic_tokens(origin, Some(window.clone())).unwrap(); + }); + bar.last_action = "open".to_string(); + }; } Some( Do::MoveCursor | Do::ExtendSelectionToMouse | Do::Hover, @@ -1186,6 +1217,7 @@ RequestBoolean(t) => { K(_) => RequestBoolean(t), C(_) => _, Changed => _, + M(_) => _, }, Search((x, y, m)) => { M(MouseButton => MouseButton::Left) => Default [MoveCursor], @@ -1260,37 +1292,27 @@ rust_fsm::state_machine! { pub(crate) CompletionState => CompletionAction<'i> => CDo None => Click => None, None => K(Key<&'i str> => Key::Character(k @ ("." | ":"))) => Complete( - (Option<Complete>, Option<(JoinHandle< - Result< - Option<CompletionResponse>, - tokio::sync::oneshot::error::RecvError, - >, - >, usize)>) => (None,None) + Rq<Complete, lsp_types::request::Completion, usize> => default() ) [Request(CompletionContext => CompletionContext {trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER, trigger_character:Some(k.to_string()) })], - None => K(Key::Named(NamedKey::Space) if ctrl()) => Complete((None, None)) [Request(CompletionContext { trigger_kind: CompletionTriggerKind::INVOKED, trigger_character:None })], - None => K(Key::Character(x) if x.chars().next().is_some_and(is_word)) => Complete((None,None)) [Request(CompletionContext { trigger_kind: CompletionTriggerKind::INVOKED, trigger_character:None })], + None => K(Key::Named(NamedKey::Space) if ctrl()) => Complete(default()) [Request(CompletionContext { trigger_kind: CompletionTriggerKind::INVOKED, trigger_character:None })], + None => K(Key::Character(x) if x.chars().all(char::is_alphabetic)) => Complete(default()) [Request(CompletionContext { trigger_kind: CompletionTriggerKind::INVOKED, trigger_character:None })], None => K(_) => _, // when - Complete((Some(_x),_y)) => K(Key::Named(NamedKey::Tab) if shift()) => _ [SelectPrevious], - Complete((Some(_x),_y)) => K(Key::Named(NamedKey::Tab)) => _ [SelectNext], - Complete((Some(_x),_y)) => K(Key::Named(NamedKey::ArrowDown)) => _ [SelectNext], - Complete((Some(_x),_y)) => K(Key::Named(NamedKey::ArrowUp)) => _ [SelectPrevious], + Complete(Rq{ result: Some(_x),request: _y }) => K(Key::Named(NamedKey::Tab) if shift()) => _ [SelectPrevious], + Complete(Rq { result: Some(_x),request: _y }) => K(Key::Named(NamedKey::Tab)) => _ [SelectNext], + Complete(Rq { result: Some(_x),request: _y }) => K(Key::Named(NamedKey::ArrowDown)) => _ [SelectNext], + Complete(Rq { result: Some(_x),request: _y }) => K(Key::Named(NamedKey::ArrowUp)) => _ [SelectPrevious], // exit cases - Complete((_x, None)) => Click => None, - Complete((_x, None)) => NoResult => None, - Complete((_x, Some((y, _))))=> K(Key::Named(Escape)) => None [Abort(((),) => y.abort())], - Complete((_x, None)) => K(Key::Named(Escape)) => None, - Complete((_x, Some((y, _)))) => Click => None [Abort(((),) => y.abort())], - Complete((_x, Some((y, _)))) => K(Key::Character(x) if !x.chars().all(is_word)) => None [Abort(y.abort())], - Complete((_x, None)) => K(Key::Character(x) if !x.chars().all(is_word)) => None, + Complete(_) => Click => None, + Complete(_) => NoResult => None, + Complete(_)=> K(Key::Named(Escape)) => None, + Complete(_) => K(Key::Character(x) if !x.chars().all(is_word)) => None, - Complete((Some(x), task)) => K(Key::Named(NamedKey::Enter)) => None [Finish(Complete => { - task.map(|(task, _)| task.abort()); x - })], + Complete(Rq { result: Some(x), .. }) => K(Key::Named(NamedKey::Enter)) => None [Finish(Complete => x)], - Complete((_x, _y)) => K(_) => _ [Request(CompletionContext { trigger_kind: CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS, trigger_character:None })], + Complete(_x) => K(_) => _ [Request(CompletionContext { trigger_kind: CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS, trigger_character:None })], } use com::Complete; impl Default for CompletionState { @@ -1308,6 +1330,10 @@ fn filter(text: &TextArea) -> String { .collect::<String>() } } -fn frunctinator(parameter1:usize, parameter2:u8, paramter4:u16) -> usize { +fn frunctinator( + parameter1: usize, + parameter2: u8, + paramter4: u16, +) -> usize { 0 -}
\ No newline at end of file +} diff --git a/src/sig.rs b/src/sig.rs new file mode 100644 index 0000000..427e37e --- /dev/null +++ b/src/sig.rs @@ -0,0 +1,60 @@ +use std::iter::{repeat, repeat_n}; + +use dsb::Cell; +use dsb::cell::Style; +use lsp_types::{ + MarkupContent, MarkupKind, ParameterInformation, SignatureHelp, + SignatureInformation, +}; + +use crate::FG; +use crate::text::{CellBuffer, color, color_}; +pub fn active( + sig: &SignatureHelp, +) -> (&SignatureInformation, Option<&ParameterInformation>) { + let y = &sig.signatures[sig.active_signature.unwrap_or(0) as usize]; + ( + y, + sig.active_parameter + .zip(y.parameters.as_ref()) + .and_then(|(i, x)| x.get(i as usize)), + ) +} + +pub fn sig( + (y, p): (&SignatureInformation, Option<&ParameterInformation>), + c: usize, +) -> Vec<Cell> { + let bg = color_("#1c212b"); + let ds: Style = Style { bg: bg, color: FG, flags: 0 }; + let d: Cell = Cell { letter: None, style: ds }; + let sig = y.label.chars().zip(0..).map(|(x, i)| { + let mut a = ds.basic(x); + if p.is_some_and(|x| match x.label { + lsp_types::ParameterLabel::Simple(_) => false, + lsp_types::ParameterLabel::LabelOffsets([a, b]) => + (a..b).contains(&i), + }) { + a.style |= (Style::BOLD, color_("#ffcc66")); + } + a + }); + let mut v = sig.collect::<Vec<_>>(); + v.extend(repeat_n(d, c - (v.len() % c))); + v +} +pub fn doc(sig: &SignatureInformation, c: usize) -> Option<CellBuffer> { + sig.documentation + .as_ref() + .map(|x| match x { + lsp_types::Documentation::String(x) => x, + lsp_types::Documentation::MarkupContent(MarkupContent { + kind: _, + value, + }) => value, + }) + .and_then(|x| { + crate::hov::p(&x).map(|node| crate::hov::markdown2(c, &node)) + }) + .map(|cells| CellBuffer { c, vo: 0, cells: cells.into() }) +} diff --git a/src/text.rs b/src/text.rs index 543ccdf..f16a2ab 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,7 +1,5 @@ -use std::cmp::{Ordering, min}; +use std::cmp::min; use std::fmt::{Debug, Display}; -use std::iter::empty; -use std::marker::Tuple; use std::ops::{Deref, Index, IndexMut, Not as _, Range, RangeBounds}; use std::path::Path; use std::pin::pin; @@ -342,8 +340,16 @@ impl TextArea { crate::sni::Snippet::parse(&x.new_text, begin) .ok_or(anyhow!("failed to parse snippet"))?; self.rope.try_insert(begin, &tex)?; - self.cursor = sni.next().unwrap().r().end; - self.tabstops = Some(sni); + self.cursor = match sni.next() { + Some(x) => { + self.tabstops = Some(sni); + x.r().end + } + None => { + self.tabstops = None; + end + } + }; Ok(()) } pub fn cursor(&self) -> (usize, usize) { @@ -805,9 +811,9 @@ impl TextArea { }; if ln as usize * c + x1 < self.vo * c { - // continue; + continue; } else if ln as usize * c + x1 > self.vo * c + r * c { - // break; + break; } let Some(tty) = leg.token_types.get(t.token_type as usize) else { |